]> git.mxchange.org Git - flightgear.git/blob - src/Airports/dynamics.cxx
Clean up dynamics/parking handing code.
[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 <Airports/runways.hxx>
42 #include <ATCDCL/ATCutils.hxx>
43
44 #include "simple.hxx"
45 #include "dynamics.hxx"
46
47 using std::string;
48 using std::vector;
49 using std::sort;
50 using std::random_shuffle;
51
52 FGAirportDynamics::FGAirportDynamics(FGAirport * ap):
53     _ap(ap), rwyPrefs(ap),
54     startupController    (this),
55     towerController      (this),
56     approachController   (this),
57     atisSequenceIndex(-1),
58     atisSequenceTimeStamp(0.0)
59
60 {
61     lastUpdate = 0;
62 }
63
64 // Destructor
65 FGAirportDynamics::~FGAirportDynamics()
66 {
67 }
68
69
70 // Initialization required after XMLRead
71 void FGAirportDynamics::init()
72 {
73     // This may seem a bit weird to first randomly shuffle the parkings
74     // and then sort them again. However, parkings are sorted here by ascending 
75     // radius. Since many parkings have similar radii, with each radius class they will
76     // still be allocated relatively systematically. Randomizing prior to sorting will
77     // prevent any initial orderings to be destroyed, leading (hopefully) to a more 
78     // naturalistic gate assignment. 
79     random_shuffle(parkings.begin(), parkings.end());
80     sort(parkings.begin(), parkings.end());
81     // add the gate positions to the ground network. 
82     groundNetwork.setParent(_ap);
83     groundNetwork.addNodes(&parkings);
84     groundNetwork.init();
85     groundNetwork.setTowerController(&towerController);
86     
87 }
88
89 int FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
90                                            const string & acType,
91                                            const string & airline,
92                                            bool skipEmptyAirlineCode)
93 {
94   FGParkingVecIterator i;
95   for (i = parkings.begin(); i != parkings.end(); i++) {
96     // Taken by another aircraft, or no airline codes
97     if (!i->isAvailable()) {
98       continue;
99     }
100     
101     if (skipEmptyAirlineCode && i->getCodes().empty()) {
102       continue;
103     }
104     
105     // check airline codes match
106     if (!airline.empty() && !i->getCodes().empty()) {
107       if (i->getCodes().find(airline, 0) == string::npos) {
108         continue;
109       }
110     }
111     
112     // Type doesn't match
113     if (i->getType() != flType) {
114       continue;
115     }
116     // too small
117     if (i->getRadius() < radius) {
118       continue;
119     }
120     
121     i->setAvailable(false);
122     return i->getIndex();
123   }
124   
125   return -1;
126 }
127
128 int FGAirportDynamics::getAvailableParking(double radius, const string & flType,
129                                             const string & acType,
130                                             const string & airline)
131 {
132     if (parkings.empty()) {
133       return -1;
134     }
135   
136   // most exact seach - airline codes must be present and match
137   int result = innerGetAvailableParking(radius, flType, acType, airline, true);
138   if (result >= 0) {
139     return result;
140   }
141   
142   // more tolerant - gates with empty airline codes are permitted
143   result = innerGetAvailableParking(radius, flType, acType, airline, false);
144   if (result >= 0) {
145     return result;
146   }
147
148   // fallback - ignore the airline code entirely
149   return innerGetAvailableParking(radius, flType, acType, string(), false);
150 }
151
152 FGParking *FGAirportDynamics::getParking(int id)
153 {
154     FGParkingVecIterator i = parkings.begin();
155     for (i = parkings.begin(); i != parkings.end(); i++) {
156         if (id == i->getIndex()) {
157             return &(*i);
158         }
159     }
160     return 0;
161 }
162
163 string FGAirportDynamics::getParkingName(int id)
164 {
165     FGParkingVecIterator i = parkings.begin();
166     for (i = parkings.begin(); i != parkings.end(); i++) {
167         if (id == i->getIndex()) {
168             return i->getName();
169         }
170     }
171
172     return string("overflow");
173 }
174
175 int FGAirportDynamics::findParkingByName(const std::string& name) const
176 {
177   FGParkingVec::const_iterator i = parkings.begin();
178   for (i = parkings.begin(); i != parkings.end(); i++) {
179     if (i->getName() == name) {
180       return i->getIndex();
181     }
182   }
183   
184   return -1;
185 }
186
187 void FGAirportDynamics::releaseParking(int id)
188 {
189     if (id >= 0) {
190
191         FGParkingVecIterator i = parkings.begin();
192         for (i = parkings.begin(); i != parkings.end(); i++) {
193             if (id == i->getIndex()) {
194                 i->setAvailable(true);
195             }
196         }
197     }
198 }
199
200 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
201 {
202     rwyPrefs = ref;
203 }
204
205 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
206                                              int action, string & runway,
207                                              double heading)
208 {
209     double windSpeed;
210     double windHeading;
211     double maxTail;
212     double maxCross;
213     string name;
214     string type;
215
216     if (!rwyPrefs.available()) {
217         return false;
218     }
219
220     RunwayGroup *currRunwayGroup = 0;
221     int nrActiveRunways = 0;
222     time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
223     if ((abs((long) (dayStart - lastUpdate)) > 600)
224         || trafficType != prevTrafficType) {
225         landing.clear();
226         takeoff.clear();
227         lastUpdate = dayStart;
228         prevTrafficType = trafficType;
229         /*
230         FGEnvironment
231             stationweather =
232             ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
233             ->getEnvironment(getLatitude(), getLongitude(),
234                              getElevation());
235         */
236         windSpeed   = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
237         windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
238         //stationweather.get_wind_from_heading_deg();
239         string scheduleName;
240         //cerr << "finding active Runway for : " << _ap->getId() << endl;
241         //cerr << "Wind Heading              : " << windHeading << endl;
242         //cerr << "Wind Speed                : " << windSpeed << endl;
243
244         //cerr << "Nr of seconds since day start << " << dayStart << endl;
245
246         ScheduleTime *currSched;
247         //cerr << "A"<< endl;
248         currSched = rwyPrefs.getSchedule(trafficType.c_str());
249         if (!(currSched))
250             return false;
251         //cerr << "B"<< endl;
252         scheduleName = currSched->getName(dayStart);
253         maxTail = currSched->getTailWind();
254         maxCross = currSched->getCrossWind();
255         //cerr << "Current Schedule =        : " << scheduleName << endl;
256         if (scheduleName.empty())
257             return false;
258         //cerr << "C"<< endl;
259         currRunwayGroup = rwyPrefs.getGroup(scheduleName);
260         //cerr << "D"<< endl;
261         if (!(currRunwayGroup))
262             return false;
263         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
264
265         // Keep a history of the currently active runways, to ensure
266         // that an already established selection of runways will not
267         // be overridden once a more preferred selection becomes 
268         // available as that can lead to random runway swapping.
269         if (trafficType == "com") {
270             currentlyActive = &comActive;
271         } else if (trafficType == "gen") {
272             currentlyActive = &genActive;
273         } else if (trafficType == "mil") {
274             currentlyActive = &milActive;
275         } else if (trafficType == "ul") {
276             currentlyActive = &ulActive;
277         }
278
279         //cerr << "Durrently active selection for " << trafficType << ": ";
280         for (stringVecIterator it = currentlyActive->begin();
281              it != currentlyActive->end(); it++) {
282              //cerr << (*it) << " ";
283          }
284          //cerr << endl;
285
286         currRunwayGroup->setActive(_ap,
287                                    windSpeed,
288                                    windHeading,
289                                    maxTail, maxCross, currentlyActive);
290
291         // Note that I SHOULD keep multiple lists in memory, one for 
292         // general aviation, one for commercial and one for military
293         // traffic.
294         currentlyActive->clear();
295         nrActiveRunways = currRunwayGroup->getNrActiveRunways();
296         //cerr << "Choosing runway for " << trafficType << endl;
297         for (int i = 0; i < nrActiveRunways; i++) {
298             type = "unknown";   // initialize to something other than landing or takeoff
299             currRunwayGroup->getActive(i, name, type);
300             if (type == "landing") {
301                 landing.push_back(name);
302                 currentlyActive->push_back(name);
303                 //cerr << "Landing " << name << endl; 
304             }
305             if (type == "takeoff") {
306                 takeoff.push_back(name);
307                 currentlyActive->push_back(name);
308                 //cerr << "takeoff " << name << endl;
309             }
310         }
311         //cerr << endl;
312     }
313
314     if (action == 1)            // takeoff 
315     {
316         int nr = takeoff.size();
317         if (nr) {
318             // Note that the randomization below, is just a placeholder to choose between
319             // multiple active runways for this action. This should be
320             // under ATC control.
321             runway = chooseRwyByHeading(takeoff, heading);
322         } else {                // Fallback
323             runway = chooseRunwayFallback();
324         }
325     }
326
327     if (action == 2)            // landing
328     {
329         int nr = landing.size();
330         if (nr) {
331             runway = chooseRwyByHeading(landing, heading);
332         } else {                //fallback
333             runway = chooseRunwayFallback();
334         }
335     }
336
337     return true;
338 }
339
340 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
341                                              double heading)
342 {
343     double bestError = 360.0;
344     double rwyHeading, headingError;
345     string runway;
346     for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
347         if (!_ap->hasRunwayWithIdent(*i)) {
348           SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
349             " not found at " << _ap->ident());
350           continue;
351         }
352         
353         FGRunway *rwy = _ap->getRunwayByIdent((*i));
354         rwyHeading = rwy->headingDeg();
355         headingError = fabs(heading - rwyHeading);
356         if (headingError > 180)
357             headingError = fabs(headingError - 360);
358         if (headingError < bestError) {
359             runway = (*i);
360             bestError = headingError;
361         }
362     }
363     //cerr << "Using active runway " << runway << " for heading " << heading << endl;
364     return runway;
365 }
366
367 void FGAirportDynamics::getActiveRunway(const string & trafficType,
368                                         int action, string & runway,
369                                         double heading)
370 {
371     bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
372     if (!ok) {
373         runway = chooseRunwayFallback();
374     }
375 }
376
377 string FGAirportDynamics::chooseRunwayFallback()
378 {
379     FGRunway *rwy = _ap->getActiveRunwayForUsage();
380     return rwy->ident();
381 }
382
383 void FGAirportDynamics::addParking(FGParking & park)
384 {
385     parkings.push_back(park);
386 }
387
388 double FGAirportDynamics::getElevation() const
389 {
390     return _ap->getElevation();
391 }
392
393 const string FGAirportDynamics::getId() const
394 {
395     return _ap->getId();
396 }
397
398 // Experimental: Return a different ground frequency depending on the leg of the
399 // Flight. Leg should always have a minimum value of two when this function is called. 
400 // Note that in this scheme, the assignment of various frequencies to various ground 
401 // operations is completely arbitrary. As such, is a short cut I need to take now,
402 // so that at least I can start working on assigning different frequencies to different
403 // operations.
404
405 int FGAirportDynamics::getGroundFrequency(unsigned leg)
406 {
407     //return freqGround.size() ? freqGround[0] : 0; };
408     //cerr << "Getting frequency for : " << leg << endl;
409     int groundFreq = 0;
410     if (leg < 1) {
411         SG_LOG(SG_ATC, SG_ALERT,
412                "Leg value is smaller than one at " << SG_ORIGIN);
413     }
414     if (freqGround.size() == 0) {
415         return 0;
416     }
417
418     if ((freqGround.size() < leg) && (leg > 0)) {
419         groundFreq =
420             (freqGround.size() <=
421              (leg - 1)) ? freqGround[freqGround.size() -
422                                      1] : freqGround[leg - 1];
423     }
424     if ((freqGround.size() >= leg) && (leg > 0)) {
425         groundFreq = freqGround[leg - 1];
426     }
427     return groundFreq;
428 }
429
430 int FGAirportDynamics::getTowerFrequency(unsigned nr)
431 {
432     int towerFreq = 0;
433     if (nr < 2) {
434         SG_LOG(SG_ATC, SG_ALERT,
435                "Leg value is smaller than two at " << SG_ORIGIN);
436     }
437     if (freqTower.size() == 0) {
438         return 0;
439     }
440     if ((freqTower.size() > nr - 1) && (nr > 1)) {
441         towerFreq = freqTower[nr - 1];
442     }
443     if ((freqTower.size() < nr - 1) && (nr > 1)) {
444         towerFreq =
445             (freqTower.size() <
446              (nr - 1)) ? freqTower[freqTower.size() -
447                                      1] : freqTower[nr - 2];
448     }
449     if ((freqTower.size() >= nr - 1) && (nr > 1)) {
450         towerFreq = freqTower[nr - 2];
451     }
452     return towerFreq;
453 }
454
455 const std::string FGAirportDynamics::getAtisSequence()
456 {
457    if (atisSequenceIndex == -1) {
458        updateAtisSequence(1, false);
459    }
460    
461    return GetPhoneticLetter(atisSequenceIndex);
462 }
463
464 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
465 {
466     double now = globals->get_sim_time_sec();
467     if (atisSequenceIndex == -1) {
468         // first computation
469         atisSequenceTimeStamp = now;
470         atisSequenceIndex = rand() % LTRS; // random initial sequence letters
471         return atisSequenceIndex;
472     }
473
474     int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
475     atisSequenceTimeStamp += (interval * steps);
476     if (forceUpdate && (steps == 0)) {
477         ++steps; // a "special" ATIS update is required
478     } 
479     
480     atisSequenceIndex = (atisSequenceIndex + steps) % LTRS;
481     // return a huge value if no update occurred
482     return (atisSequenceIndex + (steps ? 0 : LTRS*1000));
483 }