]> git.mxchange.org Git - flightgear.git/blob - src/Traffic/TrafficMgr.cxx
Nasal: use SG_LOG for security error messages to avoid truncation
[flightgear.git] / src / Traffic / TrafficMgr.cxx
1 /******************************************************************************
2  * TrafficMGr.cxx
3  * Written by Durk Talsma, started May 5, 2004.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  *
19  *
20  **************************************************************************/
21
22 /* 
23  * Traffic manager parses airlines timetable-like data and uses this to 
24  * determine the approximate position of each AI aircraft in its database.
25  * When an AI aircraft is close to the user's position, a more detailed 
26  * AIModels based simulation is set up. 
27  * 
28  * I'm currently assuming the following simplifications:
29  * 1) The earth is a perfect sphere
30  * 2) Each aircraft flies a perfect great circle route.
31  * 3) Each aircraft flies at a constant speed (with infinite accelerations and
32  *    decelerations) 
33  * 4) Each aircraft leaves at exactly the departure time. 
34  * 5) Each aircraft arrives at exactly the specified arrival time. 
35  *
36  *
37  *****************************************************************************/
38
39 #ifdef HAVE_CONFIG_H
40 #  include "config.h"
41 #endif
42
43 #include <stdlib.h>
44 #include <time.h>
45 #include <cstring>
46 #include <iostream>
47 #include <fstream>
48
49
50 #include <string>
51 #include <vector>
52 #include <algorithm>
53 #include <boost/foreach.hpp>
54
55 #include <simgear/compiler.h>
56 #include <simgear/misc/sg_path.hxx>
57 #include <simgear/misc/sg_dir.hxx>
58 #include <simgear/props/props.hxx>
59 #include <simgear/structure/subsystem_mgr.hxx>
60 #include <simgear/structure/exception.hxx>
61
62 #include <simgear/xml/easyxml.hxx>
63 #include <simgear/threads/SGThread.hxx>
64 #include <simgear/threads/SGGuard.hxx>
65 #include <simgear/scene/tsync/terrasync.hxx>
66
67 #include <AIModel/AIAircraft.hxx>
68 #include <AIModel/AIFlightPlan.hxx>
69 #include <AIModel/AIBase.hxx>
70 #include <Airports/airport.hxx>
71 #include <Main/fg_init.hxx>
72
73 #include "TrafficMgr.hxx"
74
75 using std::sort;
76 using std::strcmp;
77 using std::endl;
78 using std::string;
79 using std::vector;
80
81 /**
82  * Thread encapsulating parsing the traffic schedules. 
83  */
84 class ScheduleParseThread : public SGThread, public XMLVisitor
85 {
86 public:
87   ScheduleParseThread(FGTrafficManager* traffic) :
88     _trafficManager(traffic),
89     _isFinished(false),
90     _cancelThread(false),
91     cruiseAlt(0),
92     score(0),
93     acCounter(0),
94     radius(0),
95     offset(0),
96     heavy(false)
97   {
98     
99   }
100   
101   // if we're destroyed while running, ensure the thread exits cleanly
102   ~ScheduleParseThread()
103   {
104     _lock.lock();
105     if (!_isFinished) {
106       _cancelThread = true; // request cancellation so we don't wait ages
107       _lock.unlock();
108       join();
109     } else {
110       _lock.unlock();
111     }
112   }
113   
114   void setTrafficDirs(const PathList& dirs)
115   {
116     _trafficDirPaths = dirs;
117   }
118   
119   bool isFinished() const
120   {
121     SGGuard<SGMutex> g(_lock);
122     return _isFinished;
123   }
124   
125   virtual void run()
126   {
127       BOOST_FOREACH(SGPath p, _trafficDirPaths) {
128           parseTrafficDir(p);
129           if (_cancelThread) {
130               return;
131           }
132       }
133     
134     SGGuard<SGMutex> g(_lock);
135     _isFinished = true;
136   }
137     
138     void startXML()
139     {
140         //cout << "Start XML" << endl;
141         requiredAircraft = "";
142         homePort = "";
143     }
144     
145     void endXML()
146     {
147         //cout << "End XML" << endl;
148     }
149     
150     void startElement(const char *name,
151                                         const XMLAttributes & atts)
152     {
153         const char *attval;
154         //cout << "Start element " << name << endl;
155         //FGTrafficManager temp;
156         //for (int i = 0; i < atts.size(); i++)
157         //  if (string(atts.getName(i)) == string("include"))
158         attval = atts.getValue("include");
159         if (attval != 0) {
160             //cout << "including " << attval << endl;
161             SGPath path = globals->get_fg_root();
162             path.append("/Traffic/");
163             path.append(attval);
164             readXML(path.str(), *this);
165         }
166         elementValueStack.push_back("");
167         //  cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl;
168     }
169     
170     void endElement(const char *name)
171     {
172         //cout << "End element " << name << endl;
173         const string & value = elementValueStack.back();
174         
175         if (!strcmp(name, "model"))
176             mdl = value;
177         else if (!strcmp(name, "livery"))
178             livery = value;
179         else if (!strcmp(name, "home-port"))
180             homePort = value;
181         else if (!strcmp(name, "registration"))
182             registration = value;
183         else if (!strcmp(name, "airline"))
184             airline = value;
185         else if (!strcmp(name, "actype"))
186             acType = value;
187         else if (!strcmp(name, "required-aircraft"))
188             requiredAircraft = value;
189         else if (!strcmp(name, "flighttype"))
190             flighttype = value;
191         else if (!strcmp(name, "radius"))
192             radius = atoi(value.c_str());
193         else if (!strcmp(name, "offset"))
194             offset = atoi(value.c_str());
195         else if (!strcmp(name, "performance-class"))
196             m_class = value;
197         else if (!strcmp(name, "heavy")) {
198             if (value == string("true"))
199                 heavy = true;
200             else
201                 heavy = false;
202         } else if (!strcmp(name, "callsign"))
203             callsign = value;
204         else if (!strcmp(name, "fltrules"))
205             fltrules = value;
206         else if (!strcmp(name, "port"))
207             port = value;
208         else if (!strcmp(name, "time"))
209             timeString = value;
210         else if (!strcmp(name, "departure")) {
211             departurePort = port;
212             departureTime = timeString;
213         } else if (!strcmp(name, "cruise-alt"))
214             cruiseAlt = atoi(value.c_str());
215         else if (!strcmp(name, "arrival")) {
216             arrivalPort = port;
217             arrivalTime = timeString;
218         } else if (!strcmp(name, "repeat"))
219             repeat = value;
220         else if (!strcmp(name, "flight")) {
221             // We have loaded and parsed all the information belonging to this flight
222             // so we temporarily store it.
223             //cerr << "Pusing back flight " << callsign << endl;
224             //cerr << callsign  <<  " " << fltrules     << " "<< departurePort << " " <<  arrivalPort << " "
225             //   << cruiseAlt <<  " " << departureTime<< " "<< arrivalTime   << " " << repeat << endl;
226             
227             //Prioritize aircraft
228             string apt = fgGetString("/sim/presets/airport-id");
229             //cerr << "Airport information: " << apt << " " << departurePort << " " << arrivalPort << endl;
230             //if (departurePort == apt) score++;
231             //flights.push_back(new FGScheduledFlight(callsign,
232             //                                fltrules,
233             //                                departurePort,
234             //                                arrivalPort,
235             //                                cruiseAlt,
236             //                                departureTime,
237             //                                arrivalTime,
238             //                                repeat));
239             if (requiredAircraft == "") {
240                 char buffer[16];
241                 snprintf(buffer, 16, "%d", acCounter);
242                 requiredAircraft = buffer;
243             }
244             SG_LOG(SG_AI, SG_DEBUG, "Adding flight: " << callsign << " "
245                    << fltrules << " "
246                    << departurePort << " "
247                    << arrivalPort << " "
248                    << cruiseAlt << " "
249                    << departureTime << " "
250                    << arrivalTime << " " << repeat << " " << requiredAircraft);
251             // For database maintainance purposes, it may be convenient to
252             //
253             if (fgGetBool("/sim/traffic-manager/dumpdata") == true) {
254                 SG_LOG(SG_AI, SG_ALERT, "Traffic Dump FLIGHT," << callsign << ","
255                        << fltrules << ","
256                        << departurePort << ","
257                        << arrivalPort << ","
258                        << cruiseAlt << ","
259                        << departureTime << ","
260                        << arrivalTime << "," << repeat << "," << requiredAircraft);
261             }
262             
263             _trafficManager->flights[requiredAircraft].push_back(new FGScheduledFlight(callsign,
264                                                                       fltrules,
265                                                                       departurePort,
266                                                                       arrivalPort,
267                                                                       cruiseAlt,
268                                                                       departureTime,
269                                                                       arrivalTime,
270                                                                       repeat,
271                                                                       requiredAircraft));
272             requiredAircraft = "";
273         } else if (!strcmp(name, "aircraft")) {
274             endAircraft();
275         }
276         
277         elementValueStack.pop_back();
278     }
279
280     
281     void data(const char *s, int len)
282     {
283         string token = string(s, len);
284         //cout << "Character data " << string(s,len) << endl;
285         elementValueStack.back() += token;
286     }
287     
288     void pi(const char *target, const char *data)
289     {
290         //cout << "Processing instruction " << target << ' ' << data << endl;
291     }
292     
293     void warning(const char *message, int line, int column)
294     {
295         SG_LOG(SG_IO, SG_WARN,
296                "Warning: " << message << " (" << line << ',' << column << ')');
297     }
298     
299     void error(const char *message, int line, int column)
300     {
301         SG_LOG(SG_IO, SG_ALERT,
302                "Error: " << message << " (" << line << ',' << column << ')');
303     }
304     
305 private:
306     void endAircraft()
307     {
308         string isHeavy = heavy ? "true" : "false";
309         
310         if (missingModels.find(mdl) != missingModels.end()) {
311             // don't stat() or warn again
312             requiredAircraft = homePort = "";
313             return;
314         }
315         
316         if (!FGAISchedule::validModelPath(mdl)) {
317             missingModels.insert(mdl);
318             SG_LOG(SG_AI, SG_WARN, "TrafficMgr: Missing model path:" << mdl);
319             requiredAircraft = homePort = "";
320             return;
321         }
322         
323         int proportion =
324         (int) (fgGetDouble("/sim/traffic-manager/proportion") * 100);
325         int randval = rand() & 100;
326         if (randval > proportion) {
327             requiredAircraft = homePort = "";
328             return;
329         }
330         
331         if (fgGetBool("/sim/traffic-manager/dumpdata") == true) {
332             SG_LOG(SG_AI, SG_ALERT, "Traffic Dump AC," << homePort << "," << registration << "," << requiredAircraft
333                    << "," << acType << "," << livery << ","
334                    << airline << ","  << m_class << "," << offset << "," << radius << "," << flighttype << "," << isHeavy << "," << mdl);
335         }
336         
337         if (requiredAircraft == "") {
338             char buffer[16];
339             snprintf(buffer, 16, "%d", acCounter);
340             requiredAircraft = buffer;
341         }
342         if (homePort == "") {
343             homePort = departurePort;
344         }
345         
346         // caution, modifying the scheduled aircraft strucutre from the
347         // 'wrong' thread. This is safe becuase FGTrafficManager won't touch
348         // the structure while we exist.
349         _trafficManager->scheduledAircraft.push_back(new FGAISchedule(mdl,
350                                                      livery,
351                                                      homePort,
352                                                      registration,
353                                                      requiredAircraft,
354                                                      heavy,
355                                                      acType,
356                                                      airline,
357                                                      m_class,
358                                                      flighttype,
359                                                      radius, offset));
360         
361         acCounter++;
362         requiredAircraft = "";
363         homePort = "";
364         score = 0;
365     }
366     
367     void parseTrafficDir(const SGPath& path)
368     {
369         SGTimeStamp st;
370         st.stamp();
371         
372         simgear::Dir trafficDir(path);
373         simgear::PathList d = trafficDir.children(simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
374         
375         BOOST_FOREACH(SGPath p, d) {
376             simgear::Dir d2(p);
377             SG_LOG(SG_AI, SG_INFO, "parsing traffic in:" << p);
378             simgear::PathList trafficFiles = d2.children(simgear::Dir::TYPE_FILE, ".xml");
379             BOOST_FOREACH(SGPath xml, trafficFiles) {                
380                 readXML(xml.str(), *this);
381                 if (_cancelThread) {
382                     return;
383                 }
384             }
385         } // of sub-directories iteration
386         
387         SG_LOG(SG_AI, SG_INFO, "parsing traffic schedules took:" << st.elapsedMSec() << "msec");
388     }
389     
390   FGTrafficManager* _trafficManager;
391   mutable SGMutex _lock;
392   bool _isFinished;
393   bool _cancelThread;
394   PathList _trafficDirPaths;
395    
396 // parser state
397     
398     string_list elementValueStack;
399     // record model paths which are missing, to avoid duplicate
400     // warnings when parsing traffic schedules.
401     std::set<std::string> missingModels;
402     
403     std::string mdl, livery, registration, callsign, fltrules,
404     port, timeString, departurePort, departureTime, arrivalPort, arrivalTime,
405     repeat, acType, airline, m_class, flighttype, requiredAircraft, homePort;
406     int cruiseAlt;
407     int score, acCounter;
408     double radius, offset;
409     bool heavy;
410
411 };
412
413 /******************************************************************************
414  * TrafficManager
415  *****************************************************************************/
416 FGTrafficManager::FGTrafficManager() :
417   inited(false),
418   doingInit(false),
419   trafficSyncRequested(false),
420   waitingMetarTime(0.0),
421   enabled("/sim/traffic-manager/enabled"),
422   aiEnabled("/sim/ai/enabled"),
423   realWxEnabled("/environment/realwx/enabled"),
424   metarValid("/environment/metar/valid")
425 {
426 }
427
428 FGTrafficManager::~FGTrafficManager()
429 {
430     shutdown();
431 }
432
433 void FGTrafficManager::shutdown()
434 {
435     if (!inited) {
436       if (doingInit) {
437         scheduleParser.reset();
438         doingInit = false;
439       }
440       
441       return;
442     }
443   
444     // Save the heuristics data
445     bool saveData = false;
446     std::ofstream cachefile;
447     if (fgGetBool("/sim/traffic-manager/heuristics")) {
448         SGPath cacheData(globals->get_fg_home());
449         cacheData.append("ai");
450         const string airport = fgGetString("/sim/presets/airport-id");
451
452         if ((airport) != "") {
453             char buffer[128];
454             ::snprintf(buffer, 128, "%c/%c/%c/",
455                        airport[0], airport[1], airport[2]);
456             cacheData.append(buffer);
457             cacheData.append(airport + "-cache.txt");
458             
459             // Note: Intuitively, this doesn't make sense, but I do need to create the full file path first
460             // before creating the directories. The SimGear fgpath code has changed so that it first chops off
461             // the trailing dir separator and then determines the directory part of the file path by searching
462             // for the last dir separator. Effecively, this causes a full element of the directory tree to be
463             // skipped. 
464             SG_LOG(SG_GENERAL, SG_DEBUG, "Trying to create dir for : " << cacheData.c_str());
465             if (!cacheData.exists()) {
466                 cacheData.create_dir(0755);
467             }
468             saveData = true;
469             cachefile.open(cacheData.str().c_str());
470             cachefile << "[TrafficManagerCachedata:ref:2011:09:04]" << endl;
471             
472         }
473     }
474   
475     BOOST_FOREACH(FGAISchedule* acft, scheduledAircraft) {
476         if (saveData) {
477             cachefile << acft->getRegistration() << " "
478                 << acft->getRunCount() << " "
479                 << acft->getHits() << " "
480                 << acft->getLastUsed() << endl;
481         }
482         delete acft;
483     }
484     if (saveData) {
485         cachefile.close();
486     }
487     scheduledAircraft.clear();
488     flights.clear();
489
490     currAircraft = scheduledAircraft.begin();
491     doingInit = false;
492     inited = false;
493     trafficSyncRequested = false;
494 }
495
496 void FGTrafficManager::init()
497 {
498     if (!enabled) {
499       return;
500     }
501
502     // TorstenD: don't start the traffic manager before the FDM is initialized
503     // The FDM needs the scenery loaded and will wait for our spawned AIModels PagedLOD Nodes 
504     // to appear if they are close (less than 1000m) to our position
505     if( !fgGetBool("/sim/signals/fdm-initialized") )
506       return;
507
508     assert(!doingInit);
509     simgear::SGTerraSync* terraSync = static_cast<simgear::SGTerraSync*>(globals->get_subsystem("terrasync"));
510     bool doDataSync = fgGetBool("/sim/terrasync/ai-data-enabled");
511     if (doDataSync && terraSync) {
512         if (!trafficSyncRequested) {
513             SG_LOG(SG_AI, SG_INFO, "Sync of AI traffic via TerraSync enabled");
514             terraSync->scheduleDataDir("AI/Traffic");
515             trafficSyncRequested = true;
516         }
517         
518         if (terraSync->isDataDirPending("AI/Traffic")) {
519             return; // remain in the init state
520         }
521         
522         SG_LOG(SG_AI, SG_INFO, "Traffic files sync complete");
523     }
524     
525     doingInit = true;
526     if (string(fgGetString("/sim/traffic-manager/datafile")).empty()) {
527         PathList dirs = globals->get_data_paths("AI/Traffic");
528         
529         // temporary flag to restrict loading while traffic data is found
530         // through terrasync /and/ fgdata. Ultimatley we *do* want to be able to
531         // overlay sources.
532         
533         if (dirs.size() > 1) {
534             SGPath p = dirs.back();
535             if (simgear::strutils::starts_with(p.str(), globals->get_fg_root())) {
536                 dirs.pop_back();
537             }
538         }
539         
540         if (dirs.empty()) {
541             doingInit = false;
542             return;
543         }
544         
545         scheduleParser.reset(new ScheduleParseThread(this));
546         scheduleParser->setTrafficDirs(dirs);
547         scheduleParser->start();
548     } else {
549         fgSetBool("/sim/traffic-manager/heuristics", false);
550         SGPath path = string(fgGetString("/sim/traffic-manager/datafile"));
551         string ext = path.extension();
552         if (path.extension() == "xml") {
553             if (path.exists()) {
554                 // use a SchedulerParser to parse, but run it in this thread,
555                 // i.e don't start it
556                 ScheduleParseThread parser(this);
557                 readXML(path.str(), parser);
558             }
559         } else if (path.extension() == "conf") {
560             if (path.exists()) {
561                 readTimeTableFromFile(path);
562             }
563         } else {
564              SG_LOG(SG_AI, SG_ALERT,
565                                "Unknown data format " << path.str()
566                                 << " for traffic");
567         }
568         //exit(1);
569     }
570 }
571
572
573
574 void FGTrafficManager::finishInit()
575 {
576     assert(doingInit);
577     SG_LOG(SG_AI, SG_INFO, "finishing AI-Traffic init");
578     loadHeuristics();
579     
580     // Do sorting and scoring separately, to take advantage of the "homeport" variable
581     BOOST_FOREACH(FGAISchedule* schedule, scheduledAircraft) {
582         schedule->setScore();
583     }
584     
585     sort(scheduledAircraft.begin(), scheduledAircraft.end(),
586          compareSchedules);
587     currAircraft = scheduledAircraft.begin();
588     currAircraftClosest = scheduledAircraft.begin();
589     
590     doingInit = false;
591     inited = true;
592 }
593
594 void FGTrafficManager::loadHeuristics()
595 {
596     if (!fgGetBool("/sim/traffic-manager/heuristics")) {
597         return;
598     }
599   
600     HeuristicMap heurMap;
601     //cerr << "Processing Heuristics" << endl;
602     // Load the heuristics data
603     SGPath cacheData(globals->get_fg_home());
604     cacheData.append("ai");
605     string airport = fgGetString("/sim/presets/airport-id");
606     if ((airport) != "") {
607       char buffer[128];
608       ::snprintf(buffer, 128, "%c/%c/%c/",
609                  airport[0], airport[1], airport[2]);
610       cacheData.append(buffer);
611       cacheData.append(airport + "-cache.txt");
612       string revisionStr;
613       if (cacheData.exists()) {
614         std::ifstream data(cacheData.c_str());
615         data >> revisionStr;
616         if (revisionStr != "[TrafficManagerCachedata:ref:2011:09:04]") {
617           SG_LOG(SG_AI, SG_ALERT,"Traffic Manager Warning: discarding outdated cachefile " <<
618                  cacheData.c_str() << " for Airport " << airport);
619         } else {
620           while (1) {
621             Heuristic h; // = new Heuristic;
622             data >> h.registration >> h.runCount >> h.hits >> h.lastRun;
623             if (data.eof())
624               break;
625             HeuristicMapIterator itr = heurMap.find(h.registration);
626             if (itr != heurMap.end()) {
627               SG_LOG(SG_AI, SG_WARN,"Traffic Manager Warning: found duplicate tailnumber " <<
628                      h.registration << " for AI aircraft");
629             } else {
630               heurMap[h.registration] = h;
631             }
632           }
633         }
634       }
635     } 
636     
637   for(currAircraft = scheduledAircraft.begin(); currAircraft != scheduledAircraft.end(); ++currAircraft) {
638         const string& registration = (*currAircraft)->getRegistration();
639         HeuristicMapIterator itr = heurMap.find(registration);
640         if (itr != heurMap.end()) {
641             (*currAircraft)->setrunCount(itr->second.runCount);
642             (*currAircraft)->setHits(itr->second.hits);
643             (*currAircraft)->setLastUsed(itr->second.lastRun);
644         }
645     }
646 }
647
648 bool FGTrafficManager::metarReady(double dt)
649 {
650     // wait for valid METAR (when realWX is enabled only), since we need
651     // to know the active runway
652     if (metarValid || !realWxEnabled)
653     {
654         waitingMetarTime = 0.0;
655         return true;
656     }
657
658     // METAR timeout: when running offline, remote server is down etc
659     if (waitingMetarStation != fgGetString("/environment/metar/station-id"))
660     {
661         // station has changed: wait for reply, restart timeout
662         waitingMetarTime = 0.0;
663         waitingMetarStation = fgGetString("/environment/metar/station-id");
664         return false;
665     }
666
667     // timeout elapsed (10 seconds)?
668     if (waitingMetarTime > 20.0)
669     {
670         return true;
671     }
672
673     waitingMetarTime += dt;
674     return false;
675 }
676
677 void FGTrafficManager::update(double dt)
678 {
679     if (!enabled)
680     {
681         if (inited || doingInit)
682             shutdown();
683         return;
684     }
685
686     if (!metarReady(dt))
687         return;
688
689     if (!aiEnabled)
690     {
691         // traffic depends on AI module
692         aiEnabled = true;
693     }
694
695     if (!inited) {
696         if (!doingInit) {
697             init();
698         }
699                 
700         if (!doingInit || !scheduleParser->isFinished()) {
701           return;
702         }
703       
704         finishInit();
705     }
706         
707     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
708     if (scheduledAircraft.empty()) {
709         return;
710     }
711
712     SGVec3d userCart = globals->get_aircraft_position_cart();
713
714     if (currAircraft == scheduledAircraft.end()) {
715         currAircraft = scheduledAircraft.begin();
716     }
717
718     //cerr << "Processing << " << (*currAircraft)->getRegistration() << " with score " << (*currAircraft)->getScore() << endl;
719     if ((*currAircraft)->update(now, userCart)) {
720         // schedule is done - process another aircraft in next iteration
721         currAircraft++;
722     }
723 }
724
725 void FGTrafficManager::readTimeTableFromFile(SGPath infileName)
726 {
727     string model;
728     string livery;
729     string homePort;
730     string registration;
731     string flightReq;
732     bool   isHeavy;
733     string acType;
734     string airline;
735     string m_class;
736     string FlightType;
737     double radius;
738     double offset;
739
740     char buffer[256];
741     string buffString;
742     vector <string> tokens, depTime,arrTime;
743     vector <string>::iterator it;
744     std::ifstream infile(infileName.str().c_str());
745     while (1) {
746          infile.getline(buffer, 256);
747          if (infile.eof()) {
748              break;
749          }
750          //cerr << "Read line : " << buffer << endl;
751          buffString = string(buffer);
752          tokens.clear();
753          Tokenize(buffString, tokens, " \t");
754          //for (it = tokens.begin(); it != tokens.end(); it++) {
755          //    cerr << "Tokens: " << *(it) << endl;
756          //}
757          //cerr << endl;
758          if (!tokens.empty()) {
759              if (tokens[0] == string("AC")) {
760                  if (tokens.size() != 13) {
761                      throw sg_io_exception("Error parsing traffic file @ " + buffString, sg_location(infileName.str()));
762                  }
763                  
764                  
765                  model          = tokens[12];
766                  livery         = tokens[6];
767                  homePort       = tokens[1];
768                  registration   = tokens[2];
769                  if (tokens[11] == string("false")) {
770                      isHeavy = false;
771                  } else {
772                      isHeavy = true;
773                  }
774                  acType         = tokens[4];
775                  airline        = tokens[5];
776                  flightReq      = tokens[3] + tokens[5];
777                  m_class        = tokens[10];
778                  FlightType     = tokens[9];
779                  radius         = atof(tokens[8].c_str());
780                  offset         = atof(tokens[7].c_str());;
781                  
782                  if (!FGAISchedule::validModelPath(model)) {
783                      SG_LOG(SG_AI, SG_WARN, "TrafficMgr: Missing model path:" <<
784                             model << " from " << infileName.str());
785                  } else {
786                  
787                  SG_LOG(SG_AI, SG_INFO, "Adding Aircraft" << model << " " << livery << " " << homePort << " "
788                                                                 << registration << " " << flightReq << " " << isHeavy 
789                                                                 << " " << acType << " " << airline << " " << m_class 
790                                                                 << " " << FlightType << " " << radius << " " << offset);
791                  scheduledAircraft.push_back(new FGAISchedule(model, 
792                                                               livery, 
793                                                               homePort,
794                                                               registration, 
795                                                               flightReq,
796                                                               isHeavy,
797                                                               acType, 
798                                                               airline, 
799                                                               m_class, 
800                                                               FlightType,
801                                                               radius,
802                                                               offset));
803                  } // of valid model path
804              }
805              if (tokens[0] == string("FLIGHT")) {
806                  //cerr << "Found flight " << buffString << " size is : " << tokens.size() << endl;
807                  if (tokens.size() != 10) {
808                      SG_LOG(SG_AI, SG_ALERT, "Error parsing traffic file " << infileName.str() << " at " << buffString);
809                      exit(1);
810                  }
811                  string callsign = tokens[1];
812                  string fltrules = tokens[2];
813                  string weekdays = tokens[3];
814                  string departurePort = tokens[5];
815                  string arrivalPort   = tokens[7];
816                  int    cruiseAlt     = atoi(tokens[8].c_str());
817                  string depTimeGen    = tokens[4];
818                  string arrTimeGen    = tokens[6];
819                  string repeat        = "WEEK";
820                  string requiredAircraft = tokens[9];
821
822                  if (weekdays.size() != 7) {
823                      SG_LOG(SG_AI, SG_ALERT, "Found misconfigured weekdays string" << weekdays);
824                      exit(1);
825                  }
826                  depTime.clear();
827                  arrTime.clear();
828                  Tokenize(depTimeGen, depTime, ":");
829                  Tokenize(arrTimeGen, arrTime, ":");
830                  double dep = atof(depTime[0].c_str()) + (atof(depTime[1].c_str()) / 60.0);
831                  double arr = atof(arrTime[0].c_str()) + (atof(arrTime[1].c_str()) / 60.0);
832                  //cerr << "Using " << dep << " " << arr << endl;
833                  bool arrivalWeekdayNeedsIncrement = false;
834                  if (arr < dep) {
835                        arrivalWeekdayNeedsIncrement = true;
836                  }
837                  for (int i = 0; i < 7; i++) {
838                      int j = i+1;
839                      if (weekdays[i] != '.') {
840                          char buffer[4];
841                          snprintf(buffer, 4, "%d/", j);
842                          string departureTime = string(buffer) + depTimeGen + string(":00");
843                          string arrivalTime;
844                          if (!arrivalWeekdayNeedsIncrement) {
845                              arrivalTime   = string(buffer) + arrTimeGen + string(":00");
846                          }
847                          if (arrivalWeekdayNeedsIncrement && i != 6 ) {
848                              snprintf(buffer, 4, "%d/", j+1);
849                              arrivalTime   = string(buffer) + arrTimeGen + string(":00");
850                          }
851                          if (arrivalWeekdayNeedsIncrement && i == 6 ) {
852                              snprintf(buffer, 4, "%d/", 0);
853                              arrivalTime   = string(buffer) + arrTimeGen  + string(":00");
854                          }
855                          SG_LOG(SG_AI, SG_ALERT, "Adding flight " << callsign       << " "
856                                                       << fltrules       << " "
857                                                       <<  departurePort << " "
858                                                       <<  arrivalPort   << " "
859                                                       <<  cruiseAlt     << " "
860                                                       <<  departureTime << " "
861                                                       <<  arrivalTime   << " "
862                                                       << repeat        << " " 
863                                                       <<  requiredAircraft);
864
865                          flights[requiredAircraft].push_back(new FGScheduledFlight(callsign,
866                                                                  fltrules,
867                                                                  departurePort,
868                                                                  arrivalPort,
869                                                                  cruiseAlt,
870                                                                  departureTime,
871                                                                  arrivalTime,
872                                                                  repeat,
873                                                                  requiredAircraft));
874                     }
875                 }
876              }
877          }
878
879     }
880     //exit(1);
881 }
882
883
884 void FGTrafficManager::Tokenize(const string& str,
885                       vector<string>& tokens,
886                       const string& delimiters)
887 {
888     // Skip delimiters at beginning.
889     string::size_type lastPos = str.find_first_not_of(delimiters, 0);
890     // Find first "non-delimiter".
891     string::size_type pos     = str.find_first_of(delimiters, lastPos);
892
893     while (string::npos != pos || string::npos != lastPos)
894     {
895         // Found a token, add it to the vector.
896         tokens.push_back(str.substr(lastPos, pos - lastPos));
897         // Skip delimiters.  Note the "not_of"
898         lastPos = str.find_first_not_of(delimiters, pos);
899         // Find next "non-delimiter"
900         pos = str.find_first_of(delimiters, lastPos);
901     }
902 }
903
904