]> git.mxchange.org Git - flightgear.git/blob - src/Airports/dynamics.cxx
Add airport dynamics manager
[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 <Navaids/NavDataCache.hxx>
44
45 #include "airport.hxx"
46 #include "dynamics.hxx"
47
48 using std::string;
49 using std::vector;
50 using std::sort;
51 using std::random_shuffle;
52
53 class ParkingAssignment::ParkingAssignmentPrivate
54 {
55 public:
56   ParkingAssignmentPrivate(FGParking* pk, FGAirportDynamics* dyn) :
57     refCount(0),
58     parking(pk),
59     dynamics(dyn)
60   {
61     assert(pk);
62     assert(dyn);
63     retain(); // initial count of 1
64   }
65   
66   ~ParkingAssignmentPrivate()
67   {
68     dynamics->releaseParking(parking);
69   }
70   
71   void release()
72   {
73     if ((--refCount) == 0) {
74       delete this;
75     }
76   }
77   
78   void retain()
79   {
80     ++refCount;
81   }
82   
83   unsigned int refCount;
84   FGParkingRef parking;
85   FGAirportDynamicsRef dynamics;
86 };
87
88 ParkingAssignment::ParkingAssignment() :
89   _sharedData(NULL)
90 {
91 }
92
93 ParkingAssignment::~ParkingAssignment()
94 {
95   if (_sharedData) {
96     _sharedData->release();
97   }
98 }
99   
100 ParkingAssignment::ParkingAssignment(FGParking* pk, FGAirportDynamics* dyn) :
101   _sharedData(NULL)
102 {
103   if (pk) {
104     _sharedData = new ParkingAssignmentPrivate(pk, dyn);
105   }
106 }
107
108 ParkingAssignment::ParkingAssignment(const ParkingAssignment& aOther) :
109   _sharedData(aOther._sharedData)
110 {
111   if (_sharedData) {
112     _sharedData->retain();
113   }
114 }
115
116 void ParkingAssignment::operator=(const ParkingAssignment& aOther)
117 {
118   if (_sharedData == aOther._sharedData) {
119     return; // self-assignment, special case
120   }
121   
122   if (_sharedData) {
123     _sharedData->release();
124   }
125   
126   _sharedData = aOther._sharedData;
127   if (_sharedData) {
128     _sharedData->retain();
129   }
130 }
131   
132 void ParkingAssignment::release()
133 {
134   if (_sharedData) {
135     _sharedData->release();
136     _sharedData = NULL;
137   }
138 }
139
140 bool ParkingAssignment::isValid() const
141 {
142   return (_sharedData != NULL);
143 }
144
145 FGParking* ParkingAssignment::parking() const
146 {
147   return _sharedData ? _sharedData->parking.ptr() : NULL;
148 }
149
150 ////////////////////////////////////////////////////////////////////////////////
151
152 FGAirportDynamics::FGAirportDynamics(FGAirport * ap):
153     _ap(ap), rwyPrefs(ap),
154     startupController    (this),
155     towerController      (this),
156     approachController   (this),
157     atisSequenceIndex(-1),
158     atisSequenceTimeStamp(0.0)
159
160 {
161     lastUpdate = 0;
162 }
163
164 // Destructor
165 FGAirportDynamics::~FGAirportDynamics()
166 {
167     SG_LOG(SG_AI, SG_INFO, "destroyed dynamics for:" << _ap->ident());
168 }
169
170
171 // Initialization required after XMLRead
172 void FGAirportDynamics::init()
173 {
174     groundNetwork.init(this);
175     groundNetwork.setTowerController(&towerController);
176     
177 }
178
179 FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
180                                            const string & airline,
181                                            bool skipEmptyAirlineCode)
182 {
183     const FGParkingList& parkings(groundNetwork.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 ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
209                                             const string & acType,
210                                             const string & airline)
211 {
212   SG_UNUSED(acType); // sadly not used at the moment
213   
214   // most exact seach - airline codes must be present and match
215   FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
216   if (result) {
217     return ParkingAssignment(result, this);
218   }
219   
220   // more tolerant - gates with empty airline codes are permitted
221   result = innerGetAvailableParking(radius, flType, airline, false);
222   if (result) {
223     return ParkingAssignment(result, this);
224   }
225
226   // fallback - ignore the airline code entirely
227   result = innerGetAvailableParking(radius, flType, string(), false);
228   return result ? ParkingAssignment(result, this) : ParkingAssignment();
229 }
230
231 ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
232 {
233     const FGParkingList& parkings(groundNetwork.allParkings());
234     FGParkingList::const_iterator it;
235     for (it = parkings.begin(); it != parkings.end(); ++it) {
236         if ((*it)->name() == name) {
237             return ParkingAssignment(*it, const_cast<FGAirportDynamics*>(this));
238         }
239     }
240
241   return ParkingAssignment();
242 }
243
244 void FGAirportDynamics::setParkingAvailable(FGParking* park, bool available)
245 {
246   if (available) {
247     releaseParking(park);
248   } else {
249     occupiedParkings.insert(park);
250   }
251 }
252
253 bool FGAirportDynamics::isParkingAvailable(FGParking* parking) const
254 {
255   return (occupiedParkings.find(parking) == occupiedParkings.end());
256 }
257
258 void FGAirportDynamics::releaseParking(FGParking* id)
259 {
260   ParkingSet::iterator it = occupiedParkings.find(id);
261   if (it == occupiedParkings.end()) {
262     return;
263   }
264   
265   occupiedParkings.erase(it);
266 }
267
268 class GetParkingsPredicate
269 {
270     bool mustBeAvailable;
271     std::string type;
272     const FGAirportDynamics* dynamics;
273 public:
274     GetParkingsPredicate(bool b, const std::string& ty, const FGAirportDynamics* dyn) :
275         mustBeAvailable(b),
276         type(ty),
277         dynamics(dyn)
278     {}
279
280     bool operator()(const FGParkingRef& park) const
281     {
282         if (!type.empty() && (park->getType() != type))
283             return true;
284
285         if (mustBeAvailable && !dynamics->isParkingAvailable(park)) {
286             return true;
287         }
288
289         return false;
290     }
291 };
292
293 FGParkingList FGAirportDynamics::getParkings(bool onlyAvailable, const std::string &type) const
294 {
295     FGParkingList result(groundNetwork.allParkings());
296
297     GetParkingsPredicate pred(onlyAvailable, type, this);
298     FGParkingList::iterator it = std::remove_if(result.begin(), result.end(), pred);
299     result.erase(it, result.end());
300     return result;
301 }
302
303 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
304 {
305     rwyPrefs = ref;
306 }
307
308 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
309                                              int action, string & runway,
310                                              double heading)
311 {
312     double windSpeed;
313     double windHeading;
314     double maxTail;
315     double maxCross;
316     string name;
317     string type;
318
319     if (!rwyPrefs.available()) {
320         return false;
321     }
322
323     RunwayGroup *currRunwayGroup = 0;
324     int nrActiveRunways = 0;
325     time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
326     if ((std::abs((long) (dayStart - lastUpdate)) > 600)
327         || trafficType != prevTrafficType) {
328         landing.clear();
329         takeoff.clear();
330         lastUpdate = dayStart;
331         prevTrafficType = trafficType;
332         /*
333         FGEnvironment
334             stationweather =
335             ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
336             ->getEnvironment(getLatitude(), getLongitude(),
337                              getElevation());
338         */
339         windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
340         windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
341         //stationweather.get_wind_from_heading_deg();
342         string scheduleName;
343         //cerr << "finding active Runway for : " << _ap->getId() << endl;
344         //cerr << "Wind Heading              : " << windHeading << endl;
345         //cerr << "Wind Speed                : " << windSpeed << endl;
346
347         //cerr << "Nr of seconds since day start << " << dayStart << endl;
348
349         ScheduleTime *currSched;
350         //cerr << "A"<< endl;
351         currSched = rwyPrefs.getSchedule(trafficType.c_str());
352         if (!(currSched))
353             return false;
354         //cerr << "B"<< endl;
355         scheduleName = currSched->getName(dayStart);
356         maxTail = currSched->getTailWind();
357         maxCross = currSched->getCrossWind();
358         //cerr << "Current Schedule =        : " << scheduleName << endl;
359         if (scheduleName.empty())
360             return false;
361         //cerr << "C"<< endl;
362         currRunwayGroup = rwyPrefs.getGroup(scheduleName);
363         //cerr << "D"<< endl;
364         if (!(currRunwayGroup))
365             return false;
366         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
367
368         // Keep a history of the currently active runways, to ensure
369         // that an already established selection of runways will not
370         // be overridden once a more preferred selection becomes 
371         // available as that can lead to random runway swapping.
372         if (trafficType == "com") {
373             currentlyActive = &comActive;
374         } else if (trafficType == "gen") {
375             currentlyActive = &genActive;
376         } else if (trafficType == "mil") {
377             currentlyActive = &milActive;
378         } else if (trafficType == "ul") {
379             currentlyActive = &ulActive;
380         }
381
382         //cerr << "Durrently active selection for " << trafficType << ": ";
383         for (stringVecIterator it = currentlyActive->begin();
384              it != currentlyActive->end(); it++) {
385              //cerr << (*it) << " ";
386          }
387          //cerr << endl;
388
389         currRunwayGroup->setActive(_ap,
390                                    windSpeed,
391                                    windHeading,
392                                    maxTail, maxCross, currentlyActive);
393
394         // Note that I SHOULD keep multiple lists in memory, one for 
395         // general aviation, one for commercial and one for military
396         // traffic.
397         currentlyActive->clear();
398         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
399         //cerr << "Choosing runway for " << trafficType << endl;
400         for (int i = 0; i < nrActiveRunways; i++) {
401             type = "unknown";   // initialize to something other than landing or takeoff
402             currRunwayGroup->getActive(i, name, type);
403             if (type == "landing") {
404                 landing.push_back(name);
405                 currentlyActive->push_back(name);
406                 //cerr << "Landing " << name << endl; 
407             }
408             if (type == "takeoff") {
409                 takeoff.push_back(name);
410                 currentlyActive->push_back(name);
411                 //cerr << "takeoff " << name << endl;
412             }
413         }
414         //cerr << endl;
415     }
416
417     if (action == 1)            // takeoff 
418     {
419         int nr = takeoff.size();
420         if (nr) {
421             // Note that the randomization below, is just a placeholder to choose between
422             // multiple active runways for this action. This should be
423             // under ATC control.
424             runway = chooseRwyByHeading(takeoff, heading);
425         } else {                // Fallback
426             runway = chooseRunwayFallback();
427         }
428     }
429
430     if (action == 2)            // landing
431     {
432         int nr = landing.size();
433         if (nr) {
434             runway = chooseRwyByHeading(landing, heading);
435         } else {                //fallback
436             runway = chooseRunwayFallback();
437         }
438     }
439
440     return true;
441 }
442
443 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
444                                              double heading)
445 {
446     double bestError = 360.0;
447     double rwyHeading, headingError;
448     string runway;
449     for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
450         if (!_ap->hasRunwayWithIdent(*i)) {
451           SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
452             " not found at " << _ap->ident());
453           continue;
454         }
455         
456         FGRunway *rwy = _ap->getRunwayByIdent((*i));
457         rwyHeading = rwy->headingDeg();
458         headingError = fabs(heading - rwyHeading);
459         if (headingError > 180)
460             headingError = fabs(headingError - 360);
461         if (headingError < bestError) {
462             runway = (*i);
463             bestError = headingError;
464         }
465     }
466     //cerr << "Using active runway " << runway << " for heading " << heading << endl;
467     return runway;
468 }
469
470 void FGAirportDynamics::getActiveRunway(const string & trafficType,
471                                         int action, string & runway,
472                                         double heading)
473 {
474     bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
475     if (!ok) {
476         runway = chooseRunwayFallback();
477     }
478 }
479
480 string FGAirportDynamics::chooseRunwayFallback()
481 {
482     FGRunway *rwy = _ap->getActiveRunwayForUsage();
483     return rwy->ident();
484 }
485
486 double FGAirportDynamics::getElevation() const
487 {
488     return _ap->getElevation();
489 }
490
491 const string FGAirportDynamics::getId() const
492 {
493     return _ap->getId();
494 }
495
496 // Experimental: Return a different ground frequency depending on the leg of the
497 // Flight. Leg should always have a minimum value of two when this function is called. 
498 // Note that in this scheme, the assignment of various frequencies to various ground 
499 // operations is completely arbitrary. As such, is a short cut I need to take now,
500 // so that at least I can start working on assigning different frequencies to different
501 // operations.
502
503 int FGAirportDynamics::getGroundFrequency(unsigned leg)
504 {
505     //return freqGround.size() ? freqGround[0] : 0; };
506     //cerr << "Getting frequency for : " << leg << endl;
507     int groundFreq = 0;
508     if (leg < 1) {
509         SG_LOG(SG_ATC, SG_ALERT,
510                "Leg value is smaller than one at " << SG_ORIGIN);
511     }
512     if (freqGround.size() == 0) {
513         return 0;
514     }
515
516     if ((freqGround.size() < leg) && (leg > 0)) {
517         groundFreq =
518             (freqGround.size() <=
519              (leg - 1)) ? freqGround[freqGround.size() -
520                                      1] : freqGround[leg - 1];
521     }
522     if ((freqGround.size() >= leg) && (leg > 0)) {
523         groundFreq = freqGround[leg - 1];
524     }
525     return groundFreq;
526 }
527
528 int FGAirportDynamics::getTowerFrequency(unsigned nr)
529 {
530     int towerFreq = 0;
531     if (nr < 2) {
532         SG_LOG(SG_ATC, SG_ALERT,
533                "Leg value is smaller than two at " << SG_ORIGIN);
534     }
535     if (freqTower.size() == 0) {
536         return 0;
537     }
538     if ((freqTower.size() > nr - 1) && (nr > 1)) {
539         towerFreq = freqTower[nr - 1];
540     }
541     if ((freqTower.size() < nr - 1) && (nr > 1)) {
542         towerFreq =
543             (freqTower.size() <
544              (nr - 1)) ? freqTower[freqTower.size() -
545                                      1] : freqTower[nr - 2];
546     }
547     if ((freqTower.size() >= nr - 1) && (nr > 1)) {
548         towerFreq = freqTower[nr - 2];
549     }
550     return towerFreq;
551 }
552
553 const std::string FGAirportDynamics::getAtisSequence()
554 {
555    if (atisSequenceIndex == -1) {
556        updateAtisSequence(1, false);
557    }
558
559    char atisSequenceString[2];
560    atisSequenceString[0] = 'a' + atisSequenceIndex;
561    atisSequenceString[1] = 0;
562    
563    return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
564 }
565
566 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
567 {
568     double now = globals->get_sim_time_sec();
569     if (atisSequenceIndex == -1) {
570         // first computation
571         atisSequenceTimeStamp = now;
572         atisSequenceIndex = rand() % 26; // random initial sequence letters
573         return atisSequenceIndex;
574     }
575
576     int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
577     atisSequenceTimeStamp += (interval * steps);
578     if (forceUpdate && (steps == 0)) {
579         ++steps; // a "special" ATIS update is required
580     } 
581     
582     atisSequenceIndex = (atisSequenceIndex + steps) % 26;
583     // return a huge value if no update occurred
584     return (atisSequenceIndex + (steps ? 0 : 26*1000));
585 }