1
0
Fork 0
flightgear/src/Airports/dynamics.cxx
Automatic Release Builder f4f261a068 Guard against data errors in rwyprefs.xml
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
2020-10-29 16:22:35 +00:00

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));
}