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