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 <Main/locale.hxx>
42 #include <Airports/runways.hxx>
43 #include <Airports/groundnetwork.hxx>
44 #include <Navaids/NavDataCache.hxx>
46 #include "airport.hxx"
47 #include "dynamics.hxx"
52 using std::random_shuffle;
54 class ParkingAssignment::ParkingAssignmentPrivate
57 ParkingAssignmentPrivate(FGParking* pk, FGAirportDynamics* dyn) :
64 retain(); // initial count of 1
67 ~ParkingAssignmentPrivate()
69 dynamics->releaseParking(parking);
74 if ((--refCount) == 0) {
84 unsigned int refCount;
86 FGAirportDynamicsRef dynamics;
89 ParkingAssignment::ParkingAssignment() :
94 ParkingAssignment::~ParkingAssignment()
97 _sharedData->release();
101 ParkingAssignment::ParkingAssignment(FGParking* pk, FGAirportDynamics* dyn) :
105 _sharedData = new ParkingAssignmentPrivate(pk, dyn);
109 ParkingAssignment::ParkingAssignment(const ParkingAssignment& aOther) :
110 _sharedData(aOther._sharedData)
113 _sharedData->retain();
117 void ParkingAssignment::operator=(const ParkingAssignment& aOther)
119 if (_sharedData == aOther._sharedData) {
120 return; // self-assignment, special case
124 _sharedData->release();
127 _sharedData = aOther._sharedData;
129 _sharedData->retain();
133 void ParkingAssignment::release()
136 _sharedData->release();
141 bool ParkingAssignment::isValid() const
143 return (_sharedData != NULL);
146 FGParking* ParkingAssignment::parking() const
148 return _sharedData ? _sharedData->parking.ptr() : NULL;
151 ////////////////////////////////////////////////////////////////////////////////
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)
163 groundNetwork.reset(new FGGroundNetwork);
167 FGAirportDynamics::~FGAirportDynamics()
169 SG_LOG(SG_AI, SG_INFO, "destroyed dynamics for:" << _ap->ident());
173 // Initialization required after XMLRead
174 void FGAirportDynamics::init()
177 groundNetwork->init(this);
178 groundController.setTowerController(&towerController);
179 groundController.init(this);
182 FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
183 const string & airline,
184 bool skipEmptyAirlineCode)
186 const FGParkingList& parkings(groundNetwork->allParkings());
187 FGParkingList::const_iterator it;
188 for (it = parkings.begin(); it != parkings.end(); ++it) {
189 FGParkingRef parking = *it;
190 if (!isParkingAvailable(parking)) {
194 if (skipEmptyAirlineCode && parking->getCodes().empty()) {
198 if (!airline.empty() && !parking->getCodes().empty()) {
199 if (parking->getCodes().find(airline, 0) == string::npos) {
204 setParkingAvailable(parking, false);
211 ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
212 const string & acType,
213 const string & airline)
215 SG_UNUSED(acType); // sadly not used at the moment
217 // most exact seach - airline codes must be present and match
218 FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
220 return ParkingAssignment(result, this);
223 // more tolerant - gates with empty airline codes are permitted
224 result = innerGetAvailableParking(radius, flType, airline, false);
226 return ParkingAssignment(result, this);
229 // fallback - ignore the airline code entirely
230 result = innerGetAvailableParking(radius, flType, string(), false);
231 return result ? ParkingAssignment(result, this) : ParkingAssignment();
234 ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
236 const FGParkingList& parkings(groundNetwork->allParkings());
237 FGParkingList::const_iterator it;
238 for (it = parkings.begin(); it != parkings.end(); ++it) {
239 if ((*it)->name() == name) {
240 return ParkingAssignment(*it, const_cast<FGAirportDynamics*>(this));
244 return ParkingAssignment();
247 void FGAirportDynamics::setParkingAvailable(FGParking* park, bool available)
250 releaseParking(park);
252 occupiedParkings.insert(park);
256 bool FGAirportDynamics::isParkingAvailable(FGParking* parking) const
258 return (occupiedParkings.find(parking) == occupiedParkings.end());
261 void FGAirportDynamics::releaseParking(FGParking* id)
263 ParkingSet::iterator it = occupiedParkings.find(id);
264 if (it == occupiedParkings.end()) {
268 occupiedParkings.erase(it);
271 class GetParkingsPredicate
273 bool mustBeAvailable;
275 const FGAirportDynamics* dynamics;
277 GetParkingsPredicate(bool b, const std::string& ty, const FGAirportDynamics* dyn) :
283 bool operator()(const FGParkingRef& park) const
285 if (!type.empty() && (park->getType() != type))
288 if (mustBeAvailable && !dynamics->isParkingAvailable(park)) {
296 FGParkingList FGAirportDynamics::getParkings(bool onlyAvailable, const std::string &type) const
298 FGParkingList result(groundNetwork->allParkings());
300 GetParkingsPredicate pred(onlyAvailable, type, this);
301 FGParkingList::iterator it = std::remove_if(result.begin(), result.end(), pred);
302 result.erase(it, result.end());
306 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
311 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
312 int action, string & runway,
322 if (!rwyPrefs.available()) {
326 RunwayGroup *currRunwayGroup = 0;
327 int nrActiveRunways = 0;
328 time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
329 if ((std::abs((long) (dayStart - lastUpdate)) > 600)
330 || trafficType != prevTrafficType) {
333 lastUpdate = dayStart;
334 prevTrafficType = trafficType;
338 ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
339 ->getEnvironment(getLatitude(), getLongitude(),
342 windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
343 windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
344 //stationweather.get_wind_from_heading_deg();
346 //cerr << "finding active Runway for : " << _ap->getId() << endl;
347 //cerr << "Wind Heading : " << windHeading << endl;
348 //cerr << "Wind Speed : " << windSpeed << endl;
350 //cerr << "Nr of seconds since day start << " << dayStart << endl;
352 ScheduleTime *currSched;
353 //cerr << "A"<< endl;
354 currSched = rwyPrefs.getSchedule(trafficType.c_str());
357 //cerr << "B"<< endl;
358 scheduleName = currSched->getName(dayStart);
359 maxTail = currSched->getTailWind();
360 maxCross = currSched->getCrossWind();
361 //cerr << "Current Schedule = : " << scheduleName << endl;
362 if (scheduleName.empty())
364 //cerr << "C"<< endl;
365 currRunwayGroup = rwyPrefs.getGroup(scheduleName);
366 //cerr << "D"<< endl;
367 if (!(currRunwayGroup))
369 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
371 // Keep a history of the currently active runways, to ensure
372 // that an already established selection of runways will not
373 // be overridden once a more preferred selection becomes
374 // available as that can lead to random runway swapping.
375 if (trafficType == "com") {
376 currentlyActive = &comActive;
377 } else if (trafficType == "gen") {
378 currentlyActive = &genActive;
379 } else if (trafficType == "mil") {
380 currentlyActive = &milActive;
381 } else if (trafficType == "ul") {
382 currentlyActive = &ulActive;
385 //cerr << "Durrently active selection for " << trafficType << ": ";
386 for (stringVecIterator it = currentlyActive->begin();
387 it != currentlyActive->end(); it++) {
388 //cerr << (*it) << " ";
392 currRunwayGroup->setActive(_ap,
395 maxTail, maxCross, currentlyActive);
397 // Note that I SHOULD keep multiple lists in memory, one for
398 // general aviation, one for commercial and one for military
400 currentlyActive->clear();
401 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
402 //cerr << "Choosing runway for " << trafficType << endl;
403 for (int i = 0; i < nrActiveRunways; i++) {
404 type = "unknown"; // initialize to something other than landing or takeoff
405 currRunwayGroup->getActive(i, name, type);
406 if (type == "landing") {
407 landing.push_back(name);
408 currentlyActive->push_back(name);
409 //cerr << "Landing " << name << endl;
411 if (type == "takeoff") {
412 takeoff.push_back(name);
413 currentlyActive->push_back(name);
414 //cerr << "takeoff " << name << endl;
420 if (action == 1) // takeoff
422 int nr = takeoff.size();
424 // Note that the randomization below, is just a placeholder to choose between
425 // multiple active runways for this action. This should be
426 // under ATC control.
427 runway = chooseRwyByHeading(takeoff, heading);
429 runway = chooseRunwayFallback();
433 if (action == 2) // landing
435 int nr = landing.size();
437 runway = chooseRwyByHeading(landing, heading);
439 runway = chooseRunwayFallback();
446 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
449 double bestError = 360.0;
450 double rwyHeading, headingError;
452 for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
453 if (!_ap->hasRunwayWithIdent(*i)) {
454 SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
455 " not found at " << _ap->ident());
459 FGRunway *rwy = _ap->getRunwayByIdent((*i));
460 rwyHeading = rwy->headingDeg();
461 headingError = fabs(heading - rwyHeading);
462 if (headingError > 180)
463 headingError = fabs(headingError - 360);
464 if (headingError < bestError) {
466 bestError = headingError;
469 //cerr << "Using active runway " << runway << " for heading " << heading << endl;
473 void FGAirportDynamics::getActiveRunway(const string & trafficType,
474 int action, string & runway,
477 bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
479 runway = chooseRunwayFallback();
483 string FGAirportDynamics::chooseRunwayFallback()
485 FGRunway *rwy = _ap->getActiveRunwayForUsage();
489 double FGAirportDynamics::getElevation() const
491 return _ap->getElevation();
494 const string FGAirportDynamics::getId() const
499 // Experimental: Return a different ground frequency depending on the leg of the
500 // Flight. Leg should always have a minimum value of two when this function is called.
501 // Note that in this scheme, the assignment of various frequencies to various ground
502 // operations is completely arbitrary. As such, is a short cut I need to take now,
503 // so that at least I can start working on assigning different frequencies to different
506 int FGAirportDynamics::getGroundFrequency(unsigned leg)
508 //return freqGround.size() ? freqGround[0] : 0; };
509 //cerr << "Getting frequency for : " << leg << endl;
512 SG_LOG(SG_ATC, SG_ALERT,
513 "Leg value is smaller than one at " << SG_ORIGIN);
515 if (freqGround.size() == 0) {
519 if ((freqGround.size() < leg) && (leg > 0)) {
521 (freqGround.size() <=
522 (leg - 1)) ? freqGround[freqGround.size() -
523 1] : freqGround[leg - 1];
525 if ((freqGround.size() >= leg) && (leg > 0)) {
526 groundFreq = freqGround[leg - 1];
531 int FGAirportDynamics::getTowerFrequency(unsigned nr)
535 SG_LOG(SG_ATC, SG_ALERT,
536 "Leg value is smaller than two at " << SG_ORIGIN);
538 if (freqTower.size() == 0) {
541 if ((freqTower.size() > nr - 1) && (nr > 1)) {
542 towerFreq = freqTower[nr - 1];
544 if ((freqTower.size() < nr - 1) && (nr > 1)) {
547 (nr - 1)) ? freqTower[freqTower.size() -
548 1] : freqTower[nr - 2];
550 if ((freqTower.size() >= nr - 1) && (nr > 1)) {
551 towerFreq = freqTower[nr - 2];
556 const std::string FGAirportDynamics::getAtisSequence()
558 if (atisSequenceIndex == -1) {
559 updateAtisSequence(1, false);
562 char atisSequenceString[2];
563 atisSequenceString[0] = 'a' + atisSequenceIndex;
564 atisSequenceString[1] = 0;
566 return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
569 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
571 double now = globals->get_sim_time_sec();
572 if (atisSequenceIndex == -1) {
574 atisSequenceTimeStamp = now;
575 atisSequenceIndex = rand() % 26; // random initial sequence letters
576 return atisSequenceIndex;
579 int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
580 atisSequenceTimeStamp += (interval * steps);
581 if (forceUpdate && (steps == 0)) {
582 ++steps; // a "special" ATIS update is required
585 atisSequenceIndex = (atisSequenceIndex + steps) % 26;
586 // return a huge value if no update occurred
587 return (atisSequenceIndex + (steps ? 0 : 26*1000));