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)
166 FGAirportDynamics::~FGAirportDynamics()
168 SG_LOG(SG_AI, SG_INFO, "destroyed dynamics for:" << _ap->ident());
172 // Initialization required after XMLRead
173 void FGAirportDynamics::init()
175 groundController.setTowerController(&towerController);
176 groundController.init(this);
179 FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
180 const string & airline,
181 bool skipEmptyAirlineCode)
183 const FGParkingList& parkings(getGroundNetwork()->allParkings());
184 FGParkingList::const_iterator it;
185 for (it = parkings.begin(); it != parkings.end(); ++it) {
186 FGParkingRef parking = *it;
187 if (!isParkingAvailable(parking)) {
191 if (skipEmptyAirlineCode && parking->getCodes().empty()) {
195 if (!airline.empty() && !parking->getCodes().empty()) {
196 if (parking->getCodes().find(airline, 0) == string::npos) {
201 setParkingAvailable(parking, false);
208 bool FGAirportDynamics::hasParkings() const
210 return !getGroundNetwork()->allParkings().empty();
213 ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
214 const string & acType,
215 const string & airline)
217 SG_UNUSED(acType); // sadly not used at the moment
219 // most exact seach - airline codes must be present and match
220 FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
222 return ParkingAssignment(result, this);
225 // more tolerant - gates with empty airline codes are permitted
226 result = innerGetAvailableParking(radius, flType, airline, false);
228 return ParkingAssignment(result, this);
231 // fallback - ignore the airline code entirely
232 result = innerGetAvailableParking(radius, flType, string(), false);
233 return result ? ParkingAssignment(result, this) : ParkingAssignment();
236 ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
238 const FGParkingList& parkings(getGroundNetwork()->allParkings());
239 FGParkingList::const_iterator it;
240 for (it = parkings.begin(); it != parkings.end(); ++it) {
241 if ((*it)->name() == name) {
242 return ParkingAssignment(*it, const_cast<FGAirportDynamics*>(this));
246 return ParkingAssignment();
249 FGGroundNetwork *FGAirportDynamics::getGroundNetwork() const
251 return _ap->groundNetwork();
254 void FGAirportDynamics::setParkingAvailable(FGParking* park, bool available)
257 releaseParking(park);
259 occupiedParkings.insert(park);
263 bool FGAirportDynamics::isParkingAvailable(FGParking* parking) const
265 return (occupiedParkings.find(parking) == occupiedParkings.end());
268 void FGAirportDynamics::releaseParking(FGParking* id)
270 ParkingSet::iterator it = occupiedParkings.find(id);
271 if (it == occupiedParkings.end()) {
275 occupiedParkings.erase(it);
278 class GetParkingsPredicate
280 bool mustBeAvailable;
282 const FGAirportDynamics* dynamics;
284 GetParkingsPredicate(bool b, const std::string& ty, const FGAirportDynamics* dyn) :
290 bool operator()(const FGParkingRef& park) const
292 if (!type.empty() && (park->getType() != type))
295 if (mustBeAvailable && !dynamics->isParkingAvailable(park)) {
303 FGParkingList FGAirportDynamics::getParkings(bool onlyAvailable, const std::string &type) const
305 FGParkingList result(getGroundNetwork()->allParkings());
307 GetParkingsPredicate pred(onlyAvailable, type, this);
308 FGParkingList::iterator it = std::remove_if(result.begin(), result.end(), pred);
309 result.erase(it, result.end());
313 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
318 bool areRunwaysParallel(const FGRunwayRef& a, const FGRunwayRef& b)
320 double hdgDiff = (b->headingDeg() - a->headingDeg());
321 SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0);
322 return (fabs(hdgDiff) < 5.0);
325 double runwayScore(const FGRunwayRef& rwy)
327 return rwy->lengthM() + rwy->widthM();
330 double runwayWindScore(const FGRunwayRef& runway, double windHeading,
333 double hdgDiff = fabs(windHeading - runway->headingDeg()) * SG_DEGREES_TO_RADIANS;
334 SGMiscd::normalizeAngle(hdgDiff);
336 double crossWind = windSpeedKts * sin(hdgDiff);
337 double tailWind = -windSpeedKts * cos(hdgDiff);
339 return -(crossWind + tailWind);
342 typedef std::vector<FGRunwayRef> RunwayVec;
345 class FallbackRunwayGroup
348 FallbackRunwayGroup(const FGRunwayRef& rwy) :
351 runways.push_back(rwy);
352 _leadRunwayScore = runwayScore(rwy);
353 _groupScore += _leadRunwayScore;
356 bool canAccept(const FGRunwayRef& rwy) const
358 if (!areRunwaysParallel(runways.front(), rwy)) {
365 void addRunway(const FGRunwayRef& rwy)
367 assert(areRunwaysParallel(runways.front(), rwy));
368 double score = runwayScore(rwy);
369 if (score < (0.5 * _leadRunwayScore)) {
370 // drop the runway completely. this is to drop short parallel
371 // runways from being active
375 runways.push_back(rwy);
376 _groupScore += score;
379 void adjustScoreForWind(double windHeading, double windSpeedKts)
381 _basicScore = _groupScore;
382 RunwayVec::iterator it;
383 for (it = runways.begin(); it != runways.end(); ++it) {
384 _groupScore += runwayWindScore(*it, windHeading, windSpeedKts);
388 double groupScore() const
393 void getRunways(FGRunwayList& arrivals, FGRunwayList& departures)
395 // make the common cases very obvious
396 if (runways.size() == 1) {
397 arrivals.push_back(runways.front());
398 departures.push_back(runways.front());
403 // becuase runways were sorted by score when building, they were added
404 // by score also, so we can use a simple algorithim to assign
405 for (unsigned int r=0; r < runways.size(); ++r) {
407 arrivals.push_back(runways[r]);
409 departures.push_back(runways[r]);
417 os << runways.front()->ident();
418 for (unsigned int r=1; r <runways.size(); ++r) {
419 os << ", " << runways[r]->ident();
422 os << " (score=" << _basicScore << ", wind score=" << _groupScore << ")";
434 class WindExclusionCheck
437 WindExclusionCheck(double windHeading, double windSpeedKts) :
438 _windSpeedKts(windSpeedKts),
439 _windHeading(windHeading)
442 bool operator()(const FGRunwayRef& rwy) const
444 return (runwayWindScore(rwy, _windHeading, _windSpeedKts) > 30);
447 double _windSpeedKts,
454 bool operator()(const FGRunwayRef& a, const FGRunwayRef& b) const
456 return runwayScore(a) > runwayScore(b);
460 class GroupSortByScore
463 bool operator()(const FallbackRunwayGroup& a, const FallbackRunwayGroup& b) const
465 return a.groupScore() > b.groupScore();
469 string FGAirportDynamics::fallbackGetActiveRunway(int action, double heading)
471 bool updateNeeded = false;
472 if (_lastFallbackUpdate == SGTimeStamp()) {
475 updateNeeded = (_lastFallbackUpdate.elapsedMSec() > (1000 * 60 * 15));
479 double windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt");
480 double windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
482 // discount runways based on cross / tail-wind
483 WindExclusionCheck windCheck(windHeading, windSpeed);
484 RunwayVec runways(parent()->getRunways());
485 RunwayVec::iterator it = std::remove_if(runways.begin(), runways.end(),
487 runways.erase(it, runways.end());
489 // sort highest scored to lowest scored
490 std::sort(runways.begin(), runways.end(), SortByScore());
492 std::vector<FallbackRunwayGroup> groups;
493 std::vector<FallbackRunwayGroup>::iterator git;
495 for (it = runways.begin(); it != runways.end(); ++it) {
496 bool existingGroupDidAccept = false;
497 for (git = groups.begin(); git != groups.end(); ++git) {
498 if (git->canAccept(*it)) {
499 existingGroupDidAccept = true;
503 } // of existing groups iteration
505 if (!existingGroupDidAccept) {
506 // create a new group
507 groups.push_back(FallbackRunwayGroup(*it));
509 } // of group building phase
512 // select highest scored group based on cross/tail wind
513 for (git = groups.begin(); git != groups.end(); ++git) {
514 git->adjustScoreForWind(windHeading, windSpeed);
517 std::sort(groups.begin(), groups.end(), GroupSortByScore());
521 os << parent()->ident() << " groups:";
523 for (git = groups.begin(); git != groups.end(); ++git) {
524 os << "\n\t" << git->dump();
527 std::string s = os.str();
528 SG_LOG(SG_AI, SG_INFO, s);
531 // assign takeoff and landing runways
532 FallbackRunwayGroup bestGroup = groups.front();
534 _lastFallbackUpdate.stamp();
535 _fallbackRunwayCounter = 0;
536 _fallbackDepartureRunways.clear();
537 _fallbackArrivalRunways.clear();
538 bestGroup.getRunways(_fallbackArrivalRunways, _fallbackDepartureRunways);
541 os << "\tArrival:" << _fallbackArrivalRunways.front()->ident();
542 for (unsigned int r=1; r <_fallbackArrivalRunways.size(); ++r) {
543 os << ", " << _fallbackArrivalRunways[r]->ident();
545 os << "\n\tDeparture:" << _fallbackDepartureRunways.front()->ident();
546 for (unsigned int r=1; r <_fallbackDepartureRunways.size(); ++r) {
547 os << ", " << _fallbackDepartureRunways[r]->ident();
550 std::string s = os.str();
551 SG_LOG(SG_AI, SG_INFO, parent()->ident() << " fallback runways assignments for "
552 << static_cast<int>(windHeading) << "@" << static_cast<int>(windSpeed) << "\n" << s);
555 _fallbackRunwayCounter++;
557 // ensure we cycle through possible runways where they exist
559 r = _fallbackDepartureRunways[_fallbackRunwayCounter % _fallbackDepartureRunways.size()];
561 r = _fallbackArrivalRunways[_fallbackRunwayCounter % _fallbackArrivalRunways.size()];
567 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
568 int action, string & runway,
578 if (!rwyPrefs.available()) {
579 runway = fallbackGetActiveRunway(action, heading);
583 RunwayGroup *currRunwayGroup = 0;
584 int nrActiveRunways = 0;
585 time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
586 if ((std::abs((long) (dayStart - lastUpdate)) > 600)
587 || trafficType != prevTrafficType) {
590 lastUpdate = dayStart;
591 prevTrafficType = trafficType;
595 ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
596 ->getEnvironment(getLatitude(), getLongitude(),
599 windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
600 windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
601 //stationweather.get_wind_from_heading_deg();
603 //cerr << "finding active Runway for : " << _ap->getId() << endl;
604 //cerr << "Wind Heading : " << windHeading << endl;
605 //cerr << "Wind Speed : " << windSpeed << endl;
607 //cerr << "Nr of seconds since day start << " << dayStart << endl;
609 ScheduleTime *currSched;
610 //cerr << "A"<< endl;
611 currSched = rwyPrefs.getSchedule(trafficType.c_str());
614 //cerr << "B"<< endl;
615 scheduleName = currSched->getName(dayStart);
616 maxTail = currSched->getTailWind();
617 maxCross = currSched->getCrossWind();
618 //cerr << "Current Schedule = : " << scheduleName << endl;
619 if (scheduleName.empty())
621 //cerr << "C"<< endl;
622 currRunwayGroup = rwyPrefs.getGroup(scheduleName);
623 //cerr << "D"<< endl;
624 if (!(currRunwayGroup))
626 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
628 // Keep a history of the currently active runways, to ensure
629 // that an already established selection of runways will not
630 // be overridden once a more preferred selection becomes
631 // available as that can lead to random runway swapping.
632 if (trafficType == "com") {
633 currentlyActive = &comActive;
634 } else if (trafficType == "gen") {
635 currentlyActive = &genActive;
636 } else if (trafficType == "mil") {
637 currentlyActive = &milActive;
638 } else if (trafficType == "ul") {
639 currentlyActive = &ulActive;
642 //cerr << "Durrently active selection for " << trafficType << ": ";
643 for (stringVecIterator it = currentlyActive->begin();
644 it != currentlyActive->end(); it++) {
645 //cerr << (*it) << " ";
649 currRunwayGroup->setActive(_ap,
652 maxTail, maxCross, currentlyActive);
654 // Note that I SHOULD keep multiple lists in memory, one for
655 // general aviation, one for commercial and one for military
657 currentlyActive->clear();
658 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
659 //cerr << "Choosing runway for " << trafficType << endl;
660 for (int i = 0; i < nrActiveRunways; i++) {
661 type = "unknown"; // initialize to something other than landing or takeoff
662 currRunwayGroup->getActive(i, name, type);
663 if (type == "landing") {
664 landing.push_back(name);
665 currentlyActive->push_back(name);
666 //cerr << "Landing " << name << endl;
668 if (type == "takeoff") {
669 takeoff.push_back(name);
670 currentlyActive->push_back(name);
671 //cerr << "takeoff " << name << endl;
677 if (action == 1) // takeoff
679 int nr = takeoff.size();
681 // Note that the randomization below, is just a placeholder to choose between
682 // multiple active runways for this action. This should be
683 // under ATC control.
684 runway = chooseRwyByHeading(takeoff, heading);
686 runway = chooseRunwayFallback();
690 if (action == 2) // landing
692 int nr = landing.size();
694 runway = chooseRwyByHeading(landing, heading);
696 runway = chooseRunwayFallback();
703 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
706 double bestError = 360.0;
707 double rwyHeading, headingError;
709 for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
710 if (!_ap->hasRunwayWithIdent(*i)) {
711 SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
712 " not found at " << _ap->ident());
716 FGRunway *rwy = _ap->getRunwayByIdent((*i));
717 rwyHeading = rwy->headingDeg();
718 headingError = fabs(heading - rwyHeading);
719 if (headingError > 180)
720 headingError = fabs(headingError - 360);
721 if (headingError < bestError) {
723 bestError = headingError;
726 //cerr << "Using active runway " << runway << " for heading " << heading << endl;
730 void FGAirportDynamics::getActiveRunway(const string & trafficType,
731 int action, string & runway,
734 bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
736 runway = chooseRunwayFallback();
740 string FGAirportDynamics::chooseRunwayFallback()
742 FGRunway *rwy = _ap->getActiveRunwayForUsage();
746 double FGAirportDynamics::getElevation() const
748 return _ap->getElevation();
751 const string FGAirportDynamics::getId() const
756 // Experimental: Return a different ground frequency depending on the leg of the
757 // Flight. Leg should always have a minimum value of two when this function is called.
758 // Note that in this scheme, the assignment of various frequencies to various ground
759 // operations is completely arbitrary. As such, is a short cut I need to take now,
760 // so that at least I can start working on assigning different frequencies to different
763 int FGAirportDynamics::getGroundFrequency(unsigned leg)
765 //return freqGround.size() ? freqGround[0] : 0; };
766 //cerr << "Getting frequency for : " << leg << endl;
769 SG_LOG(SG_ATC, SG_ALERT,
770 "Leg value is smaller than one at " << SG_ORIGIN);
773 const intVec& freqGround(getGroundNetwork()->getGroundFrequencies());
775 if (freqGround.size() == 0) {
779 if ((freqGround.size() < leg) && (leg > 0)) {
781 (freqGround.size() <=
782 (leg - 1)) ? freqGround[freqGround.size() -
783 1] : freqGround[leg - 1];
785 if ((freqGround.size() >= leg) && (leg > 0)) {
786 groundFreq = freqGround[leg - 1];
791 int FGAirportDynamics::getTowerFrequency(unsigned nr)
795 SG_LOG(SG_ATC, SG_ALERT,
796 "Leg value is smaller than two at " << SG_ORIGIN);
799 const intVec& freqTower(getGroundNetwork()->getTowerFrequencies());
801 if (freqTower.size() == 0) {
804 if ((freqTower.size() > nr - 1) && (nr > 1)) {
805 towerFreq = freqTower[nr - 1];
807 if ((freqTower.size() < nr - 1) && (nr > 1)) {
810 (nr - 1)) ? freqTower[freqTower.size() -
811 1] : freqTower[nr - 2];
813 if ((freqTower.size() >= nr - 1) && (nr > 1)) {
814 towerFreq = freqTower[nr - 2];
819 const std::string FGAirportDynamics::getAtisSequence()
821 if (atisSequenceIndex == -1) {
822 updateAtisSequence(1, false);
825 char atisSequenceString[2];
826 atisSequenceString[0] = 'a' + atisSequenceIndex;
827 atisSequenceString[1] = 0;
829 return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
832 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
834 double now = globals->get_sim_time_sec();
835 if (atisSequenceIndex == -1) {
837 atisSequenceTimeStamp = now;
838 atisSequenceIndex = rand() % 26; // random initial sequence letters
839 return atisSequenceIndex;
842 int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
843 atisSequenceTimeStamp += (interval * steps);
844 if (forceUpdate && (steps == 0)) {
845 ++steps; // a "special" ATIS update is required
848 atisSequenceIndex = (atisSequenceIndex + steps) % 26;
849 // return a huge value if no update occurred
850 return (atisSequenceIndex + (steps ? 0 : 26*1000));