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 <Airports/runways.hxx>
42 #include <ATCDCL/ATCutils.hxx>
43 #include <Navaids/NavDataCache.hxx>
46 #include "dynamics.hxx"
51 using std::random_shuffle;
53 class ParkingAssignment::ParkingAssignmentPrivate
56 ParkingAssignmentPrivate(FGParking* pk, FGAirport* apt) :
63 retain(); // initial count of 1
66 ~ParkingAssignmentPrivate()
68 airport->getDynamics()->releaseParking(parking->guid());
73 if ((--refCount) == 0) {
83 unsigned int refCount;
84 SGSharedPtr<FGParking> parking;
85 SGSharedPtr<FGAirport> airport;
88 ParkingAssignment::ParkingAssignment() :
93 ParkingAssignment::~ParkingAssignment()
96 _sharedData->release();
100 ParkingAssignment::ParkingAssignment(FGParking* pk, FGAirport* apt) :
104 _sharedData = new ParkingAssignmentPrivate(pk, apt);
108 ParkingAssignment::ParkingAssignment(const ParkingAssignment& aOther) :
109 _sharedData(aOther._sharedData)
112 _sharedData->retain();
116 void ParkingAssignment::operator=(const ParkingAssignment& aOther)
118 if (_sharedData == aOther._sharedData) {
119 return; // self-assignment, special case
123 _sharedData->release();
126 _sharedData = aOther._sharedData;
128 _sharedData->retain();
132 void ParkingAssignment::release()
135 _sharedData->release();
140 bool ParkingAssignment::isValid() const
142 return (_sharedData != NULL);
145 FGParking* ParkingAssignment::parking() const
147 return _sharedData ? _sharedData->parking.ptr() : NULL;
150 ////////////////////////////////////////////////////////////////////////////////
152 FGAirportDynamics::FGAirportDynamics(FGAirport * ap):
153 _ap(ap), rwyPrefs(ap),
154 startupController (this),
155 towerController (this),
156 approachController (this),
157 atisSequenceIndex(-1),
158 atisSequenceTimeStamp(0.0)
165 FGAirportDynamics::~FGAirportDynamics()
170 // Initialization required after XMLRead
171 void FGAirportDynamics::init()
173 groundNetwork.init(_ap);
174 groundNetwork.setTowerController(&towerController);
178 FGParking* FGAirportDynamics::innerGetAvailableParking(double radius, const string & flType,
179 const string & airline,
180 bool skipEmptyAirlineCode)
182 flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
183 BOOST_FOREACH(PositionedID pk, cache->findAirportParking(_ap->guid(), flType, radius)) {
184 if (!isParkingAvailable(pk)) {
188 FGParking* parking = getParking(pk);
189 if (skipEmptyAirlineCode && parking->getCodes().empty()) {
193 if (!airline.empty() && !parking->getCodes().empty()) {
194 if (parking->getCodes().find(airline, 0) == string::npos) {
199 setParkingAvailable(pk, false);
206 ParkingAssignment FGAirportDynamics::getAvailableParking(double radius, const string & flType,
207 const string & acType,
208 const string & airline)
210 SG_UNUSED(acType); // sadly not used at the moment
212 // most exact seach - airline codes must be present and match
213 FGParking* result = innerGetAvailableParking(radius, flType, airline, true);
215 return ParkingAssignment(result, _ap);
218 // more tolerant - gates with empty airline codes are permitted
219 result = innerGetAvailableParking(radius, flType, airline, false);
221 return ParkingAssignment(result, _ap);
224 // fallback - ignore the airline code entirely
225 result = innerGetAvailableParking(radius, flType, string(), false);
226 return result ? ParkingAssignment(result, _ap) : ParkingAssignment();
229 FGParking *FGAirportDynamics::getParking(PositionedID id) const
231 return static_cast<FGParking*>(flightgear::NavDataCache::instance()->loadById(id));
234 string FGAirportDynamics::getParkingName(PositionedID id) const
236 FGParking* p = getParking(id);
244 ParkingAssignment FGAirportDynamics::getParkingByName(const std::string& name) const
246 PositionedID guid = flightgear::NavDataCache::instance()->airportItemWithIdent(parent()->guid(), FGPositioned::PARKING, name);
248 return ParkingAssignment();
251 return ParkingAssignment(getParking(guid), _ap);
254 void FGAirportDynamics::setParkingAvailable(PositionedID guid, bool available)
257 releaseParking(guid);
259 occupiedParkings.insert(guid);
263 bool FGAirportDynamics::isParkingAvailable(PositionedID parking) const
265 return (occupiedParkings.find(parking) == occupiedParkings.end());
268 void FGAirportDynamics::releaseParking(PositionedID id)
270 ParkingSet::iterator it = occupiedParkings.find(id);
271 if (it == occupiedParkings.end()) {
275 occupiedParkings.erase(it);
278 void FGAirportDynamics::setRwyUse(const FGRunwayPreference & ref)
283 bool FGAirportDynamics::innerGetActiveRunway(const string & trafficType,
284 int action, string & runway,
294 if (!rwyPrefs.available()) {
298 RunwayGroup *currRunwayGroup = 0;
299 int nrActiveRunways = 0;
300 time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
301 if ((abs((long) (dayStart - lastUpdate)) > 600)
302 || trafficType != prevTrafficType) {
305 lastUpdate = dayStart;
306 prevTrafficType = trafficType;
310 ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
311 ->getEnvironment(getLatitude(), getLongitude(),
314 windSpeed = fgGetInt("/environment/metar/base-wind-speed-kt"); //stationweather.get_wind_speed_kt();
315 windHeading = fgGetInt("/environment/metar/base-wind-dir-deg");
316 //stationweather.get_wind_from_heading_deg();
318 //cerr << "finding active Runway for : " << _ap->getId() << endl;
319 //cerr << "Wind Heading : " << windHeading << endl;
320 //cerr << "Wind Speed : " << windSpeed << endl;
322 //cerr << "Nr of seconds since day start << " << dayStart << endl;
324 ScheduleTime *currSched;
325 //cerr << "A"<< endl;
326 currSched = rwyPrefs.getSchedule(trafficType.c_str());
329 //cerr << "B"<< endl;
330 scheduleName = currSched->getName(dayStart);
331 maxTail = currSched->getTailWind();
332 maxCross = currSched->getCrossWind();
333 //cerr << "Current Schedule = : " << scheduleName << endl;
334 if (scheduleName.empty())
336 //cerr << "C"<< endl;
337 currRunwayGroup = rwyPrefs.getGroup(scheduleName);
338 //cerr << "D"<< endl;
339 if (!(currRunwayGroup))
341 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
343 // Keep a history of the currently active runways, to ensure
344 // that an already established selection of runways will not
345 // be overridden once a more preferred selection becomes
346 // available as that can lead to random runway swapping.
347 if (trafficType == "com") {
348 currentlyActive = &comActive;
349 } else if (trafficType == "gen") {
350 currentlyActive = &genActive;
351 } else if (trafficType == "mil") {
352 currentlyActive = &milActive;
353 } else if (trafficType == "ul") {
354 currentlyActive = &ulActive;
357 //cerr << "Durrently active selection for " << trafficType << ": ";
358 for (stringVecIterator it = currentlyActive->begin();
359 it != currentlyActive->end(); it++) {
360 //cerr << (*it) << " ";
364 currRunwayGroup->setActive(_ap,
367 maxTail, maxCross, currentlyActive);
369 // Note that I SHOULD keep multiple lists in memory, one for
370 // general aviation, one for commercial and one for military
372 currentlyActive->clear();
373 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
374 //cerr << "Choosing runway for " << trafficType << endl;
375 for (int i = 0; i < nrActiveRunways; i++) {
376 type = "unknown"; // initialize to something other than landing or takeoff
377 currRunwayGroup->getActive(i, name, type);
378 if (type == "landing") {
379 landing.push_back(name);
380 currentlyActive->push_back(name);
381 //cerr << "Landing " << name << endl;
383 if (type == "takeoff") {
384 takeoff.push_back(name);
385 currentlyActive->push_back(name);
386 //cerr << "takeoff " << name << endl;
392 if (action == 1) // takeoff
394 int nr = takeoff.size();
396 // Note that the randomization below, is just a placeholder to choose between
397 // multiple active runways for this action. This should be
398 // under ATC control.
399 runway = chooseRwyByHeading(takeoff, heading);
401 runway = chooseRunwayFallback();
405 if (action == 2) // landing
407 int nr = landing.size();
409 runway = chooseRwyByHeading(landing, heading);
411 runway = chooseRunwayFallback();
418 string FGAirportDynamics::chooseRwyByHeading(stringVec rwys,
421 double bestError = 360.0;
422 double rwyHeading, headingError;
424 for (stringVecIterator i = rwys.begin(); i != rwys.end(); i++) {
425 if (!_ap->hasRunwayWithIdent(*i)) {
426 SG_LOG(SG_ATC, SG_WARN, "chooseRwyByHeading: runway " << *i <<
427 " not found at " << _ap->ident());
431 FGRunway *rwy = _ap->getRunwayByIdent((*i));
432 rwyHeading = rwy->headingDeg();
433 headingError = fabs(heading - rwyHeading);
434 if (headingError > 180)
435 headingError = fabs(headingError - 360);
436 if (headingError < bestError) {
438 bestError = headingError;
441 //cerr << "Using active runway " << runway << " for heading " << heading << endl;
445 void FGAirportDynamics::getActiveRunway(const string & trafficType,
446 int action, string & runway,
449 bool ok = innerGetActiveRunway(trafficType, action, runway, heading);
451 runway = chooseRunwayFallback();
455 string FGAirportDynamics::chooseRunwayFallback()
457 FGRunway *rwy = _ap->getActiveRunwayForUsage();
461 double FGAirportDynamics::getElevation() const
463 return _ap->getElevation();
466 const string FGAirportDynamics::getId() const
471 // Experimental: Return a different ground frequency depending on the leg of the
472 // Flight. Leg should always have a minimum value of two when this function is called.
473 // Note that in this scheme, the assignment of various frequencies to various ground
474 // operations is completely arbitrary. As such, is a short cut I need to take now,
475 // so that at least I can start working on assigning different frequencies to different
478 int FGAirportDynamics::getGroundFrequency(unsigned leg)
480 //return freqGround.size() ? freqGround[0] : 0; };
481 //cerr << "Getting frequency for : " << leg << endl;
484 SG_LOG(SG_ATC, SG_ALERT,
485 "Leg value is smaller than one at " << SG_ORIGIN);
487 if (freqGround.size() == 0) {
491 if ((freqGround.size() < leg) && (leg > 0)) {
493 (freqGround.size() <=
494 (leg - 1)) ? freqGround[freqGround.size() -
495 1] : freqGround[leg - 1];
497 if ((freqGround.size() >= leg) && (leg > 0)) {
498 groundFreq = freqGround[leg - 1];
503 int FGAirportDynamics::getTowerFrequency(unsigned nr)
507 SG_LOG(SG_ATC, SG_ALERT,
508 "Leg value is smaller than two at " << SG_ORIGIN);
510 if (freqTower.size() == 0) {
513 if ((freqTower.size() > nr - 1) && (nr > 1)) {
514 towerFreq = freqTower[nr - 1];
516 if ((freqTower.size() < nr - 1) && (nr > 1)) {
519 (nr - 1)) ? freqTower[freqTower.size() -
520 1] : freqTower[nr - 2];
522 if ((freqTower.size() >= nr - 1) && (nr > 1)) {
523 towerFreq = freqTower[nr - 2];
528 const std::string FGAirportDynamics::getAtisSequence()
530 if (atisSequenceIndex == -1) {
531 updateAtisSequence(1, false);
534 return GetPhoneticLetter(atisSequenceIndex);
537 int FGAirportDynamics::updateAtisSequence(int interval, bool forceUpdate)
539 double now = globals->get_sim_time_sec();
540 if (atisSequenceIndex == -1) {
542 atisSequenceTimeStamp = now;
543 atisSequenceIndex = rand() % LTRS; // random initial sequence letters
544 return atisSequenceIndex;
547 int steps = static_cast<int>((now - atisSequenceTimeStamp) / interval);
548 atisSequenceTimeStamp += (interval * steps);
549 if (forceUpdate && (steps == 0)) {
550 ++steps; // a "special" ATIS update is required
553 atisSequenceIndex = (atisSequenceIndex + steps) % LTRS;
554 // return a huge value if no update occurred
555 return (atisSequenceIndex + (steps ? 0 : LTRS*1000));