]> git.mxchange.org Git - flightgear.git/blob - src/Airports/dynamics.cxx
Code cleanups, code updates and fix at least on (possible) devide-by-zero
[flightgear.git] / src / Airports / dynamics.cxx
1 // dynamics.cxx - Code to manage the higher order airport ground activities
2 // Written by Durk Talsma, started December 2004.
3 //
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 // $Id$
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include <algorithm>
26 #include <string>
27 #include <vector>
28
29 #include <boost/foreach.hpp>
30
31 #include <simgear/compiler.h>
32
33 #include <Environment/environment_mgr.hxx>
34 #include <Environment/environment.hxx>
35 #include <simgear/misc/sg_path.hxx>
36 #include <simgear/props/props.hxx>
37 #include <simgear/structure/subsystem_mgr.hxx>
38 #include <simgear/debug/logstream.hxx>
39 #include <Main/globals.hxx>
40 #include <Main/fg_props.hxx>
41 #include <Main/locale.hxx>
42 #include <Airports/runways.hxx>
43 #include <Airports/groundnetwork.hxx>
44 #include <Navaids/NavDataCache.hxx>
45
46 #include "airport.hxx"
47 #include "dynamics.hxx"
48
49 using std::string;
50 using std::vector;
51 using std::sort;
52 using std::random_shuffle;
53
54 class ParkingAssignment::ParkingAssignmentPrivate
55 {
56 public:
57   ParkingAssignmentPrivate(FGParking* pk, FGAirportDynamics* dyn) :
58     refCount(0),
59     parking(pk),
60     dynamics(dyn)
61   {
62     assert(pk);
63     assert(dyn);
64     retain(); // initial count of 1
65   }
66   
67   ~ParkingAssignmentPrivate()
68   {
69     dynamics->releaseParking(parking);
70   }
71   
72   void release()
73   {
74     if ((--refCount) == 0) {
75       delete this;
76     }
77   }
78   
79   void retain()
80   {
81     ++refCount;
82   }
83   
84   unsigned int refCount;
85   FGParkingRef parking;
86   FGAirportDynamicsRef dynamics;
87 };
88
89 ParkingAssignment::ParkingAssignment() :
90   _sharedData(NULL)
91 {
92 }
93
94 ParkingAssignment::~ParkingAssignment()
95 {
96   if (_sharedData) {
97     _sharedData->release();
98   }
99 }
100   
101 ParkingAssignment::ParkingAssignment(FGParking* pk, FGAirportDynamics* dyn) :
102   _sharedData(NULL)
103 {
104   if (pk) {
105     _sharedData = new ParkingAssignmentPrivate(pk, dyn);
106   }
107 }
108
109 ParkingAssignment::ParkingAssignment(const ParkingAssignment& aOther) :
110   _sharedData(aOther._sharedData)
111 {
112   if (_sharedData) {
113     _sharedData->retain();
114   }
115 }
116
117 void ParkingAssignment::operator=(const ParkingAssignment& aOther)
118 {
119   if (_sharedData == aOther._sharedData) {
120     return; // self-assignment, special case
121   }
122   
123   if (_sharedData) {
124     _sharedData->release();
125   }
126   
127   _sharedData = aOther._sharedData;
128   if (_sharedData) {
129     _sharedData->retain();
130   }
131 }
132   
133 void ParkingAssignment::release()
134 {
135   if (_sharedData) {
136     _sharedData->release();
137     _sharedData = NULL;
138   }
139 }
140
141 bool ParkingAssignment::isValid() const
142 {
143   return (_sharedData != NULL);
144 }
145
146 FGParking* ParkingAssignment::parking() const
147 {
148   return _sharedData ? _sharedData->parking.ptr() : NULL;
149 }
150
151 ////////////////////////////////////////////////////////////////////////////////
152
153 FGAirportDynamics::FGAirportDynamics(FGAirport * ap):
154     _ap(ap), rwyPrefs(ap),
155     startupController    (this),
156     towerController      (this),
157     approachController   (this),
158     atisSequenceIndex(-1),
159     atisSequenceTimeStamp(0.0)
160
161 {
162     lastUpdate = 0;
163 }
164
165 // Destructor
166 FGAirportDynamics::~FGAirportDynamics()
167 {
168     SG_LOG(SG_AI, SG_INFO, "destroyed dynamics for:" << _ap->ident());
169 }
170
171
172 // Initialization required after XMLRead
173 void FGAirportDynamics::init()
174 {
175     groundController.setTowerController(&towerController);
176     groundController.init(this);
177 }
178
179 FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
180                                            const string & airline,
181                                            bool skipEmptyAirlineCode)
182 {
183     const FGParkingList& parkings(getGroundNetwork()->allParkings());
184     FGParkingList::const_iterator it;
185     for (it = parkings.begin(); it != parkings.end(); ++it) {
186         FGParkingRef parking = *it;
187         if (!isParkingAvailable(parking)) {
188           continue;
189         }
190
191         if (skipEmptyAirlineCode && parking->getCodes().empty()) {
192           continue;
193         }
194
195         if (!airline.empty() && !parking->getCodes().empty()) {
196           if (parking->getCodes().find(airline, 0) == string::npos) {
197             continue;
198           }
199         }
200
201         setParkingAvailable(parking, false);
202         return parking;
203     }
204
205     return NULL;
206 }
207
208 bool FGAirportDynamics::hasParkings() const
209 {
210     return !getGroundNetwork()->allParkings().empty();
211 }
212
213 ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
214                                             const string & acType,
215                                             const string & airline)
216 {
217   SG_UNUSED(acType); // sadly not used at the moment
218   
219   // most exact seach - airline codes must be present and match
220   FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
221   if (result) {
222     return ParkingAssignment(result, this);
223   }
224   
225   // more tolerant - gates with empty airline codes are permitted
226   result = innerGetAvailableParking(radius, flType, airline, false);
227   if (result) {
228     return ParkingAssignment(result, this);
229   }
230
231   // fallback - ignore the airline code entirely
232   result = innerGetAvailableParking(radius, flType, string(), false);
233   return result ? ParkingAssignment(result, this) : ParkingAssignment();
234 }
235
236 ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
237 {
238     const FGParkingList& parkings(getGroundNetwork()->allParkings());
239     FGParkingList::const_iterator it;
240     for (it = parkings.begin(); it != parkings.end(); ++it) {
241         if ((*it)->name() == name) {
242             return ParkingAssignment(*it, const_cast<FGAirportDynamics*>(this));
243         }
244     }
245
246     return ParkingAssignment();
247 }
248
249 FGGroundNetwork *FGAirportDynamics::getGroundNetwork() const
250 {
251     return _ap->groundNetwork();
252 }
253
254 void FGAirportDynamics::setParkingAvailable(FGParking* park, bool available)
255 {
256   if (available) {
257     releaseParking(park);
258   } else {
259     occupiedParkings.insert(park);
260   }
261 }
262
263 bool FGAirportDynamics::isParkingAvailable(FGParking* parking) const
264 {
265   return (occupiedParkings.find(parking) == occupiedParkings.end());
266 }
267
268 void FGAirportDynamics::releaseParking(FGParking* id)
269 {
270   ParkingSet::iterator it = occupiedParkings.find(id);
271   if (it == occupiedParkings.end()) {
272     return;
273   }
274   
275   occupiedParkings.erase(it);
276 }
277
278 class GetParkingsPredicate
279 {
280     bool mustBeAvailable;
281     std::string type;
282     const FGAirportDynamics* dynamics;
283 public:
284     GetParkingsPredicate(bool b, const std::string& ty, const FGAirportDynamics* dyn) :
285         mustBeAvailable(b),
286         type(ty),
287         dynamics(dyn)
288     {}
289
290     bool operator()(const FGParkingRef& park) const
291     {
292         if (!type.empty() && (park->getType() != type))
293             return true;
294
295         if (mustBeAvailable && !dynamics->isParkingAvailable(park)) {
296             return true;
297         }
298
299         return false;
300     }
301 };
302
303 FGParkingList FGAirportDynamics::getParkings(bool onlyAvailable, const std::string &type) const
304 {
305     FGParkingList result(getGroundNetwork()->allParkings());
306
307     GetParkingsPredicate pred(onlyAvailable, type, this);
308     FGParkingList::iterator it = std::remove_if(result.begin(), result.end(), pred);
309     result.erase(it, result.end());
310     return result;
311 }
312
313 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
314 {
315     rwyPrefs = ref;
316 }
317
318 bool areRunwaysParallel(const FGRunwayRef& a, const FGRunwayRef& b)
319 {
320     double hdgDiff = (b->headingDeg() - a->headingDeg());
321     SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0);
322     return (fabs(hdgDiff) < 5.0);
323 }
324
325 double runwayScore(const FGRunwayRef& rwy)
326 {
327     return rwy->lengthM() + rwy->widthM();
328 }
329
330 double runwayWindScore(const FGRunwayRef& runway, double windHeading,
331                        double windSpeedKts)
332 {
333     double hdgDiff = fabs(windHeading - runway->headingDeg()) * SG_DEGREES_TO_RADIANS;
334     SGMiscd::normalizeAngle(hdgDiff);
335
336     double crossWind = windSpeedKts * sin(hdgDiff);
337     double tailWind = -windSpeedKts * cos(hdgDiff);
338
339     return -(crossWind + tailWind);
340 }
341
342 typedef std::vector<FGRunwayRef> RunwayVec;
343
344
345 class FallbackRunwayGroup
346 {
347 public:
348     FallbackRunwayGroup(const FGRunwayRef& rwy) :
349         _groupScore(0.0)
350     {
351         runways.push_back(rwy);
352         _leadRunwayScore = runwayScore(rwy);
353         _groupScore += _leadRunwayScore;
354     }
355
356     bool canAccept(const FGRunwayRef& rwy) const
357     {
358         if (!areRunwaysParallel(runways.front(), rwy)) {
359             return false;
360         }
361
362         return true;
363     }
364
365     void addRunway(const FGRunwayRef& rwy)
366     {
367         assert(areRunwaysParallel(runways.front(), rwy));
368         double score = runwayScore(rwy);
369         if (score < (0.5 * _leadRunwayScore)) {
370             // drop the runway completely. this is to drop short parallel
371             // runways from being active
372             return;
373         }
374
375         runways.push_back(rwy);
376         _groupScore += score;
377     }
378
379     void adjustScoreForWind(double windHeading, double windSpeedKts)
380     {
381         _basicScore = _groupScore;
382         RunwayVec::iterator it;
383         for (it = runways.begin(); it != runways.end(); ++it) {
384             _groupScore += runwayWindScore(*it, windHeading, windSpeedKts);
385         }
386     }
387
388     double groupScore() const
389     {
390         return _groupScore;
391     }
392
393     void getRunways(FGRunwayList& arrivals, FGRunwayList& departures)
394     {
395     // make the common cases very obvious
396         if (runways.size() == 1) {
397             arrivals.push_back(runways.front());
398             departures.push_back(runways.front());
399             return;
400         }
401
402
403     // becuase runways were sorted by score when building, they were added
404     // by score also, so we can use a simple algorithim to assign
405         for (unsigned int r=0; r < runways.size(); ++r) {
406             if ((r % 2) == 0) {
407                 arrivals.push_back(runways[r]);
408             } else {
409                 departures.push_back(runways[r]);
410             }
411         }
412     }
413
414     std::string dump()
415     {
416         ostringstream os;
417         os << runways.front()->ident();
418         for (unsigned int r=1; r <runways.size(); ++r) {
419             os << ", " << runways[r]->ident();
420         }
421
422         os << " (score=" << _basicScore << ", wind score=" << _groupScore << ")";
423         return os.str();
424     }
425
426 private:
427     RunwayVec runways;
428     double _groupScore,
429         _leadRunwayScore;
430     double _basicScore;
431
432 };
433
434 class WindExclusionCheck
435 {
436 public:
437     WindExclusionCheck(double windHeading, double windSpeedKts) :
438         _windSpeedKts(windSpeedKts),
439         _windHeading(windHeading)
440     {}
441
442     bool operator()(const FGRunwayRef& rwy) const
443     {
444         return (runwayWindScore(rwy, _windHeading, _windSpeedKts) > 30);
445     }
446 private:
447     double _windSpeedKts,
448         _windHeading;
449 };
450
451 class SortByScore
452 {
453 public:
454     bool operator()(const FGRunwayRef& a, const FGRunwayRef& b) const
455     {
456         return runwayScore(a) > runwayScore(b);
457     }
458 };
459
460 class GroupSortByScore
461 {
462 public:
463     bool operator()(const FallbackRunwayGroup& a, const FallbackRunwayGroup& b) const
464     {
465         return a.groupScore() > b.groupScore();
466     }
467 };
468
469 string FGAirportDynamics::fallbackGetActiveRunway(int action, double heading)
470 {
471     bool updateNeeded = false;
472     if (_lastFallbackUpdate == SGTimeStamp()) {
473         updateNeeded = true;
474     } else {
475         updateNeeded = (_lastFallbackUpdate.elapsedMSec() > (1000 * 60 * 15));
476     }
477
478     if (updateNeeded) {
479         double windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt");
480         double windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
481
482         // discount runways based on cross / tail-wind
483         WindExclusionCheck windCheck(windHeading, windSpeed);
484         RunwayVec runways(parent()->getRunways());
485         RunwayVec::iterator it = std::remove_if(runways.begin(), runways.end(),
486                                                 windCheck);
487         runways.erase(it, runways.end());
488
489         // sort highest scored to lowest scored
490         std::sort(runways.begin(), runways.end(), SortByScore());
491
492         std::vector<FallbackRunwayGroup> groups;
493         std::vector<FallbackRunwayGroup>::iterator git;
494
495         for (it = runways.begin(); it != runways.end(); ++it) {
496             bool existingGroupDidAccept = false;
497             for (git = groups.begin(); git != groups.end(); ++git) {
498                 if (git->canAccept(*it)) {
499                     existingGroupDidAccept = true;
500                     git->addRunway(*it);
501                     break;
502                 }
503             } // of existing groups iteration
504
505             if (!existingGroupDidAccept) {
506                 // create a new group
507                 groups.push_back(FallbackRunwayGroup(*it));
508             }
509         } // of group building phase
510
511
512         // select highest scored group based on cross/tail wind
513         for (git = groups.begin(); git != groups.end(); ++git) {
514             git->adjustScoreForWind(windHeading, windSpeed);
515         }
516
517         std::sort(groups.begin(), groups.end(), GroupSortByScore());
518
519         {
520             ostringstream os;
521             os << parent()->ident() << " groups:";
522
523             for (git = groups.begin(); git != groups.end(); ++git) {
524                 os << "\n\t" << git->dump();
525             }
526
527             std::string s = os.str();
528             SG_LOG(SG_AI, SG_INFO, s);
529         }
530
531         // assign takeoff and landing runways
532         FallbackRunwayGroup bestGroup = groups.front();
533         
534         _lastFallbackUpdate.stamp();
535         _fallbackRunwayCounter = 0;
536         _fallbackDepartureRunways.clear();
537         _fallbackArrivalRunways.clear();
538         bestGroup.getRunways(_fallbackArrivalRunways, _fallbackDepartureRunways);
539
540         ostringstream os;
541         os << "\tArrival:" << _fallbackArrivalRunways.front()->ident();
542         for (unsigned int r=1; r <_fallbackArrivalRunways.size(); ++r) {
543             os << ", " << _fallbackArrivalRunways[r]->ident();
544         }
545         os << "\n\tDeparture:" << _fallbackDepartureRunways.front()->ident();
546         for (unsigned int r=1; r <_fallbackDepartureRunways.size(); ++r) {
547             os << ", " << _fallbackDepartureRunways[r]->ident();
548         }
549
550         std::string s = os.str();
551         SG_LOG(SG_AI, SG_INFO, parent()->ident() << " fallback runways assignments for "
552                << static_cast<int>(windHeading) << "@" << static_cast<int>(windSpeed) << "\n" << s);
553     }
554
555     _fallbackRunwayCounter++;
556     FGRunwayRef r;
557     // ensure we cycle through possible runways where they exist
558     if (action == 1) {
559         r = _fallbackDepartureRunways[_fallbackRunwayCounter % _fallbackDepartureRunways.size()];
560     } else {
561         r = _fallbackArrivalRunways[_fallbackRunwayCounter % _fallbackArrivalRunways.size()];
562     }
563
564     return r->ident();
565 }
566
567 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
568                                              int action, string & runway,
569                                              double heading)
570 {
571     double windSpeed;
572     double windHeading;
573     double maxTail;
574     double maxCross;
575     string name;
576     string type;
577
578     if (!rwyPrefs.available()) {
579         runway = fallbackGetActiveRunway(action, heading);
580         return true;
581     }
582
583     RunwayGroup *currRunwayGroup = 0;
584     int nrActiveRunways = 0;
585     time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
586     if ((std::abs((long) (dayStart - lastUpdate)) > 600)
587         || trafficType != prevTrafficType) {
588         landing.clear();
589         takeoff.clear();
590         lastUpdate = dayStart;
591         prevTrafficType = trafficType;
592         /*
593         FGEnvironment
594             stationweather =
595             ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
596             ->getEnvironment(getLatitude(), getLongitude(),
597                              getElevation());
598         */
599         windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
600         windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
601         //stationweather.get_wind_from_heading_deg();
602         string scheduleName;
603         //cerr << "finding active Runway for : " << _ap->getId() << endl;
604         //cerr << "Wind Heading              : " << windHeading << endl;
605         //cerr << "Wind Speed                : " << windSpeed << endl;
606
607         //cerr << "Nr of seconds since day start << " << dayStart << endl;
608
609         ScheduleTime *currSched;
610         //cerr << "A"<< endl;
611         currSched = rwyPrefs.getSchedule(trafficType.c_str());
612         if (!(currSched))
613             return false;
614         //cerr << "B"<< endl;
615         scheduleName = currSched->getName(dayStart);
616         maxTail = currSched->getTailWind();
617         maxCross = currSched->getCrossWind();
618         //cerr << "Current Schedule =        : " << scheduleName << endl;
619         if (scheduleName.empty())
620             return false;
621         //cerr << "C"<< endl;
622         currRunwayGroup = rwyPrefs.getGroup(scheduleName);
623         //cerr << "D"<< endl;
624         if (!(currRunwayGroup))
625             return false;
626         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
627
628         // Keep a history of the currently active runways, to ensure
629         // that an already established selection of runways will not
630         // be overridden once a more preferred selection becomes 
631         // available as that can lead to random runway swapping.
632         if (trafficType == "com") {
633             currentlyActive = &comActive;
634         } else if (trafficType == "gen") {
635             currentlyActive = &genActive;
636         } else if (trafficType == "mil") {
637             currentlyActive = &milActive;
638         } else if (trafficType == "ul") {
639             currentlyActive = &ulActive;
640         }
641
642         //cerr << "Durrently active selection for " << trafficType << ": ";
643         for (stringVecIterator it = currentlyActive->begin();
644              it != currentlyActive->end(); it++) {
645              //cerr << (*it) << " ";
646          }
647          //cerr << endl;
648
649         currRunwayGroup->setActive(_ap,
650                                    windSpeed,
651                                    windHeading,
652                                    maxTail, maxCross, currentlyActive);
653
654         // Note that I SHOULD keep multiple lists in memory, one for 
655         // general aviation, one for commercial and one for military
656         // traffic.
657         currentlyActive->clear();
658         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
659         //cerr << "Choosing runway for " << trafficType << endl;
660         for (int i = 0; i < nrActiveRunways; i++) {
661             type = "unknown";   // initialize to something other than landing or takeoff
662             currRunwayGroup->getActive(i, name, type);
663             if (type == "landing") {
664                 landing.push_back(name);
665                 currentlyActive->push_back(name);
666                 //cerr << "Landing " << name << endl; 
667             }
668             if (type == "takeoff") {
669                 takeoff.push_back(name);
670                 currentlyActive->push_back(name);
671                 //cerr << "takeoff " << name << endl;
672             }
673         }
674         //cerr << endl;
675     }
676
677     if (action == 1)            // takeoff 
678     {
679         int nr = takeoff.size();
680         if (nr) {
681             // Note that the randomization below, is just a placeholder to choose between
682             // multiple active runways for this action. This should be
683             // under ATC control.
684             runway = chooseRwyByHeading(takeoff, heading);
685         } else {                // Fallback
686             runway = chooseRunwayFallback();
687         }
688     }
689
690     if (action == 2)            // landing
691     {
692         int nr = landing.size();
693         if (nr) {
694             runway = chooseRwyByHeading(landing, heading);
695         } else {                //fallback
696             runway = chooseRunwayFallback();
697         }
698     }
699
700     return true;
701 }
702
703 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
704                                              double heading)
705 {
706     double bestError = 360.0;
707     double rwyHeading, headingError;
708     string runway;
709     for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
710         if (!_ap->hasRunwayWithIdent(*i)) {
711           SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
712             " not found at " << _ap->ident());
713           continue;
714         }
715         
716         FGRunway *rwy = _ap->getRunwayByIdent((*i));
717         rwyHeading = rwy->headingDeg();
718         headingError = fabs(heading - rwyHeading);
719         if (headingError > 180)
720             headingError = fabs(headingError - 360);
721         if (headingError < bestError) {
722             runway = (*i);
723             bestError = headingError;
724         }
725     }
726     //cerr << "Using active runway " << runway << " for heading " << heading << endl;
727     return runway;
728 }
729
730 void FGAirportDynamics::getActiveRunway(const string & trafficType,
731                                         int action, string & runway,
732                                         double heading)
733 {
734     bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
735     if (!ok) {
736         runway = chooseRunwayFallback();
737     }
738 }
739
740 string FGAirportDynamics::chooseRunwayFallback()
741 {
742     FGRunway *rwy = _ap->getActiveRunwayForUsage();
743     return rwy->ident();
744 }
745
746 double FGAirportDynamics::getElevation() const
747 {
748     return _ap->getElevation();
749 }
750
751 const string FGAirportDynamics::getId() const
752 {
753     return _ap->getId();
754 }
755
756 // Experimental: Return a different ground frequency depending on the leg of the
757 // Flight. Leg should always have a minimum value of two when this function is called. 
758 // Note that in this scheme, the assignment of various frequencies to various ground 
759 // operations is completely arbitrary. As such, is a short cut I need to take now,
760 // so that at least I can start working on assigning different frequencies to different
761 // operations.
762
763 int FGAirportDynamics::getGroundFrequency(unsigned leg)
764 {
765     //return freqGround.size() ? freqGround[0] : 0; };
766     //cerr << "Getting frequency for : " << leg << endl;
767     int groundFreq = 0;
768     if (leg < 1) {
769         SG_LOG(SG_ATC, SG_ALERT,
770                "Leg value is smaller than one at " << SG_ORIGIN);
771     }
772
773     const intVec& freqGround(getGroundNetwork()->getGroundFrequencies());
774
775     if (freqGround.size() == 0) {
776         return 0;
777     }
778
779     if ((freqGround.size() < leg) && (leg > 0)) {
780         groundFreq =
781             (freqGround.size() <=
782              (leg - 1)) ? freqGround[freqGround.size() -
783                                      1] : freqGround[leg - 1];
784     }
785     if ((freqGround.size() >= leg) && (leg > 0)) {
786         groundFreq = freqGround[leg - 1];
787     }
788     return groundFreq;
789 }
790
791 int FGAirportDynamics::getTowerFrequency(unsigned nr)
792 {
793     int towerFreq = 0;
794     if (nr < 2) {
795         SG_LOG(SG_ATC, SG_ALERT,
796                "Leg value is smaller than two at " << SG_ORIGIN);
797     }
798
799     const intVec& freqTower(getGroundNetwork()->getTowerFrequencies());
800
801     if (freqTower.size() == 0) {
802         return 0;
803     }
804     if ((freqTower.size() > nr - 1) && (nr > 1)) {
805         towerFreq = freqTower[nr - 1];
806     }
807     if ((freqTower.size() < nr - 1) && (nr > 1)) {
808         towerFreq =
809             (freqTower.size() <
810              (nr - 1)) ? freqTower[freqTower.size() -
811                                      1] : freqTower[nr - 2];
812     }
813     if ((freqTower.size() >= nr - 1) && (nr > 1)) {
814         towerFreq = freqTower[nr - 2];
815     }
816     return towerFreq;
817 }
818
819 const std::string FGAirportDynamics::getAtisSequence()
820 {
821    if (atisSequenceIndex == -1) {
822        updateAtisSequence(1, false);
823    }
824
825    char atisSequenceString[2];
826    atisSequenceString[0] = 'a' + atisSequenceIndex;
827    atisSequenceString[1] = 0;
828    
829    return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
830 }
831
832 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
833 {
834     double now = globals->get_sim_time_sec();
835     if (atisSequenceIndex == -1) {
836         // first computation
837         atisSequenceTimeStamp = now;
838         atisSequenceIndex = rand() % 26; // random initial sequence letters
839         return atisSequenceIndex;
840     }
841
842     int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
843     atisSequenceTimeStamp += (interval * steps);
844     if (forceUpdate && (steps == 0)) {
845         ++steps; // a "special" ATIS update is required
846     } 
847     
848     atisSequenceIndex = (atisSequenceIndex + steps) % 26;
849     // return a huge value if no update occurred
850     return (atisSequenceIndex + (steps ? 0 : 26*1000));
851 }