2 // simple.cxx -- a really simplistic class to manage airport ID,
3 // lat, lon of the center of one of it's runways, and
6 // Written by Curtis Olson, started April 1998.
7 // Updated by Durk Talsma, started December, 2004.
9 // Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt
11 // This program is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU General Public License as
13 // published by the Free Software Foundation; either version 2 of the
14 // License, or (at your option) any later version.
16 // This program is distributed in the hope that it will be useful, but
17 // WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 #include "airport.hxx"
34 #include <boost/foreach.hpp>
36 #include <simgear/misc/sg_path.hxx>
37 #include <simgear/props/props.hxx>
38 #include <simgear/props/props_io.hxx>
39 #include <simgear/debug/logstream.hxx>
40 #include <simgear/sg_inlines.h>
41 #include <simgear/structure/exception.hxx>
43 #include <Environment/environment_mgr.hxx>
44 #include <Environment/environment.hxx>
45 #include <Main/fg_props.hxx>
46 #include <Airports/runways.hxx>
47 #include <Airports/pavement.hxx>
48 #include <Airports/dynamics.hxx>
49 #include <Airports/xmlloader.hxx>
50 #include <Navaids/procedure.hxx>
51 #include <Navaids/waypoint.hxx>
52 #include <ATC/CommStation.hxx>
53 #include <Navaids/NavDataCache.hxx>
58 using namespace flightgear;
61 /***************************************************************************
63 ***************************************************************************/
65 AirportCache FGAirport::airportCache;
67 FGAirport::FGAirport(PositionedID aGuid, const string &id, const SGGeod& location,
68 const string &name, bool has_metar, Type aType) :
69 FGPositioned(aGuid, aType, id, location),
71 _has_metar(has_metar),
73 mTowerDataLoaded(false),
74 mRunwaysLoaded(false),
75 mTaxiwaysLoaded(false),
76 mProceduresLoaded(false),
82 FGAirport::~FGAirport()
87 bool FGAirport::isAirport() const
89 return type() == AIRPORT;
92 bool FGAirport::isSeaport() const
94 return type() == SEAPORT;
97 bool FGAirport::isHeliport() const
99 return type() == HELIPORT;
102 bool FGAirport::isAirportType(FGPositioned* pos)
108 return (pos->type() >= AIRPORT) && (pos->type() <= SEAPORT);
111 FGAirportDynamics * FGAirport::getDynamics()
117 _dynamics = new FGAirportDynamics(this);
118 XMLLoader::load(_dynamics);
121 FGRunwayPreference rwyPrefs(this);
122 XMLLoader::load(&rwyPrefs);
123 _dynamics->setRwyUse(rwyPrefs);
128 unsigned int FGAirport::numRunways() const
131 return mRunways.size();
134 unsigned int FGAirport::numHelipads() const
137 return mHelipads.size();
140 FGRunway* FGAirport::getRunwayByIndex(unsigned int aIndex) const
144 assert(aIndex >= 0 && aIndex < mRunways.size());
145 return (FGRunway*) flightgear::NavDataCache::instance()->loadById(mRunways[aIndex]);
148 FGHelipad* FGAirport::getHelipadByIndex(unsigned int aIndex) const
152 assert(aIndex >= 0 && aIndex < mHelipads.size());
153 return (FGHelipad*) flightgear::NavDataCache::instance()->loadById(mHelipads[aIndex]);
156 bool FGAirport::hasRunwayWithIdent(const string& aIdent) const
158 return flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::RUNWAY, aIdent) != 0;
161 FGRunway* FGAirport::getRunwayByIdent(const string& aIdent) const
163 PositionedID id = flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::RUNWAY, aIdent);
165 SG_LOG(SG_GENERAL, SG_ALERT, "no such runway '" << aIdent << "' at airport " << ident());
166 throw sg_range_exception("unknown runway " + aIdent + " at airport:" + ident(), "FGAirport::getRunwayByIdent");
169 return (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
173 FGRunway* FGAirport::findBestRunwayForHeading(double aHeading) const
177 FGRunway* result = NULL;
178 double currentBestQuality = 0.0;
180 SGPropertyNode *param = fgGetNode("/sim/airport/runways/search", true);
181 double lengthWeight = param->getDoubleValue("length-weight", 0.01);
182 double widthWeight = param->getDoubleValue("width-weight", 0.01);
183 double surfaceWeight = param->getDoubleValue("surface-weight", 10);
184 double deviationWeight = param->getDoubleValue("deviation-weight", 1);
186 BOOST_FOREACH(PositionedID id, mRunways) {
187 FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
188 double good = rwy->score(lengthWeight, widthWeight, surfaceWeight);
189 double dev = aHeading - rwy->headingDeg();
190 SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
191 double bad = fabs(deviationWeight * dev) + 1e-20;
192 double quality = good / bad;
194 if (quality > currentBestQuality) {
195 currentBestQuality = quality;
203 FGRunway* FGAirport::findBestRunwayForPos(const SGGeod& aPos) const
207 FGRunway* result = NULL;
208 double currentLowestDev = 180.0;
210 BOOST_FOREACH(PositionedID id, mRunways) {
211 FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
213 double inboundCourse = SGGeodesy::courseDeg(aPos, rwy->end());
214 double dev = inboundCourse - rwy->headingDeg();
215 SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
218 if (dev < currentLowestDev) { // new best match
219 currentLowestDev = dev;
222 } // of runway iteration
228 bool FGAirport::hasHardRunwayOfLengthFt(double aLengthFt) const
232 BOOST_FOREACH(PositionedID id, mRunways) {
233 FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
235 if (rwy->isReciprocal()) {
236 continue; // we only care about lengths, so don't do work twice
239 if (rwy->isHardSurface() && (rwy->lengthFt() >= aLengthFt)) {
240 return true; // we're done!
242 } // of runways iteration
247 unsigned int FGAirport::numTaxiways() const
250 return mTaxiways.size();
253 FGTaxiway* FGAirport::getTaxiwayByIndex(unsigned int aIndex) const
257 assert(aIndex >= 0 && aIndex < mTaxiways.size());
258 return (FGTaxiway*) flightgear::NavDataCache::instance()->loadById(mTaxiways[aIndex]);
261 unsigned int FGAirport::numPavements() const
264 return mPavements.size();
267 FGPavement* FGAirport::getPavementByIndex(unsigned int aIndex) const
270 assert(aIndex >= 0 && aIndex < mPavements.size());
271 return (FGPavement*) flightgear::NavDataCache::instance()->loadById(mPavements[aIndex]);
274 FGRunway* FGAirport::getActiveRunwayForUsage() const
276 FGEnvironmentMgr* envMgr = (FGEnvironmentMgr *) globals->get_subsystem("environment");
278 // This forces West-facing rwys to be used in no-wind situations
279 // which is consistent with Flightgear's initial setup.
283 FGEnvironment stationWeather(envMgr->getEnvironment(mPosition));
285 double windSpeed = stationWeather.get_wind_speed_kt();
286 if (windSpeed > 0.0) {
287 hdg = stationWeather.get_wind_from_heading_deg();
291 return findBestRunwayForHeading(hdg);
294 FGAirport* FGAirport::findClosest(const SGGeod& aPos, double aCuttofNm, Filter* filter)
296 AirportFilter aptFilter;
297 if (filter == NULL) {
301 FGPositionedRef r = FGPositioned::findClosest(aPos, aCuttofNm, filter);
306 return static_cast<FGAirport*>(r.ptr());
309 FGAirport::HardSurfaceFilter::HardSurfaceFilter(double minLengthFt) :
310 mMinLengthFt(minLengthFt)
312 if (minLengthFt < 0.0) {
313 mMinLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 0.0);
317 bool FGAirport::HardSurfaceFilter::passAirport(FGAirport* aApt) const
319 return aApt->hasHardRunwayOfLengthFt(mMinLengthFt);
322 FGAirport* FGAirport::findByIdent(const std::string& aIdent)
324 AirportCache::iterator it = airportCache.find(aIdent);
325 if (it != airportCache.end())
329 FGAirport* r = static_cast<FGAirport*> (FGPositioned::findFirstWithIdent(aIdent, &filter).get());
331 // add airport to the cache (even when it's NULL, so we don't need to search in vain again)
332 airportCache[aIdent] = r;
334 // we don't warn here when r==NULL, let the caller do that
338 FGAirport* FGAirport::getByIdent(const std::string& aIdent)
340 FGAirport* r = findByIdent(aIdent);
342 throw sg_range_exception("No such airport with ident: " + aIdent);
346 char** FGAirport::searchNamesAndIdents(const std::string& aFilter)
348 return NavDataCache::instance()->searchAirportNamesAndIdents(aFilter);
351 // find basic airport location info from airport database
352 const FGAirport *fgFindAirportID( const string& id)
358 return FGAirport::findByIdent(id);
361 void FGAirport::loadRunways() const
363 if (mRunwaysLoaded) {
364 return; // already loaded, great
367 loadSceneryDefinitions();
369 mRunwaysLoaded = true;
370 mRunways = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::RUNWAY);
373 void FGAirport::loadHelipads() const
375 if (mHelipadsLoaded) {
376 return; // already loaded, great
379 loadSceneryDefinitions();
381 mHelipadsLoaded = true;
382 mHelipads = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::HELIPAD);
385 void FGAirport::loadTaxiways() const
387 if (mTaxiwaysLoaded) {
388 return; // already loaded, great
391 mTaxiwaysLoaded = true;
392 mTaxiways = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::TAXIWAY);
395 void FGAirport::loadProcedures() const
397 if (mProceduresLoaded) {
401 mProceduresLoaded = true;
403 if (!XMLLoader::findAirportData(ident(), "procedures", path)) {
404 SG_LOG(SG_GENERAL, SG_INFO, "no procedures data available for " << ident());
408 SG_LOG(SG_GENERAL, SG_INFO, ident() << ": loading procedures from " << path.str());
409 RouteBase::loadAirportProcedures(path, const_cast<FGAirport*>(this));
412 void FGAirport::loadSceneryDefinitions() const
414 NavDataCache* cache = NavDataCache::instance();
416 if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
417 return; // no XML threshold data
420 if (!cache->isCachedFileModified(path)) {
421 // cached values are correct, we're all done
425 flightgear::NavDataCache::Transaction txn(cache);
426 SGPropertyNode_ptr rootNode = new SGPropertyNode;
427 readProperties(path.str(), rootNode);
428 const_cast<FGAirport*>(this)->readThresholdData(rootNode);
429 cache->stampCacheFile(path);
433 void FGAirport::readThresholdData(SGPropertyNode* aRoot)
435 SGPropertyNode* runway;
437 for (; (runway = aRoot->getChild("runway", runwayIndex)) != NULL; ++runwayIndex) {
438 SGPropertyNode* t0 = runway->getChild("threshold", 0),
439 *t1 = runway->getChild("threshold", 1);
441 assert(t1); // too strict? maybe we should finally allow single-ended runways
443 processThreshold(t0);
444 processThreshold(t1);
445 } // of runways iteration
448 void FGAirport::processThreshold(SGPropertyNode* aThreshold)
450 // first, let's identify the current runway
451 string rwyIdent(aThreshold->getStringValue("rwy"));
452 NavDataCache* cache = NavDataCache::instance();
453 PositionedID id = cache->airportItemWithIdent(guid(), FGPositioned::RUNWAY, rwyIdent);
455 SG_LOG(SG_GENERAL, SG_DEBUG, "FGAirport::processThreshold: "
456 "found runway not defined in the global data:" << ident() << "/" << rwyIdent);
460 double lon = aThreshold->getDoubleValue("lon"),
461 lat = aThreshold->getDoubleValue("lat");
462 SGGeod newThreshold(SGGeod::fromDegM(lon, lat, mPosition.getElevationM()));
464 double newHeading = aThreshold->getDoubleValue("hdg-deg");
465 double newDisplacedThreshold = aThreshold->getDoubleValue("displ-m") * SG_METER_TO_FEET;
466 double newStopway = aThreshold->getDoubleValue("stopw-m") * SG_METER_TO_FEET;
468 cache->updateRunwayThreshold(id, newThreshold,
469 newHeading, newDisplacedThreshold, newStopway);
472 SGGeod FGAirport::getTowerLocation() const
476 NavDataCache* cache = NavDataCache::instance();
477 PositionedIDVec towers = cache->airportItemsOfType(guid(), FGPositioned::TOWER);
478 if (towers.empty()) {
479 SG_LOG(SG_GENERAL, SG_ALERT, "No towers defined for:" <<ident());
483 FGPositionedRef tower = cache->loadById(towers.front());
484 return tower->geod();
487 void FGAirport::validateTowerData() const
489 if (mTowerDataLoaded) {
493 mTowerDataLoaded = true;
494 NavDataCache* cache = NavDataCache::instance();
496 if (!XMLLoader::findAirportData(ident(), "twr", path)) {
497 return; // no XML tower data
500 if (!cache->isCachedFileModified(path)) {
501 // cached values are correct, we're all done
505 flightgear::NavDataCache::Transaction txn(cache);
506 SGPropertyNode_ptr rootNode = new SGPropertyNode;
507 readProperties(path.str(), rootNode);
508 const_cast<FGAirport*>(this)->readTowerData(rootNode);
509 cache->stampCacheFile(path);
513 void FGAirport::readTowerData(SGPropertyNode* aRoot)
515 SGPropertyNode* twrNode = aRoot->getChild("tower")->getChild("twr");
516 double lat = twrNode->getDoubleValue("lat"),
517 lon = twrNode->getDoubleValue("lon"),
518 elevM = twrNode->getDoubleValue("elev-m");
519 // tower elevation is AGL, not AMSL. Since we don't want to depend on the
520 // scenery for a precise terrain elevation, we use the field elevation
521 // (this is also what the apt.dat code does)
522 double fieldElevationM = geod().getElevationM();
523 SGGeod towerLocation(SGGeod::fromDegM(lon, lat, fieldElevationM + elevM));
525 NavDataCache* cache = NavDataCache::instance();
526 PositionedIDVec towers = cache->airportItemsOfType(guid(), FGPositioned::TOWER);
527 if (towers.empty()) {
528 cache->insertTower(guid(), towerLocation);
530 // update the position
531 cache->updatePosition(towers.front(), towerLocation);
535 bool FGAirport::validateILSData()
537 if (mILSDataLoaded) {
541 mILSDataLoaded = true;
542 NavDataCache* cache = NavDataCache::instance();
544 if (!XMLLoader::findAirportData(ident(), "ils", path)) {
545 return false; // no XML tower data
548 if (!cache->isCachedFileModified(path)) {
549 // cached values are correct, we're all done
553 SGPropertyNode_ptr rootNode = new SGPropertyNode;
554 readProperties(path.str(), rootNode);
556 flightgear::NavDataCache::Transaction txn(cache);
557 readILSData(rootNode);
558 cache->stampCacheFile(path);
561 // we loaded data, tell the caller it might need to reload things
565 void FGAirport::readILSData(SGPropertyNode* aRoot)
567 NavDataCache* cache = NavDataCache::instance();
569 // find the entry matching the runway
570 SGPropertyNode* runwayNode, *ilsNode;
571 for (int i=0; (runwayNode = aRoot->getChild("runway", i)) != NULL; ++i) {
572 for (int j=0; (ilsNode = runwayNode->getChild("ils", j)) != NULL; ++j) {
573 // must match on both nav-ident and runway ident, to support the following:
574 // - runways with multiple distinct ILS installations (KEWD, for example)
575 // - runways where both ends share the same nav ident (LFAT, for example)
576 PositionedID ils = cache->findILS(guid(), ilsNode->getStringValue("rwy"),
577 ilsNode->getStringValue("nav-id"));
579 SG_LOG(SG_GENERAL, SG_INFO, "reading ILS data for " << ident() <<
580 ", couldn;t find runway/navaid for:" <<
581 ilsNode->getStringValue("rwy") << "/" <<
582 ilsNode->getStringValue("nav-id"));
586 double hdgDeg = ilsNode->getDoubleValue("hdg-deg"),
587 lon = ilsNode->getDoubleValue("lon"),
588 lat = ilsNode->getDoubleValue("lat"),
589 elevM = ilsNode->getDoubleValue("elev-m");
591 cache->updateILS(ils, SGGeod::fromDegM(lon, lat, elevM), hdgDeg);
592 } // of ILS iteration
593 } // of runway iteration
596 void FGAirport::addSID(flightgear::SID* aSid)
598 mSIDs.push_back(aSid);
601 void FGAirport::addSTAR(STAR* aStar)
603 mSTARs.push_back(aStar);
606 void FGAirport::addApproach(Approach* aApp)
608 mApproaches.push_back(aApp);
611 unsigned int FGAirport::numSIDs() const
617 flightgear::SID* FGAirport::getSIDByIndex(unsigned int aIndex) const
620 return mSIDs[aIndex];
623 flightgear::SID* FGAirport::findSIDWithIdent(const std::string& aIdent) const
626 for (unsigned int i=0; i<mSIDs.size(); ++i) {
627 if (mSIDs[i]->ident() == aIdent) {
635 unsigned int FGAirport::numSTARs() const
638 return mSTARs.size();
641 STAR* FGAirport::getSTARByIndex(unsigned int aIndex) const
644 return mSTARs[aIndex];
647 STAR* FGAirport::findSTARWithIdent(const std::string& aIdent) const
650 for (unsigned int i=0; i<mSTARs.size(); ++i) {
651 if (mSTARs[i]->ident() == aIdent) {
659 unsigned int FGAirport::numApproaches() const
662 return mApproaches.size();
665 Approach* FGAirport::getApproachByIndex(unsigned int aIndex) const
668 return mApproaches[aIndex];
671 Approach* FGAirport::findApproachWithIdent(const std::string& aIdent) const
674 for (unsigned int i=0; i<mApproaches.size(); ++i) {
675 if (mApproaches[i]->ident() == aIdent) {
676 return mApproaches[i];
684 FGAirport::commStations() const
686 NavDataCache* cache = NavDataCache::instance();
687 CommStationList result;
688 BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(),
689 FGPositioned::FREQ_GROUND,
690 FGPositioned::FREQ_UNICOM))
692 result.push_back((CommStation*) cache->loadById(pos));
699 FGAirport::commStationsOfType(FGPositioned::Type aTy) const
701 NavDataCache* cache = NavDataCache::instance();
702 CommStationList result;
703 BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(), aTy)) {
704 result.push_back((CommStation*) cache->loadById(pos));
710 // get airport elevation
711 double fgGetAirportElev( const string& id )
713 const FGAirport *a=fgFindAirportID( id);
715 return a->getElevation();
722 // get airport position
723 SGGeod fgGetAirportPos( const string& id )
725 const FGAirport *a = fgFindAirportID( id);
728 return SGGeod::fromDegM(a->getLongitude(), a->getLatitude(), a->getElevation());
730 return SGGeod::fromDegM(0.0, 0.0, -9999.0);