1 // dynamics.cxx - Code to manage the higher order airport ground activities
2 // Written by Durk Talsma, started December 2004.
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.
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.
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.
29 #include <boost/foreach.hpp>
31 #include <simgear/compiler.h>
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>
45 #include "dynamics.hxx"
50 using std::random_shuffle;
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)
65 FGAirportDynamics::~FGAirportDynamics()
70 // Initialization required after XMLRead
71 void FGAirportDynamics::init()
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);
85 groundNetwork.setTowerController(&towerController);
89 int FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
90 const string & acType,
91 const string & airline,
92 bool skipEmptyAirlineCode)
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()) {
101 if (skipEmptyAirlineCode && i->getCodes().empty()) {
105 // check airline codes match
106 if (!airline.empty() && !i->getCodes().empty()) {
107 if (i->getCodes().find(airline, 0) == string::npos) {
112 // Type doesn't match
113 if (i->getType() != flType) {
117 if (i->getRadius() < radius) {
121 i->setAvailable(false);
122 return i->getIndex();
128 int FGAirportDynamics::getAvailableParking(double radius, const string & flType,
129 const string & acType,
130 const string & airline)
132 if (parkings.empty()) {
136 // most exact seach - airline codes must be present and match
137 int result = innerGetAvailableParking(radius, flType, acType, airline, true);
142 // more tolerant - gates with empty airline codes are permitted
143 result = innerGetAvailableParking(radius, flType, acType, airline, false);
148 // fallback - ignore the airline code entirely
149 return innerGetAvailableParking(radius, flType, acType, string(), false);
152 FGParking *FGAirportDynamics::getParking(int id)
154 FGParkingVecIterator i = parkings.begin();
155 for (i = parkings.begin(); i != parkings.end(); i++) {
156 if (id == i->getIndex()) {
163 string FGAirportDynamics::getParkingName(int id)
165 FGParkingVecIterator i = parkings.begin();
166 for (i = parkings.begin(); i != parkings.end(); i++) {
167 if (id == i->getIndex()) {
172 return string("overflow");
175 int FGAirportDynamics::findParkingByName(const std::string& name) const
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();
187 void FGAirportDynamics::releaseParking(int id)
191 FGParkingVecIterator i = parkings.begin();
192 for (i = parkings.begin(); i != parkings.end(); i++) {
193 if (id == i->getIndex()) {
194 i->setAvailable(true);
200 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
205 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
206 int action, string & runway,
216 if (!rwyPrefs.available()) {
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) {
227 lastUpdate = dayStart;
228 prevTrafficType = trafficType;
232 ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
233 ->getEnvironment(getLatitude(), getLongitude(),
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();
240 //cerr << "finding active Runway for : " << _ap->getId() << endl;
241 //cerr << "Wind Heading : " << windHeading << endl;
242 //cerr << "Wind Speed : " << windSpeed << endl;
244 //cerr << "Nr of seconds since day start << " << dayStart << endl;
246 ScheduleTime *currSched;
247 //cerr << "A"<< endl;
248 currSched = rwyPrefs.getSchedule(trafficType.c_str());
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())
258 //cerr << "C"<< endl;
259 currRunwayGroup = rwyPrefs.getGroup(scheduleName);
260 //cerr << "D"<< endl;
261 if (!(currRunwayGroup))
263 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
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;
279 //cerr << "Durrently active selection for " << trafficType << ": ";
280 for (stringVecIterator it = currentlyActive->begin();
281 it != currentlyActive->end(); it++) {
282 //cerr << (*it) << " ";
286 currRunwayGroup->setActive(_ap,
289 maxTail, maxCross, currentlyActive);
291 // Note that I SHOULD keep multiple lists in memory, one for
292 // general aviation, one for commercial and one for military
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;
305 if (type == "takeoff") {
306 takeoff.push_back(name);
307 currentlyActive->push_back(name);
308 //cerr << "takeoff " << name << endl;
314 if (action == 1) // takeoff
316 int nr = takeoff.size();
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);
323 runway = chooseRunwayFallback();
327 if (action == 2) // landing
329 int nr = landing.size();
331 runway = chooseRwyByHeading(landing, heading);
333 runway = chooseRunwayFallback();
340 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
343 double bestError = 360.0;
344 double rwyHeading, headingError;
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());
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) {
360 bestError = headingError;
363 //cerr << "Using active runway " << runway << " for heading " << heading << endl;
367 void FGAirportDynamics::getActiveRunway(const string & trafficType,
368 int action, string & runway,
371 bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
373 runway = chooseRunwayFallback();
377 string FGAirportDynamics::chooseRunwayFallback()
379 FGRunway *rwy = _ap->getActiveRunwayForUsage();
383 void FGAirportDynamics::addParking(FGParking & park)
385 parkings.push_back(park);
388 double FGAirportDynamics::getElevation() const
390 return _ap->getElevation();
393 const string FGAirportDynamics::getId() const
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
405 int FGAirportDynamics::getGroundFrequency(unsigned leg)
407 //return freqGround.size() ? freqGround[0] : 0; };
408 //cerr << "Getting frequency for : " << leg << endl;
411 SG_LOG(SG_ATC, SG_ALERT,
412 "Leg value is smaller than one at " << SG_ORIGIN);
414 if (freqGround.size() == 0) {
418 if ((freqGround.size() < leg) && (leg > 0)) {
420 (freqGround.size() <=
421 (leg - 1)) ? freqGround[freqGround.size() -
422 1] : freqGround[leg - 1];
424 if ((freqGround.size() >= leg) && (leg > 0)) {
425 groundFreq = freqGround[leg - 1];
430 int FGAirportDynamics::getTowerFrequency(unsigned nr)
434 SG_LOG(SG_ATC, SG_ALERT,
435 "Leg value is smaller than two at " << SG_ORIGIN);
437 if (freqTower.size() == 0) {
440 if ((freqTower.size() > nr - 1) && (nr > 1)) {
441 towerFreq = freqTower[nr - 1];
443 if ((freqTower.size() < nr - 1) && (nr > 1)) {
446 (nr - 1)) ? freqTower[freqTower.size() -
447 1] : freqTower[nr - 2];
449 if ((freqTower.size() >= nr - 1) && (nr > 1)) {
450 towerFreq = freqTower[nr - 2];
455 const std::string FGAirportDynamics::getAtisSequence()
457 if (atisSequenceIndex == -1) {
458 updateAtisSequence(1, false);
461 return GetPhoneticLetter(atisSequenceIndex);
464 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
466 double now = globals->get_sim_time_sec();
467 if (atisSequenceIndex == -1) {
469 atisSequenceTimeStamp = now;
470 atisSequenceIndex = rand() % LTRS; // random initial sequence letters
471 return atisSequenceIndex;
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
480 atisSequenceIndex = (atisSequenceIndex + steps) % LTRS;
481 // return a huge value if no update occurred
482 return (atisSequenceIndex + (steps ? 0 : LTRS*1000));