diff --git a/src/Airports/dynamics.cxx b/src/Airports/dynamics.cxx index c90d91c35..59eda8409 100644 --- a/src/Airports/dynamics.cxx +++ b/src/Airports/dynamics.cxx @@ -313,6 +313,255 @@ void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref) rwyPrefs = ref; } +bool areRunwaysParallel(const FGRunwayRef& a, const FGRunwayRef& b) +{ + double hdgDiff = (b->headingDeg() - a->headingDeg()); + SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0); + return (fabs(hdgDiff) < 5.0); +} + +double runwayScore(const FGRunwayRef& rwy) +{ + return rwy->lengthM() + rwy->widthM(); +} + +double runwayWindScore(const FGRunwayRef& runway, double windHeading, + double windSpeedKts) +{ + double hdgDiff = fabs(windHeading - runway->headingDeg()) * SG_DEGREES_TO_RADIANS; + SGMiscd::normalizeAngle(hdgDiff); + + double crossWind = windSpeedKts * sin(hdgDiff); + double tailWind = -windSpeedKts * cos(hdgDiff); + + return -(crossWind + tailWind); +} + +typedef std::vector<FGRunwayRef> RunwayVec; + + +class FallbackRunwayGroup +{ +public: + FallbackRunwayGroup(const FGRunwayRef& rwy) : + _groupScore(0.0) + { + runways.push_back(rwy); + _leadRunwayScore = runwayScore(rwy); + _groupScore += _leadRunwayScore; + } + + bool canAccept(const FGRunwayRef& rwy) const + { + if (!areRunwaysParallel(runways.front(), rwy)) { + return false; + } + + return true; + } + + void addRunway(const FGRunwayRef& rwy) + { + assert(areRunwaysParallel(runways.front(), rwy)); + double score = runwayScore(rwy); + if (score < (0.5 * _leadRunwayScore)) { + // drop the runway completely. this is to drop short parallel + // runways from being active + return; + } + + runways.push_back(rwy); + _groupScore += score; + } + + void adjustScoreForWind(double windHeading, double windSpeedKts) + { + _basicScore = _groupScore; + RunwayVec::iterator it; + for (it = runways.begin(); it != runways.end(); ++it) { + _groupScore += runwayWindScore(*it, windHeading, windSpeedKts); + } + } + + double groupScore() const + { + return _groupScore; + } + + void getRunways(FGRunwayList& arrivals, FGRunwayList& departures) + { + // make the common cases very obvious + if (runways.size() == 1) { + arrivals.push_back(runways.front()); + departures.push_back(runways.front()); + return; + } + + + // becuase runways were sorted by score when building, they were added + // by score also, so we can use a simple algorithim to assign + for (int r=0; r < runways.size(); ++r) { + if ((r % 2) == 0) { + arrivals.push_back(runways[r]); + } else { + departures.push_back(runways[r]); + } + } + } + + std::string dump() + { + ostringstream os; + os << runways.front()->ident(); + for (int r=1; r <runways.size(); ++r) { + os << ", " << runways[r]->ident(); + } + + os << " (score=" << _basicScore << ", wind score=" << _groupScore << ")"; + return os.str(); + } + +private: + RunwayVec runways; + double _groupScore, + _leadRunwayScore; + double _basicScore; + +}; + +class WindExclusionCheck +{ +public: + WindExclusionCheck(double windHeading, double windSpeedKts) : + _windSpeedKts(windSpeedKts), + _windHeading(windHeading) + {} + + bool operator()(const FGRunwayRef& rwy) const + { + return (runwayWindScore(rwy, _windHeading, _windSpeedKts) > 30); + } +private: + double _windSpeedKts, + _windHeading; +}; + +class SortByScore +{ +public: + bool operator()(const FGRunwayRef& a, const FGRunwayRef& b) const + { + return runwayScore(a) > runwayScore(b); + } +}; + +class GroupSortByScore +{ +public: + bool operator()(const FallbackRunwayGroup& a, const FallbackRunwayGroup& b) const + { + return a.groupScore() > b.groupScore(); + } +}; + +string FGAirportDynamics::fallbackGetActiveRunway(int action, double heading) +{ + bool updateNeeded = false; + if (_lastFallbackUpdate == SGTimeStamp()) { + updateNeeded = true; + } else { + updateNeeded = (_lastFallbackUpdate.elapsedMSec() > (1000 * 60 * 15)); + } + + if (updateNeeded) { + double windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt"); + double windHeading = fgGetInt("/environment/metar/base-wind-dir-deg"); + + // discount runways based on cross / tail-wind + WindExclusionCheck windCheck(windHeading, windSpeed); + RunwayVec runways(parent()->getRunways()); + RunwayVec::iterator it = std::remove_if(runways.begin(), runways.end(), + windCheck); + runways.erase(it, runways.end()); + + // sort highest scored to lowest scored + std::sort(runways.begin(), runways.end(), SortByScore()); + + std::vector<FallbackRunwayGroup> groups; + std::vector<FallbackRunwayGroup>::iterator git; + + for (it = runways.begin(); it != runways.end(); ++it) { + bool existingGroupDidAccept = false; + for (git = groups.begin(); git != groups.end(); ++git) { + if (git->canAccept(*it)) { + existingGroupDidAccept = true; + git->addRunway(*it); + break; + } + } // of existing groups iteration + + if (!existingGroupDidAccept) { + // create a new group + groups.push_back(FallbackRunwayGroup(*it)); + } + } // of group building phase + + + // select highest scored group based on cross/tail wind + for (git = groups.begin(); git != groups.end(); ++git) { + git->adjustScoreForWind(windHeading, windSpeed); + } + + std::sort(groups.begin(), groups.end(), GroupSortByScore()); + + { + ostringstream os; + os << parent()->ident() << " groups:"; + + for (git = groups.begin(); git != groups.end(); ++git) { + os << "\n\t" << git->dump(); + } + + std::string s = os.str(); + SG_LOG(SG_AI, SG_INFO, s); + } + + // assign takeoff and landing runways + FallbackRunwayGroup bestGroup = groups.front(); + + _lastFallbackUpdate.stamp(); + _fallbackRunwayCounter = 0; + _fallbackDepartureRunways.clear(); + _fallbackArrivalRunways.clear(); + bestGroup.getRunways(_fallbackArrivalRunways, _fallbackDepartureRunways); + + ostringstream os; + os << "\tArrival:" << _fallbackArrivalRunways.front()->ident(); + for (int r=1; r <_fallbackArrivalRunways.size(); ++r) { + os << ", " << _fallbackArrivalRunways[r]->ident(); + } + os << "\n\tDeparture:" << _fallbackDepartureRunways.front()->ident(); + for (int r=1; r <_fallbackDepartureRunways.size(); ++r) { + os << ", " << _fallbackDepartureRunways[r]->ident(); + } + + std::string s = os.str(); + SG_LOG(SG_AI, SG_INFO, parent()->ident() << " fallback runways assignments for " + << static_cast<int>(windHeading) << "@" << static_cast<int>(windSpeed) << "\n" << s); + } + + _fallbackRunwayCounter++; + FGRunwayRef r; + // ensure we cycle through possible runways where they exist + if (action == 1) { + r = _fallbackDepartureRunways[_fallbackRunwayCounter % _fallbackDepartureRunways.size()]; + } else { + r = _fallbackArrivalRunways[_fallbackRunwayCounter % _fallbackArrivalRunways.size()]; + } + + return r->ident(); +} + bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType, int action, string & runway, double heading) @@ -325,7 +574,8 @@ bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType, string type; if (!rwyPrefs.available()) { - return false; + runway = fallbackGetActiveRunway(action, heading); + return true; } RunwayGroup *currRunwayGroup = 0; diff --git a/src/Airports/dynamics.hxx b/src/Airports/dynamics.hxx index 4c7fbd625..c295ecd31 100644 --- a/src/Airports/dynamics.hxx +++ b/src/Airports/dynamics.hxx @@ -25,6 +25,7 @@ #include <set> #include <simgear/structure/SGReferenced.hxx> +#include <simgear/timing/timestamp.hxx> #include <ATC/trafficcontrol.hxx> #include <ATC/GroundController.hxx> @@ -98,6 +99,15 @@ private: FGParking* innerGetAvailableParking(double radius, const std::string & flType, const std::string & airline, bool skipEmptyAirlineCode); + + std::string fallbackGetActiveRunway(int action, double heading); + + // runway preference fallback data + SGTimeStamp _lastFallbackUpdate; + FGRunwayList _fallbackDepartureRunways, + _fallbackArrivalRunways; + unsigned int _fallbackRunwayCounter; + public: FGAirportDynamics(FGAirport* ap); virtual ~FGAirportDynamics();