]> git.mxchange.org Git - flightgear.git/blob - src/Airports/dynamics.cxx
httpd: provide more airport information in geojson
[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, FGAirport* apt) :
57     refCount(0),
58     parking(pk),
59     airport(apt)
60   {
61     assert(pk);
62     assert(apt);
63     retain(); // initial count of 1
64   }
65   
66   ~ParkingAssignmentPrivate()
67   {
68     airport->getDynamics()->releaseParking(parking->guid());
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   SGSharedPtr<FGParking> parking;
85   SGSharedPtr<FGAirport> airport;
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, FGAirport* apt) :
101   _sharedData(NULL)
102 {
103   if (pk) {
104     _sharedData = new ParkingAssignmentPrivate(pk, apt);
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 }
168
169
170 // Initialization required after XMLRead
171 void FGAirportDynamics::init()
172 {
173     groundNetwork.init(_ap);
174     groundNetwork.setTowerController(&towerController);
175     
176 }
177
178 FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
179                                            const string & airline,
180                                            bool skipEmptyAirlineCode)
181 {
182   flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
183   BOOST_FOREACH(PositionedID pk, cache->findAirportParking(_ap->guid(), flType, radius)) {
184     if (!isParkingAvailable(pk)) {
185       continue;
186     }
187     
188     FGParking* parking = getParking(pk);
189     if (skipEmptyAirlineCode && parking->getCodes().empty()) {
190       continue;
191     }
192     
193     if (!airline.empty() && !parking->getCodes().empty()) {
194       if (parking->getCodes().find(airline, 0) == string::npos) {
195         continue;
196       }
197     }
198     
199     setParkingAvailable(pk, false);
200     return parking;
201   }
202   
203   return NULL;
204 }
205
206 ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
207                                             const string & acType,
208                                             const string & airline)
209 {
210   SG_UNUSED(acType); // sadly not used at the moment
211   
212   // most exact seach - airline codes must be present and match
213   FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
214   if (result) {
215     return ParkingAssignment(result, _ap);
216   }
217   
218   // more tolerant - gates with empty airline codes are permitted
219   result = innerGetAvailableParking(radius, flType, airline, false);
220   if (result) {
221     return ParkingAssignment(result, _ap);
222   }
223
224   // fallback - ignore the airline code entirely
225   result = innerGetAvailableParking(radius, flType, string(), false);
226   return result ? ParkingAssignment(result, _ap) : ParkingAssignment();
227 }
228
229 FGParkingRef FGAirportDynamics::getParking(PositionedID id) const
230 {
231   return FGPositioned::loadById<FGParking>(id);
232 }
233
234 string FGAirportDynamics::getParkingName(PositionedID id) const
235 {
236   FGParking* p = getParking(id);
237   if (p) {
238     return p->getName();
239   }
240   
241   return string();
242 }
243
244 ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
245 {
246   PositionedID guid = flightgear::NavDataCache::instance()->airportItemWithIdent(parent()->guid(), FGPositioned::PARKING, name);
247   if (guid == 0) {
248     return ParkingAssignment();
249   }
250   
251   return ParkingAssignment(getParking(guid), _ap);
252 }
253
254 void FGAirportDynamics::setParkingAvailable(PositionedID guid, bool available)
255 {
256   if (available) {
257     releaseParking(guid);
258   } else {
259     occupiedParkings.insert(guid);
260   }
261 }
262
263 bool FGAirportDynamics::isParkingAvailable(PositionedID parking) const
264 {
265   return (occupiedParkings.find(parking) == occupiedParkings.end());
266 }
267
268 void FGAirportDynamics::releaseParking(PositionedID 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 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
279 {
280     rwyPrefs = ref;
281 }
282
283 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
284                                              int action, string & runway,
285                                              double heading)
286 {
287     double windSpeed;
288     double windHeading;
289     double maxTail;
290     double maxCross;
291     string name;
292     string type;
293
294     if (!rwyPrefs.available()) {
295         return false;
296     }
297
298     RunwayGroup *currRunwayGroup = 0;
299     int nrActiveRunways = 0;
300     time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
301     if ((abs((long) (dayStart - lastUpdate)) > 600)
302         || trafficType != prevTrafficType) {
303         landing.clear();
304         takeoff.clear();
305         lastUpdate = dayStart;
306         prevTrafficType = trafficType;
307         /*
308         FGEnvironment
309             stationweather =
310             ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
311             ->getEnvironment(getLatitude(), getLongitude(),
312                              getElevation());
313         */
314         windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
315         windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
316         //stationweather.get_wind_from_heading_deg();
317         string scheduleName;
318         //cerr << "finding active Runway for : " << _ap->getId() << endl;
319         //cerr << "Wind Heading              : " << windHeading << endl;
320         //cerr << "Wind Speed                : " << windSpeed << endl;
321
322         //cerr << "Nr of seconds since day start << " << dayStart << endl;
323
324         ScheduleTime *currSched;
325         //cerr << "A"<< endl;
326         currSched = rwyPrefs.getSchedule(trafficType.c_str());
327         if (!(currSched))
328             return false;
329         //cerr << "B"<< endl;
330         scheduleName = currSched->getName(dayStart);
331         maxTail = currSched->getTailWind();
332         maxCross = currSched->getCrossWind();
333         //cerr << "Current Schedule =        : " << scheduleName << endl;
334         if (scheduleName.empty())
335             return false;
336         //cerr << "C"<< endl;
337         currRunwayGroup = rwyPrefs.getGroup(scheduleName);
338         //cerr << "D"<< endl;
339         if (!(currRunwayGroup))
340             return false;
341         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
342
343         // Keep a history of the currently active runways, to ensure
344         // that an already established selection of runways will not
345         // be overridden once a more preferred selection becomes 
346         // available as that can lead to random runway swapping.
347         if (trafficType == "com") {
348             currentlyActive = &comActive;
349         } else if (trafficType == "gen") {
350             currentlyActive = &genActive;
351         } else if (trafficType == "mil") {
352             currentlyActive = &milActive;
353         } else if (trafficType == "ul") {
354             currentlyActive = &ulActive;
355         }
356
357         //cerr << "Durrently active selection for " << trafficType << ": ";
358         for (stringVecIterator it = currentlyActive->begin();
359              it != currentlyActive->end(); it++) {
360              //cerr << (*it) << " ";
361          }
362          //cerr << endl;
363
364         currRunwayGroup->setActive(_ap,
365                                    windSpeed,
366                                    windHeading,
367                                    maxTail, maxCross, currentlyActive);
368
369         // Note that I SHOULD keep multiple lists in memory, one for 
370         // general aviation, one for commercial and one for military
371         // traffic.
372         currentlyActive->clear();
373         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
374         //cerr << "Choosing runway for " << trafficType << endl;
375         for (int i = 0; i < nrActiveRunways; i++) {
376             type = "unknown";   // initialize to something other than landing or takeoff
377             currRunwayGroup->getActive(i, name, type);
378             if (type == "landing") {
379                 landing.push_back(name);
380                 currentlyActive->push_back(name);
381                 //cerr << "Landing " << name << endl; 
382             }
383             if (type == "takeoff") {
384                 takeoff.push_back(name);
385                 currentlyActive->push_back(name);
386                 //cerr << "takeoff " << name << endl;
387             }
388         }
389         //cerr << endl;
390     }
391
392     if (action == 1)            // takeoff 
393     {
394         int nr = takeoff.size();
395         if (nr) {
396             // Note that the randomization below, is just a placeholder to choose between
397             // multiple active runways for this action. This should be
398             // under ATC control.
399             runway = chooseRwyByHeading(takeoff, heading);
400         } else {                // Fallback
401             runway = chooseRunwayFallback();
402         }
403     }
404
405     if (action == 2)            // landing
406     {
407         int nr = landing.size();
408         if (nr) {
409             runway = chooseRwyByHeading(landing, heading);
410         } else {                //fallback
411             runway = chooseRunwayFallback();
412         }
413     }
414
415     return true;
416 }
417
418 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
419                                              double heading)
420 {
421     double bestError = 360.0;
422     double rwyHeading, headingError;
423     string runway;
424     for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
425         if (!_ap->hasRunwayWithIdent(*i)) {
426           SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
427             " not found at " << _ap->ident());
428           continue;
429         }
430         
431         FGRunway *rwy = _ap->getRunwayByIdent((*i));
432         rwyHeading = rwy->headingDeg();
433         headingError = fabs(heading - rwyHeading);
434         if (headingError > 180)
435             headingError = fabs(headingError - 360);
436         if (headingError < bestError) {
437             runway = (*i);
438             bestError = headingError;
439         }
440     }
441     //cerr << "Using active runway " << runway << " for heading " << heading << endl;
442     return runway;
443 }
444
445 void FGAirportDynamics::getActiveRunway(const string & trafficType,
446                                         int action, string & runway,
447                                         double heading)
448 {
449     bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
450     if (!ok) {
451         runway = chooseRunwayFallback();
452     }
453 }
454
455 string FGAirportDynamics::chooseRunwayFallback()
456 {
457     FGRunway *rwy = _ap->getActiveRunwayForUsage();
458     return rwy->ident();
459 }
460
461 double FGAirportDynamics::getElevation() const
462 {
463     return _ap->getElevation();
464 }
465
466 const string FGAirportDynamics::getId() const
467 {
468     return _ap->getId();
469 }
470
471 // Experimental: Return a different ground frequency depending on the leg of the
472 // Flight. Leg should always have a minimum value of two when this function is called. 
473 // Note that in this scheme, the assignment of various frequencies to various ground 
474 // operations is completely arbitrary. As such, is a short cut I need to take now,
475 // so that at least I can start working on assigning different frequencies to different
476 // operations.
477
478 int FGAirportDynamics::getGroundFrequency(unsigned leg)
479 {
480     //return freqGround.size() ? freqGround[0] : 0; };
481     //cerr << "Getting frequency for : " << leg << endl;
482     int groundFreq = 0;
483     if (leg < 1) {
484         SG_LOG(SG_ATC, SG_ALERT,
485                "Leg value is smaller than one at " << SG_ORIGIN);
486     }
487     if (freqGround.size() == 0) {
488         return 0;
489     }
490
491     if ((freqGround.size() < leg) && (leg > 0)) {
492         groundFreq =
493             (freqGround.size() <=
494              (leg - 1)) ? freqGround[freqGround.size() -
495                                      1] : freqGround[leg - 1];
496     }
497     if ((freqGround.size() >= leg) && (leg > 0)) {
498         groundFreq = freqGround[leg - 1];
499     }
500     return groundFreq;
501 }
502
503 int FGAirportDynamics::getTowerFrequency(unsigned nr)
504 {
505     int towerFreq = 0;
506     if (nr < 2) {
507         SG_LOG(SG_ATC, SG_ALERT,
508                "Leg value is smaller than two at " << SG_ORIGIN);
509     }
510     if (freqTower.size() == 0) {
511         return 0;
512     }
513     if ((freqTower.size() > nr - 1) && (nr > 1)) {
514         towerFreq = freqTower[nr - 1];
515     }
516     if ((freqTower.size() < nr - 1) && (nr > 1)) {
517         towerFreq =
518             (freqTower.size() <
519              (nr - 1)) ? freqTower[freqTower.size() -
520                                      1] : freqTower[nr - 2];
521     }
522     if ((freqTower.size() >= nr - 1) && (nr > 1)) {
523         towerFreq = freqTower[nr - 2];
524     }
525     return towerFreq;
526 }
527
528 const std::string FGAirportDynamics::getAtisSequence()
529 {
530    if (atisSequenceIndex == -1) {
531        updateAtisSequence(1, false);
532    }
533
534    char atisSequenceString[2];
535    atisSequenceString[0] = 'a' + atisSequenceIndex;
536    atisSequenceString[1] = 0;
537    
538    return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
539 }
540
541 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
542 {
543     double now = globals->get_sim_time_sec();
544     if (atisSequenceIndex == -1) {
545         // first computation
546         atisSequenceTimeStamp = now;
547         atisSequenceIndex = rand() % 26; // random initial sequence letters
548         return atisSequenceIndex;
549     }
550
551     int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
552     atisSequenceTimeStamp += (interval * steps);
553     if (forceUpdate && (steps == 0)) {
554         ++steps; // a "special" ATIS update is required
555     } 
556     
557     atisSequenceIndex = (atisSequenceIndex + steps) % 26;
558     // return a huge value if no update occurred
559     return (atisSequenceIndex + (steps ? 0 : 26*1000));
560 }