f4f261a068
AirportDyanmics::innerGetActiveRunway can unfortunately return an empty string even when it return ‘true’. Check for this in the wrapper, and switch to the fallback, to avoid returning a bogus runway to other parts of the code. Sentry-Id: FLIGHTGEAR-39
970 lines
28 KiB
C++
970 lines
28 KiB
C++
// dynamics.cxx - Code to manage the higher order airport ground activities
|
|
// Written by Durk Talsma, started December 2004.
|
|
//
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
//
|
|
// $Id$
|
|
|
|
#include <config.h>
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <simgear/compiler.h>
|
|
#include <simgear/structure/SGWeakPtr.hxx>
|
|
|
|
#include <Environment/environment_mgr.hxx>
|
|
#include <Environment/environment.hxx>
|
|
#include <simgear/misc/sg_path.hxx>
|
|
#include <simgear/props/props.hxx>
|
|
#include <simgear/structure/subsystem_mgr.hxx>
|
|
#include <simgear/debug/logstream.hxx>
|
|
#include <Main/globals.hxx>
|
|
#include <Main/fg_props.hxx>
|
|
#include <Main/locale.hxx>
|
|
#include <Airports/runways.hxx>
|
|
#include <Airports/groundnetwork.hxx>
|
|
#include <Navaids/NavDataCache.hxx>
|
|
#include <AIModel/AIManager.hxx>
|
|
#include <AIModel/AIBase.hxx>
|
|
|
|
#include "airport.hxx"
|
|
#include "dynamics.hxx"
|
|
|
|
using std::string;
|
|
using std::vector;
|
|
using std::sort;
|
|
|
|
// #define RUNWAY_FALLBACK_DEBUG
|
|
|
|
class ParkingAssignment::ParkingAssignmentPrivate
|
|
{
|
|
public:
|
|
ParkingAssignmentPrivate(FGParking* pk, FGAirportDynamics* dyn) :
|
|
refCount(0),
|
|
parking(pk),
|
|
dynamics(dyn)
|
|
{
|
|
assert(pk);
|
|
assert(dyn);
|
|
retain(); // initial count of 1
|
|
}
|
|
|
|
~ParkingAssignmentPrivate()
|
|
{
|
|
FGAirportDynamicsRef strongRef = dynamics.lock();
|
|
if (strongRef) {
|
|
strongRef->releaseParking(parking);
|
|
}
|
|
}
|
|
|
|
void release()
|
|
{
|
|
if ((--refCount) == 0) {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void retain()
|
|
{
|
|
++refCount;
|
|
}
|
|
|
|
unsigned int refCount;
|
|
FGParkingRef parking;
|
|
|
|
// we don't want an owning ref here, otherwise we
|
|
// have a circular ownership from AirportDynamics -> us
|
|
SGWeakPtr<FGAirportDynamics> dynamics;
|
|
};
|
|
|
|
ParkingAssignment::ParkingAssignment() :
|
|
_sharedData(NULL)
|
|
{
|
|
}
|
|
|
|
ParkingAssignment::~ParkingAssignment()
|
|
{
|
|
if (_sharedData) {
|
|
_sharedData->release();
|
|
}
|
|
}
|
|
|
|
ParkingAssignment::ParkingAssignment(FGParking* pk, FGAirportDynamics* dyn) :
|
|
_sharedData(NULL)
|
|
{
|
|
if (pk) {
|
|
_sharedData = new ParkingAssignmentPrivate(pk, dyn);
|
|
}
|
|
}
|
|
|
|
ParkingAssignment::ParkingAssignment(const ParkingAssignment& aOther) :
|
|
_sharedData(aOther._sharedData)
|
|
{
|
|
if (_sharedData) {
|
|
_sharedData->retain();
|
|
}
|
|
}
|
|
|
|
void ParkingAssignment::operator=(const ParkingAssignment& aOther)
|
|
{
|
|
if (_sharedData == aOther._sharedData) {
|
|
return; // self-assignment, special case
|
|
}
|
|
|
|
if (_sharedData) {
|
|
_sharedData->release();
|
|
}
|
|
|
|
_sharedData = aOther._sharedData;
|
|
if (_sharedData) {
|
|
_sharedData->retain();
|
|
}
|
|
}
|
|
|
|
void ParkingAssignment::release()
|
|
{
|
|
if (_sharedData) {
|
|
_sharedData->release();
|
|
_sharedData = NULL;
|
|
}
|
|
}
|
|
|
|
bool ParkingAssignment::isValid() const
|
|
{
|
|
return (_sharedData != NULL);
|
|
}
|
|
|
|
FGParking* ParkingAssignment::parking() const
|
|
{
|
|
return _sharedData ? _sharedData->parking.ptr() : NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* @brief Helper to cache all AIObject positions near the airport when
|
|
* searching for available parkings. This allows us to reject parkings
|
|
* which we might not have marked as occupied, but which an object is
|
|
* neverthless close to; such as the primary user or MP aircraft.
|
|
*/
|
|
class NearbyAIObjectCache
|
|
{
|
|
public:
|
|
NearbyAIObjectCache(FGAirportRef apt) :
|
|
m_airport(apt)
|
|
{}
|
|
|
|
bool isAnythingNear(const SGVec3d& cart, double radiusM)
|
|
{
|
|
if (!m_populated) {
|
|
populate();
|
|
}
|
|
|
|
for (auto c : m_cache) {
|
|
const double d = dist(cart, c);
|
|
if (d < radiusM) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
void populate()
|
|
{
|
|
SGVec3d cartAirportPos = m_airport->cart();
|
|
FGAIManager* aiManager = globals->get_subsystem<FGAIManager>();
|
|
for (auto ai : aiManager->get_ai_list()) {
|
|
const auto cart = ai->getCartPos();
|
|
|
|
// 20km cutoff from airport centre
|
|
if (dist(cartAirportPos, cart) > 20000) {
|
|
continue;
|
|
}
|
|
|
|
m_cache.push_back(cart);
|
|
}
|
|
m_populated = true;
|
|
}
|
|
|
|
bool m_populated = false;
|
|
FGAirportRef m_airport;
|
|
std::vector<SGVec3d> m_cache;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
FGAirportDynamics::FGAirportDynamics(FGAirport * ap):
|
|
_ap(ap), rwyPrefs(ap),
|
|
startupController (this),
|
|
towerController (this),
|
|
approachController (this),
|
|
atisSequenceIndex(-1),
|
|
atisSequenceTimeStamp(0.0)
|
|
|
|
{
|
|
lastUpdate = 0;
|
|
}
|
|
|
|
// Destructor
|
|
FGAirportDynamics::~FGAirportDynamics()
|
|
{
|
|
}
|
|
|
|
|
|
// Initialization required after XMLRead
|
|
void FGAirportDynamics::init()
|
|
{
|
|
groundController.setTowerController(&towerController);
|
|
groundController.init(this);
|
|
}
|
|
|
|
FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
|
|
const string & airline,
|
|
bool skipEmptyAirlineCode)
|
|
{
|
|
NearbyAIObjectCache nearCache(parent());
|
|
const FGParkingList& parkings(parent()->groundNetwork()->allParkings());
|
|
FGParkingList candidates;
|
|
for (auto parking : parkings) {
|
|
if (!isParkingAvailable(parking)) {
|
|
continue;
|
|
}
|
|
|
|
if (parking->getRadius() < radius) {
|
|
continue;
|
|
}
|
|
|
|
if (!flType.empty() && (parking->getType() != flType)) {
|
|
continue;
|
|
}
|
|
|
|
if (skipEmptyAirlineCode && parking->getCodes().empty()) {
|
|
continue;
|
|
}
|
|
|
|
if (!airline.empty() && !parking->getCodes().empty()) {
|
|
if (parking->getCodes().find(airline, 0) == string::npos) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (nearCache.isAnythingNear(parking->cart(), parking->getRadius())) {
|
|
continue;
|
|
}
|
|
|
|
candidates.push_back(parking);
|
|
}
|
|
|
|
if (candidates.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// sort by increasing radius, so we return the smallest radius candidate
|
|
// this avoids large spaces (A380 sized) being given to Fokkers/ATR-72s
|
|
std::sort(candidates.begin(), candidates.end(),
|
|
[](const FGParkingRef& a, const FGParkingRef& b) {
|
|
return a->getRadius() < b->getRadius();
|
|
});
|
|
|
|
setParkingAvailable(candidates.front(), false);
|
|
return candidates.front();
|
|
}
|
|
|
|
bool FGAirportDynamics::hasParkings() const
|
|
{
|
|
return !parent()->groundNetwork()->allParkings().empty();
|
|
}
|
|
|
|
ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
|
|
const string & acType,
|
|
const string & airline)
|
|
{
|
|
SG_UNUSED(acType); // sadly not used at the moment
|
|
|
|
// most exact seach - airline codes must be present and match
|
|
FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
|
|
if (result) {
|
|
return ParkingAssignment(result, this);
|
|
}
|
|
|
|
// more tolerant - gates with empty airline codes are permitted
|
|
result = innerGetAvailableParking(radius, flType, airline, false);
|
|
if (result) {
|
|
return ParkingAssignment(result, this);
|
|
}
|
|
|
|
// fallback - ignore the airline code entirely
|
|
result = innerGetAvailableParking(radius, flType, string(), false);
|
|
return result ? ParkingAssignment(result, this) : ParkingAssignment();
|
|
}
|
|
|
|
ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
|
|
{
|
|
const FGParkingList& parkings(parent()->groundNetwork()->allParkings());
|
|
FGParkingList::const_iterator it;
|
|
for (it = parkings.begin(); it != parkings.end(); ++it) {
|
|
if ((*it)->name() == name) {
|
|
return ParkingAssignment(*it, const_cast<FGAirportDynamics*>(this));
|
|
}
|
|
}
|
|
|
|
return ParkingAssignment();
|
|
}
|
|
|
|
ParkingAssignment FGAirportDynamics::getAvailableParkingByName(const std::string & name)
|
|
{
|
|
const FGParkingList& parkings(parent()->groundNetwork()->allParkings());
|
|
auto it = std::find_if(parkings.begin(), parkings.end(), [this, name] (FGParkingRef parking){
|
|
if (parking->name() != name)
|
|
return false;
|
|
|
|
return this->isParkingAvailable(parking);
|
|
});
|
|
|
|
if (it == parkings.end())
|
|
return {}; // no assignment possible
|
|
|
|
return {*it, this};
|
|
}
|
|
|
|
FGParkingRef FGAirportDynamics::getOccupiedParkingByName(const std::string& name) const
|
|
{
|
|
auto it = std::find_if(occupiedParkings.begin(), occupiedParkings.end(),
|
|
[name](const FGParkingRef& pk) { return pk->name() == name; });
|
|
if (it != occupiedParkings.end()) {
|
|
return *it;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void FGAirportDynamics::setParkingAvailable(FGParking* park, bool available)
|
|
{
|
|
if (available) {
|
|
releaseParking(park);
|
|
} else {
|
|
occupiedParkings.insert(park);
|
|
}
|
|
}
|
|
|
|
bool FGAirportDynamics::isParkingAvailable(FGParking* parking) const
|
|
{
|
|
return (occupiedParkings.find(parking) == occupiedParkings.end());
|
|
}
|
|
|
|
void FGAirportDynamics::releaseParking(FGParking* id)
|
|
{
|
|
ParkingSet::iterator it = occupiedParkings.find(id);
|
|
if (it == occupiedParkings.end()) {
|
|
return;
|
|
}
|
|
|
|
occupiedParkings.erase(it);
|
|
}
|
|
|
|
class GetParkingsPredicate
|
|
{
|
|
bool mustBeAvailable;
|
|
std::string type;
|
|
const FGAirportDynamics* dynamics;
|
|
public:
|
|
GetParkingsPredicate(bool b, const std::string& ty, const FGAirportDynamics* dyn) :
|
|
mustBeAvailable(b),
|
|
type(ty),
|
|
dynamics(dyn)
|
|
{}
|
|
|
|
bool operator()(const FGParkingRef& park) const
|
|
{
|
|
if (!type.empty() && (park->getType() != type))
|
|
return true;
|
|
|
|
if (mustBeAvailable && !dynamics->isParkingAvailable(park)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
FGParkingList FGAirportDynamics::getParkings(bool onlyAvailable, const std::string &type) const
|
|
{
|
|
FGParkingList result(parent()->groundNetwork()->allParkings());
|
|
|
|
GetParkingsPredicate pred(onlyAvailable, type, this);
|
|
auto it = std::remove_if(result.begin(), result.end(), pred);
|
|
result.erase(it, result.end());
|
|
return result;
|
|
}
|
|
|
|
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 (unsigned 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 (unsigned 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());
|
|
auto 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
|
|
|
|
if (groups.empty()) {
|
|
SG_LOG(SG_AI, SG_DEV_WARN, "fallbackGetActiveRunway: airport " << parent()->ident() << " has no runways");
|
|
return {};
|
|
}
|
|
|
|
// 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());
|
|
|
|
// debugging fallback runway assignments
|
|
#if defined(RUNWAY_FALLBACK_DEBUG)
|
|
{
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
// assign takeoff and landing runways
|
|
FallbackRunwayGroup bestGroup = groups.front();
|
|
|
|
_lastFallbackUpdate.stamp();
|
|
_fallbackRunwayCounter = 0;
|
|
_fallbackDepartureRunways.clear();
|
|
_fallbackArrivalRunways.clear();
|
|
bestGroup.getRunways(_fallbackArrivalRunways, _fallbackDepartureRunways);
|
|
|
|
#if defined(RUNWAY_FALLBACK_DEBUG)
|
|
ostringstream os;
|
|
os << "\tArrival:" << _fallbackArrivalRunways.front()->ident();
|
|
for (unsigned int r=1; r <_fallbackArrivalRunways.size(); ++r) {
|
|
os << ", " << _fallbackArrivalRunways[r]->ident();
|
|
}
|
|
os << "\n\tDeparture:" << _fallbackDepartureRunways.front()->ident();
|
|
for (unsigned 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);
|
|
#endif
|
|
}
|
|
|
|
_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)
|
|
{
|
|
double windSpeed;
|
|
double windHeading;
|
|
double maxTail;
|
|
double maxCross;
|
|
string name;
|
|
string type;
|
|
|
|
if (!rwyPrefs.available()) {
|
|
runway = fallbackGetActiveRunway(action, heading);
|
|
return !runway.empty();
|
|
}
|
|
|
|
RunwayGroup *currRunwayGroup = 0;
|
|
int nrActiveRunways = 0;
|
|
time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
|
|
if ((std::abs((long) (dayStart - lastUpdate)) > 600)
|
|
|| trafficType != prevTrafficType) {
|
|
landing.clear();
|
|
takeoff.clear();
|
|
lastUpdate = dayStart;
|
|
prevTrafficType = trafficType;
|
|
/*
|
|
FGEnvironment
|
|
stationweather =
|
|
((FGEnvironmentMgr *) globals->get_subsystem("environment"))
|
|
->getEnvironment(getLatitude(), getLongitude(),
|
|
getElevation());
|
|
*/
|
|
windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
|
|
windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
|
|
//stationweather.get_wind_from_heading_deg();
|
|
string scheduleName;
|
|
//cerr << "finding active Runway for : " << _ap->getId() << endl;
|
|
//cerr << "Wind Heading : " << windHeading << endl;
|
|
//cerr << "Wind Speed : " << windSpeed << endl;
|
|
|
|
//cerr << "Nr of seconds since day start << " << dayStart << endl;
|
|
|
|
ScheduleTime *currSched;
|
|
//cerr << "A"<< endl;
|
|
currSched = rwyPrefs.getSchedule(trafficType.c_str());
|
|
if (!(currSched))
|
|
return false;
|
|
//cerr << "B"<< endl;
|
|
scheduleName = currSched->getName(dayStart);
|
|
maxTail = currSched->getTailWind();
|
|
maxCross = currSched->getCrossWind();
|
|
//cerr << "Current Schedule = : " << scheduleName << endl;
|
|
if (scheduleName.empty())
|
|
return false;
|
|
//cerr << "C"<< endl;
|
|
currRunwayGroup = rwyPrefs.getGroup(scheduleName);
|
|
//cerr << "D"<< endl;
|
|
if (!(currRunwayGroup))
|
|
return false;
|
|
nrActiveRunways = currRunwayGroup->getNrActiveRunways();
|
|
|
|
// Keep a history of the currently active runways, to ensure
|
|
// that an already established selection of runways will not
|
|
// be overridden once a more preferred selection becomes
|
|
// available as that can lead to random runway swapping.
|
|
if (trafficType == "com") {
|
|
currentlyActive = &comActive;
|
|
} else if (trafficType == "gen") {
|
|
currentlyActive = &genActive;
|
|
} else if (trafficType == "mil") {
|
|
currentlyActive = &milActive;
|
|
} else if (trafficType == "ul") {
|
|
currentlyActive = &ulActive;
|
|
}
|
|
|
|
//cerr << "Durrently active selection for " << trafficType << ": ";
|
|
for (stringVecIterator it = currentlyActive->begin();
|
|
it != currentlyActive->end(); it++) {
|
|
//cerr << (*it) << " ";
|
|
}
|
|
//cerr << endl;
|
|
|
|
currRunwayGroup->setActive(_ap,
|
|
windSpeed,
|
|
windHeading,
|
|
maxTail, maxCross, currentlyActive);
|
|
|
|
// Note that I SHOULD keep multiple lists in memory, one for
|
|
// general aviation, one for commercial and one for military
|
|
// traffic.
|
|
currentlyActive->clear();
|
|
nrActiveRunways = currRunwayGroup->getNrActiveRunways();
|
|
//cerr << "Choosing runway for " << trafficType << endl;
|
|
for (int i = 0; i < nrActiveRunways; i++) {
|
|
type = "unknown"; // initialize to something other than landing or takeoff
|
|
currRunwayGroup->getActive(i, name, type);
|
|
if (type == "landing") {
|
|
landing.push_back(name);
|
|
currentlyActive->push_back(name);
|
|
//cerr << "Landing " << name << endl;
|
|
}
|
|
if (type == "takeoff") {
|
|
takeoff.push_back(name);
|
|
currentlyActive->push_back(name);
|
|
//cerr << "takeoff " << name << endl;
|
|
}
|
|
}
|
|
//cerr << endl;
|
|
}
|
|
|
|
if (action == 1) // takeoff
|
|
{
|
|
int nr = takeoff.size();
|
|
if (nr) {
|
|
// Note that the randomization below, is just a placeholder to choose between
|
|
// multiple active runways for this action. This should be
|
|
// under ATC control.
|
|
runway = chooseRwyByHeading(takeoff, heading);
|
|
} else { // Fallback
|
|
runway = chooseRunwayFallback();
|
|
}
|
|
}
|
|
|
|
if (action == 2) // landing
|
|
{
|
|
if (!landing.empty()) {
|
|
runway = chooseRwyByHeading(landing, heading);
|
|
}
|
|
|
|
if (runway.empty()) { //fallback
|
|
runway = chooseRunwayFallback();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
|
|
double heading)
|
|
{
|
|
double bestError = 360.0;
|
|
double rwyHeading, headingError;
|
|
string runway;
|
|
for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
|
|
if (!_ap->hasRunwayWithIdent(*i)) {
|
|
SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
|
|
" not found at " << _ap->ident());
|
|
continue;
|
|
}
|
|
|
|
FGRunway *rwy = _ap->getRunwayByIdent((*i));
|
|
rwyHeading = rwy->headingDeg();
|
|
headingError = fabs(heading - rwyHeading);
|
|
if (headingError > 180)
|
|
headingError = fabs(headingError - 360);
|
|
if (headingError < bestError) {
|
|
runway = (*i);
|
|
bestError = headingError;
|
|
}
|
|
}
|
|
//cerr << "Using active runway " << runway << " for heading " << heading << endl;
|
|
return runway;
|
|
}
|
|
|
|
void FGAirportDynamics::getActiveRunway(const string & trafficType,
|
|
int action, string & runway,
|
|
double heading)
|
|
{
|
|
bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
|
|
if (!ok || runway.empty()) {
|
|
runway = chooseRunwayFallback();
|
|
}
|
|
}
|
|
|
|
string FGAirportDynamics::chooseRunwayFallback()
|
|
{
|
|
FGRunway *rwy = _ap->getActiveRunwayForUsage();
|
|
if (!rwy) {
|
|
SG_LOG(SG_AI, SG_WARN, "FGAirportDynamics::chooseRunwayFallback failed at " << _ap->ident());
|
|
|
|
// let's use runway 0
|
|
return _ap->getRunwayByIndex(0)->ident();
|
|
}
|
|
return rwy->ident();
|
|
}
|
|
|
|
double FGAirportDynamics::getElevation() const
|
|
{
|
|
return _ap->getElevation();
|
|
}
|
|
|
|
const string FGAirportDynamics::getId() const
|
|
{
|
|
return _ap->getId();
|
|
}
|
|
|
|
// Experimental: Return a different ground frequency depending on the leg of the
|
|
// Flight. Leg should always have a minimum value of two when this function is called.
|
|
// Note that in this scheme, the assignment of various frequencies to various ground
|
|
// operations is completely arbitrary. As such, is a short cut I need to take now,
|
|
// so that at least I can start working on assigning different frequencies to different
|
|
// operations.
|
|
|
|
int FGAirportDynamics::getGroundFrequency(unsigned leg)
|
|
{
|
|
//return freqGround.size() ? freqGround[0] : 0; };
|
|
//cerr << "Getting frequency for : " << leg << endl;
|
|
int groundFreq = 0;
|
|
if (leg < 1) {
|
|
SG_LOG(SG_ATC, SG_ALERT,
|
|
"Leg value is smaller than one at " << SG_ORIGIN);
|
|
}
|
|
|
|
const intVec& freqGround(parent()->groundNetwork()->getGroundFrequencies());
|
|
|
|
if (freqGround.size() == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if ((freqGround.size() < leg) && (leg > 0)) {
|
|
groundFreq =
|
|
(freqGround.size() <=
|
|
(leg - 1)) ? freqGround[freqGround.size() -
|
|
1] : freqGround[leg - 1];
|
|
}
|
|
if ((freqGround.size() >= leg) && (leg > 0)) {
|
|
groundFreq = freqGround[leg - 1];
|
|
}
|
|
return groundFreq;
|
|
}
|
|
|
|
int FGAirportDynamics::getTowerFrequency(unsigned nr)
|
|
{
|
|
int towerFreq = 0;
|
|
if (nr < 2) {
|
|
SG_LOG(SG_ATC, SG_ALERT,
|
|
"Leg value is smaller than two at " << SG_ORIGIN);
|
|
}
|
|
|
|
const intVec& freqTower(parent()->groundNetwork()->getTowerFrequencies());
|
|
|
|
if (freqTower.size() == 0) {
|
|
return 0;
|
|
}
|
|
if ((freqTower.size() > nr - 1) && (nr > 1)) {
|
|
towerFreq = freqTower[nr - 1];
|
|
}
|
|
if ((freqTower.size() < nr - 1) && (nr > 1)) {
|
|
towerFreq =
|
|
(freqTower.size() <
|
|
(nr - 1)) ? freqTower[freqTower.size() -
|
|
1] : freqTower[nr - 2];
|
|
}
|
|
if ((freqTower.size() >= nr - 1) && (nr > 1)) {
|
|
towerFreq = freqTower[nr - 2];
|
|
}
|
|
return towerFreq;
|
|
}
|
|
|
|
const std::string FGAirportDynamics::getAtisSequence()
|
|
{
|
|
if (atisSequenceIndex == -1) {
|
|
updateAtisSequence(1, false);
|
|
}
|
|
|
|
char atisSequenceString[2];
|
|
atisSequenceString[0] = 'a' + atisSequenceIndex;
|
|
atisSequenceString[1] = 0;
|
|
|
|
return globals->get_locale()->getLocalizedString(atisSequenceString, "atc", "unknown");
|
|
}
|
|
|
|
int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
|
|
{
|
|
double now = globals->get_sim_time_sec();
|
|
if (atisSequenceIndex == -1) {
|
|
// first computation
|
|
atisSequenceTimeStamp = now;
|
|
atisSequenceIndex = rand() % 26; // random initial sequence letters
|
|
return atisSequenceIndex;
|
|
}
|
|
|
|
int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
|
|
atisSequenceTimeStamp += (interval * steps);
|
|
if (forceUpdate && (steps == 0)) {
|
|
++steps; // a "special" ATIS update is required
|
|
}
|
|
|
|
atisSequenceIndex = (atisSequenceIndex + steps) % 26;
|
|
// return a huge value if no update occurred
|
|
return (atisSequenceIndex + (steps ? 0 : 26*1000));
|
|
}
|