]> git.mxchange.org Git - flightgear.git/blob - src/Traffic/TrafficMgr.cxx
Fix bug 191, uninitialised HUD color.
[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
54 #include <simgear/compiler.h>
55 #include <simgear/misc/sg_path.hxx>
56 #include <simgear/misc/sg_dir.hxx>
57 #include <simgear/props/props.hxx>
58 #include <simgear/route/waypoint.hxx>
59 #include <simgear/structure/subsystem_mgr.hxx>
60 #include <simgear/xml/easyxml.hxx>
61
62 #include <AIModel/AIAircraft.hxx>
63 #include <AIModel/AIFlightPlan.hxx>
64 #include <AIModel/AIBase.hxx>
65 #include <Airports/simple.hxx>
66 #include <Main/fg_init.hxx>
67
68
69
70 #include "TrafficMgr.hxx"
71
72 using std::sort;
73 using std::strcmp;
74
75 /******************************************************************************
76  * TrafficManager
77  *****************************************************************************/
78 FGTrafficManager::FGTrafficManager()
79 {
80     //score = 0;
81     //runCount = 0;
82     acCounter = 0;
83 }
84
85 FGTrafficManager::~FGTrafficManager()
86 {
87     // Save the heuristics data
88     bool saveData = false;
89     ofstream cachefile;
90     if (fgGetBool("/sim/traffic-manager/heuristics")) {
91         SGPath cacheData(fgGetString("/sim/fg-home"));
92         cacheData.append("ai");
93         string airport = fgGetString("/sim/presets/airport-id");
94
95         if ((airport) != "") {
96             char buffer[128];
97             ::snprintf(buffer, 128, "%c/%c/%c/",
98                        airport[0], airport[1], airport[2]);
99             cacheData.append(buffer);
100             if (!cacheData.exists()) {
101                 cacheData.create_dir(0777);
102             }
103             cacheData.append(airport + "-cache.txt");
104             //cerr << "Saving AI traffic heuristics" << endl;
105             saveData = true;
106             cachefile.open(cacheData.str().c_str());
107         }
108     }
109     for (ScheduleVectorIterator sched = scheduledAircraft.begin();
110          sched != scheduledAircraft.end(); sched++) {
111         if (saveData) {
112             cachefile << (*sched)->getRegistration() << " "
113                 << (*sched)->getRunCount() << " "
114                 << (*sched)->getHits() << endl;
115         }
116         delete(*sched);
117     }
118     if (saveData) {
119         cachefile.close();
120     }
121     scheduledAircraft.clear();
122     flights.clear();
123 }
124
125
126 void FGTrafficManager::init()
127 {
128     heuristicsVector heuristics;
129     HeuristicMap heurMap;
130
131     if (string(fgGetString("/sim/traffic-manager/datafile")) == string("")) {
132         simgear::Dir trafficDir(SGPath(globals->get_fg_root(), "AI/Traffic"));
133         simgear::PathList d = trafficDir.children(simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
134         
135         for (unsigned int i=0; i<d.size(); ++i) {
136           simgear::Dir d2(d[i]);
137           simgear::PathList trafficFiles = d2.children(simgear::Dir::TYPE_FILE, ".xml");
138           for (unsigned int j=0; j<trafficFiles.size(); ++j) {
139             SGPath curFile = trafficFiles[j];
140             SG_LOG(SG_GENERAL, SG_DEBUG,
141                   "Scanning " << curFile.str() << " for traffic");
142             readXML(curFile.str(), *this);
143           }
144         }
145     } else {
146         fgSetBool("/sim/traffic-manager/heuristics", false);
147         SGPath path = string(fgGetString("/sim/traffic-manager/datafile"));
148         string ext = path.extension();
149         if (path.extension() == "xml") {
150             if (path.exists()) {
151                 readXML(path.str(), *this);
152             }
153         } else if (path.extension() == "conf") {
154             if (path.exists()) {
155                 readTimeTableFromFile(path);
156             }
157         } else {
158              SG_LOG(SG_GENERAL, SG_ALERT,
159                                "Unknown data format " << path.str()
160                                 << " for traffic");
161         }
162         //exit(1);
163     }
164     if (fgGetBool("/sim/traffic-manager/heuristics")) {
165         //cerr << "Processing Heuristics" << endl;
166         // Load the heuristics data
167         SGPath cacheData(fgGetString("/sim/fg-home"));
168         cacheData.append("ai");
169         string airport = fgGetString("/sim/presets/airport-id");
170         if ((airport) != "") {
171             char buffer[128];
172             ::snprintf(buffer, 128, "%c/%c/%c/",
173                        airport[0], airport[1], airport[2]);
174             cacheData.append(buffer);
175             cacheData.append(airport + "-cache.txt");
176             if (cacheData.exists()) {
177                 ifstream data(cacheData.c_str());
178                 while (1) {
179                     Heuristic h; // = new Heuristic;
180                     data >> h.registration >> h.runCount >> h.hits;
181                     if (data.eof())
182                         break;
183                     HeuristicMapIterator itr = heurMap.find(h.registration);
184                     if (itr != heurMap.end()) {
185                          SG_LOG(SG_GENERAL, SG_WARN,"Traffic Manager Warning: found duplicate tailnumber " << 
186                          h.registration << " for AI aircraft");
187                     }
188                     heurMap[h.registration] = h;
189                     heuristics.push_back(h);
190                 }
191             }
192         }
193         for (currAircraft = scheduledAircraft.begin();
194              currAircraft != scheduledAircraft.end(); currAircraft++) {
195             string registration = (*currAircraft)->getRegistration();
196             HeuristicMapIterator itr = heurMap.find(registration);
197             //cerr << "Processing heuristics for" << (*currAircraft)->getRegistration() << endl;
198             if (itr == heurMap.end()) {
199                 //cerr << "No heuristics found for " << registration << endl;
200             } else {
201                 (*currAircraft)->setrunCount(itr->second.runCount);
202                 (*currAircraft)->setHits(itr->second.hits);
203                 //cerr <<"Runcount " << itr->second->runCount << ".Hits " << itr->second->hits << endl;
204             }
205         }
206         //cerr << "Done" << endl;
207         //for (heuristicsVectorIterator hvi = heuristics.begin();
208         //     hvi != heuristics.end(); hvi++) {
209         //    delete(*hvi);
210         //}
211     }
212     // Do sorting and scoring separately, to take advantage of the "homeport| variable
213     for (currAircraft = scheduledAircraft.begin();
214          currAircraft != scheduledAircraft.end(); currAircraft++) {
215         (*currAircraft)->setScore();
216     }
217     sort(scheduledAircraft.begin(), scheduledAircraft.end(),
218          compareSchedules);
219     currAircraft = scheduledAircraft.begin();
220     currAircraftClosest = scheduledAircraft.begin();
221 }
222
223 void FGTrafficManager::update(double /*dt */ )
224 {
225     if (fgGetBool("/environment/metar/valid") == false) {
226         return;
227     }
228     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
229     if (scheduledAircraft.size() == 0) {
230         return;
231     }
232
233     SGVec3d userCart =
234         SGVec3d::fromGeod(SGGeod::
235                           fromDeg(fgGetDouble("/position/longitude-deg"),
236                                   fgGetDouble("/position/latitude-deg")));
237
238     if (currAircraft == scheduledAircraft.end()) {
239         currAircraft = scheduledAircraft.begin();
240     }
241     //cerr << "Processing << " << (*currAircraft)->getRegistration() << " with score " << (*currAircraft)->getScore() << endl;
242     if (!((*currAircraft)->update(now, userCart))) {
243         // NOTE: With traffic manager II, this statement below is no longer true
244         // after proper initialization, we shouldnt get here.
245         // But let's make sure
246         //SG_LOG( SG_GENERAL, SG_ALERT, "Failed to update aircraft schedule in traffic manager");
247     }
248     currAircraft++;
249 }
250
251 void FGTrafficManager::release(int id)
252 {
253     releaseList.push_back(id);
254 }
255
256 bool FGTrafficManager::isReleased(int id)
257 {
258     IdListIterator i = releaseList.begin();
259     while (i != releaseList.end()) {
260         if ((*i) == id) {
261             releaseList.erase(i);
262             return true;
263         }
264         i++;
265     }
266     return false;
267 }
268
269
270 void FGTrafficManager::readTimeTableFromFile(SGPath infileName)
271 {
272     string model;
273     string livery;
274     string homePort;
275     string registration;
276     string flightReq;
277     bool   isHeavy;
278     string acType;
279     string airline;
280     string m_class;
281     string FlightType;
282     double radius;
283     double offset;
284
285     char buffer[256];
286     string buffString;
287     vector <string> tokens, depTime,arrTime;
288     vector <string>::iterator it;
289     ifstream infile(infileName.str().c_str());
290     while (1) {
291          infile.getline(buffer, 256);
292          if (infile.eof()) {
293              break;
294          }
295          //cerr << "Read line : " << buffer << endl;
296          buffString = string(buffer);
297          tokens.clear();
298          Tokenize(buffString, tokens, " \t");
299          //for (it = tokens.begin(); it != tokens.end(); it++) {
300          //    cerr << "Tokens: " << *(it) << endl;
301          //}
302          //cerr << endl;
303          if (!tokens.empty()) {
304              if (tokens[0] == string("AC")) {
305                  if (tokens.size() != 13) {
306                      SG_LOG(SG_GENERAL, SG_ALERT, "Error parsing traffic file " << infileName.str() << " at " << buffString);
307                      exit(1);
308                  }
309                  model          = tokens[12];
310                  livery         = tokens[6];
311                  homePort       = tokens[1];
312                  registration   = tokens[2];
313                  if (tokens[11] == string("false")) {
314                      isHeavy = false;
315                  } else {
316                      isHeavy = true;
317                  }
318                  acType         = tokens[4];
319                  airline        = tokens[5];
320                  flightReq      = tokens[3] + tokens[5];
321                  m_class        = tokens[10];
322                  FlightType     = tokens[9];
323                  radius         = atof(tokens[8].c_str());
324                  offset         = atof(tokens[7].c_str());;
325                  SG_LOG(SG_GENERAL, SG_ALERT, "Adding Aircraft" << model << " " << livery << " " << homePort << " " 
326                                                                 << registration << " " << flightReq << " " << isHeavy 
327                                                                 << " " << acType << " " << airline << " " << m_class 
328                                                                 << " " << FlightType << " " << radius << " " << offset);
329                  scheduledAircraft.push_back(new FGAISchedule(model, 
330                                                               livery, 
331                                                               homePort,
332                                                               registration, 
333                                                               flightReq,
334                                                               isHeavy,
335                                                               acType, 
336                                                               airline, 
337                                                               m_class, 
338                                                               FlightType,
339                                                               radius,
340                                                               offset));
341              }
342              if (tokens[0] == string("FLIGHT")) {
343                  //cerr << "Found flight " << buffString << " size is : " << tokens.size() << endl;
344                  if (tokens.size() != 10) {
345                      SG_LOG(SG_GENERAL, SG_ALERT, "Error parsing traffic file " << infileName.str() << " at " << buffString);
346                      exit(1);
347                  }
348                  string callsign = tokens[1];
349                  string fltrules = tokens[2];
350                  string weekdays = tokens[3];
351                  string departurePort = tokens[5];
352                  string arrivalPort   = tokens[7];
353                  int    cruiseAlt     = atoi(tokens[8].c_str());
354                  string depTimeGen    = tokens[4];
355                  string arrTimeGen    = tokens[6];
356                  string repeat        = "WEEK";
357                  string requiredAircraft = tokens[9];
358
359                  if (weekdays.size() != 7) {
360                      SG_LOG(SG_GENERAL, SG_ALERT, "Found misconfigured weekdays string" << weekdays);
361                      exit(1);
362                  }
363                  depTime.clear();
364                  arrTime.clear();
365                  Tokenize(depTimeGen, depTime, ":");
366                  Tokenize(arrTimeGen, arrTime, ":");
367                  double dep = atof(depTime[0].c_str()) + (atof(depTime[1].c_str()) / 60.0);
368                  double arr = atof(arrTime[0].c_str()) + (atof(arrTime[1].c_str()) / 60.0);
369                  //cerr << "Using " << dep << " " << arr << endl;
370                  bool arrivalWeekdayNeedsIncrement = false;
371                  if (arr < dep) {
372                        arrivalWeekdayNeedsIncrement = true;
373                  }
374                  for (int i = 0; i < 7; i++) {
375                      int j = i+1;
376                      if (weekdays[i] != '.') {
377                          char buffer[4];
378                          snprintf(buffer, 4, "%d/", j);
379                          string departureTime = string(buffer) + depTimeGen + string(":00");
380                          string arrivalTime;
381                          if (!arrivalWeekdayNeedsIncrement) {
382                              arrivalTime   = string(buffer) + arrTimeGen + string(":00");
383                          }
384                          if (arrivalWeekdayNeedsIncrement && i != 6 ) {
385                              snprintf(buffer, 4, "%d/", j+1);
386                              arrivalTime   = string(buffer) + arrTimeGen + string(":00");
387                          }
388                          if (arrivalWeekdayNeedsIncrement && i == 6 ) {
389                              snprintf(buffer, 4, "%d/", 0);
390                              arrivalTime   = string(buffer) + arrTimeGen  + string(":00");
391                          }
392                          SG_LOG(SG_GENERAL, SG_ALERT, "Adding flight " << callsign       << " "
393                                                       << fltrules       << " "
394                                                       <<  departurePort << " "
395                                                       <<  arrivalPort   << " "
396                                                       <<  cruiseAlt     << " "
397                                                       <<  departureTime << " "
398                                                       <<  arrivalTime   << " "
399                                                       << repeat        << " " 
400                                                       <<  requiredAircraft);
401
402                          flights[requiredAircraft].push_back(new FGScheduledFlight(callsign,
403                                                                  fltrules,
404                                                                  departurePort,
405                                                                  arrivalPort,
406                                                                  cruiseAlt,
407                                                                  departureTime,
408                                                                  arrivalTime,
409                                                                  repeat,
410                                                                  requiredAircraft));
411                     }
412                 }
413              }
414          }
415
416     }
417     //exit(1);
418 }
419
420
421 void FGTrafficManager::Tokenize(const string& str,
422                       vector<string>& tokens,
423                       const string& delimiters)
424 {
425     // Skip delimiters at beginning.
426     string::size_type lastPos = str.find_first_not_of(delimiters, 0);
427     // Find first "non-delimiter".
428     string::size_type pos     = str.find_first_of(delimiters, lastPos);
429
430     while (string::npos != pos || string::npos != lastPos)
431     {
432         // Found a token, add it to the vector.
433         tokens.push_back(str.substr(lastPos, pos - lastPos));
434         // Skip delimiters.  Note the "not_of"
435         lastPos = str.find_first_not_of(delimiters, pos);
436         // Find next "non-delimiter"
437         pos = str.find_first_of(delimiters, lastPos);
438     }
439 }
440
441
442 void FGTrafficManager::startXML()
443 {
444     //cout << "Start XML" << endl;
445     requiredAircraft = "";
446     homePort = "";
447 }
448
449 void FGTrafficManager::endXML()
450 {
451     //cout << "End XML" << endl;
452 }
453
454 void FGTrafficManager::startElement(const char *name,
455                                     const XMLAttributes & atts)
456 {
457     const char *attval;
458     //cout << "Start element " << name << endl;
459     //FGTrafficManager temp;
460     //for (int i = 0; i < atts.size(); i++)
461     //  if (string(atts.getName(i)) == string("include"))
462     attval = atts.getValue("include");
463     if (attval != 0) {
464         //cout << "including " << attval << endl;
465         SGPath path = globals->get_fg_root();
466         path.append("/Traffic/");
467         path.append(attval);
468         readXML(path.str(), *this);
469     }
470     elementValueStack.push_back("");
471     //  cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
472 }
473
474 void FGTrafficManager::endElement(const char *name)
475 {
476     //cout << "End element " << name << endl;
477     const string & value = elementValueStack.back();
478
479     if (!strcmp(name, "model"))
480         mdl = value;
481     else if (!strcmp(name, "livery"))
482         livery = value;
483     else if (!strcmp(name, "home-port"))
484         homePort = value;
485     else if (!strcmp(name, "registration"))
486         registration = value;
487     else if (!strcmp(name, "airline"))
488         airline = value;
489     else if (!strcmp(name, "actype"))
490         acType = value;
491     else if (!strcmp(name, "required-aircraft"))
492         requiredAircraft = value;
493     else if (!strcmp(name, "flighttype"))
494         flighttype = value;
495     else if (!strcmp(name, "radius"))
496         radius = atoi(value.c_str());
497     else if (!strcmp(name, "offset"))
498         offset = atoi(value.c_str());
499     else if (!strcmp(name, "performance-class"))
500         m_class = value;
501     else if (!strcmp(name, "heavy")) {
502         if (value == string("true"))
503             heavy = true;
504         else
505             heavy = false;
506     } else if (!strcmp(name, "callsign"))
507         callsign = value;
508     else if (!strcmp(name, "fltrules"))
509         fltrules = value;
510     else if (!strcmp(name, "port"))
511         port = value;
512     else if (!strcmp(name, "time"))
513         timeString = value;
514     else if (!strcmp(name, "departure")) {
515         departurePort = port;
516         departureTime = timeString;
517     } else if (!strcmp(name, "cruise-alt"))
518         cruiseAlt = atoi(value.c_str());
519     else if (!strcmp(name, "arrival")) {
520         arrivalPort = port;
521         arrivalTime = timeString;
522     } else if (!strcmp(name, "repeat"))
523         repeat = value;
524     else if (!strcmp(name, "flight")) {
525         // We have loaded and parsed all the information belonging to this flight
526         // so we temporarily store it. 
527         //cerr << "Pusing back flight " << callsign << endl;
528         //cerr << callsign  <<  " " << fltrules     << " "<< departurePort << " " <<  arrivalPort << " "
529         //   << cruiseAlt <<  " " << departureTime<< " "<< arrivalTime   << " " << repeat << endl;
530
531         //Prioritize aircraft 
532         string apt = fgGetString("/sim/presets/airport-id");
533         //cerr << "Airport information: " << apt << " " << departurePort << " " << arrivalPort << endl;
534         //if (departurePort == apt) score++;
535         //flights.push_back(new FGScheduledFlight(callsign,
536         //                                fltrules,
537         //                                departurePort,
538         //                                arrivalPort,
539         //                                cruiseAlt,
540         //                                departureTime,
541         //                                arrivalTime,
542         //                                repeat));
543         if (requiredAircraft == "") {
544             char buffer[16];
545             snprintf(buffer, 16, "%d", acCounter);
546             requiredAircraft = buffer;
547         }
548         SG_LOG(SG_GENERAL, SG_DEBUG, "Adding flight: " << callsign << " "
549                << fltrules << " "
550                << departurePort << " "
551                << arrivalPort << " "
552                << cruiseAlt << " "
553                << departureTime << " "
554                << arrivalTime << " " << repeat << " " << requiredAircraft);
555         // For database maintainance purposes, it may be convenient to
556         // 
557         if (fgGetBool("/sim/traffic-manager/dumpdata") == true) {
558              SG_LOG(SG_GENERAL, SG_ALERT, "Traffic Dump FLIGHT," << callsign << ","
559                           << fltrules << ","
560                           << departurePort << ","
561                           << arrivalPort << ","
562                           << cruiseAlt << ","
563                           << departureTime << ","
564                           << arrivalTime << "," << repeat << "," << requiredAircraft);
565         }
566         flights[requiredAircraft].push_back(new FGScheduledFlight(callsign,
567                                                                   fltrules,
568                                                                   departurePort,
569                                                                   arrivalPort,
570                                                                   cruiseAlt,
571                                                                   departureTime,
572                                                                   arrivalTime,
573                                                                   repeat,
574                                                                   requiredAircraft));
575         requiredAircraft = "";
576     } else if (!strcmp(name, "aircraft")) {
577         string isHeavy;
578         if (heavy) {
579             isHeavy = "true";
580         } else {
581             isHeavy = "false"; 
582         }
583         /*
584         cerr << "Traffic Dump AC," << homePort << "," << registration << "," << requiredAircraft 
585              << "," << acType << "," << livery << "," 
586              << airline << "," << offset << "," << radius << "," << flighttype << "," << isHeavy << "," << mdl << endl;*/
587         int proportion =
588             (int) (fgGetDouble("/sim/traffic-manager/proportion") * 100);
589         int randval = rand() & 100;
590         if (randval <= proportion) {
591             if (fgGetBool("/sim/traffic-manager/dumpdata") == true) {
592                 SG_LOG(SG_GENERAL, SG_ALERT, "Traffic Dump AC," << homePort << "," << registration << "," << requiredAircraft 
593                  << "," << acType << "," << livery << "," 
594                  << airline << ","  << m_class << "," << offset << "," << radius << "," << flighttype << "," << isHeavy << "," << mdl);
595             }
596             //scheduledAircraft.push_back(new FGAISchedule(mdl, 
597             //                                     livery, 
598             //                                     registration, 
599             //                                     heavy,
600             //                                     acType, 
601             //                                     airline, 
602             //                                     m_class, 
603             //                                     flighttype,
604             //                                     radius,
605             //                                     offset,
606             //                                     score,
607             //                                     flights));
608             if (requiredAircraft == "") {
609                 char buffer[16];
610                 snprintf(buffer, 16, "%d", acCounter);
611                 requiredAircraft = buffer;
612             }
613             if (homePort == "") {
614                 homePort = departurePort;
615             }
616             scheduledAircraft.push_back(new FGAISchedule(mdl,
617                                                          livery,
618                                                          homePort,
619                                                          registration,
620                                                          requiredAircraft,
621                                                          heavy,
622                                                          acType,
623                                                          airline,
624                                                          m_class,
625                                                          flighttype,
626                                                          radius, offset));
627
628             //  while(flights.begin() != flights.end()) {
629 //      flights.pop_back();
630 //       }
631         } else {
632             cerr << "Skipping : " << randval;
633         }
634         acCounter++;
635         requiredAircraft = "";
636         homePort = "";
637         //for (FGScheduledFlightVecIterator flt = flights.begin(); flt != flights.end(); flt++)
638         //  {
639         //    delete (*flt);
640         //  }
641         //flights.clear();
642         SG_LOG(SG_GENERAL, SG_BULK, "Reading aircraft : "
643                << registration << " with prioritization score " << score);
644         score = 0;
645     }
646     elementValueStack.pop_back();
647 }
648
649 void FGTrafficManager::data(const char *s, int len)
650 {
651     string token = string(s, len);
652     //cout << "Character data " << string(s,len) << endl;
653     elementValueStack.back() += token;
654 }
655
656 void FGTrafficManager::pi(const char *target, const char *data)
657 {
658     //cout << "Processing instruction " << target << ' ' << data << endl;
659 }
660
661 void FGTrafficManager::warning(const char *message, int line, int column)
662 {
663     SG_LOG(SG_IO, SG_WARN,
664            "Warning: " << message << " (" << line << ',' << column << ')');
665 }
666
667 void FGTrafficManager::error(const char *message, int line, int column)
668 {
669     SG_LOG(SG_IO, SG_ALERT,
670            "Error: " << message << " (" << line << ',' << column << ')');
671 }