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