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.
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 FGRunway* FGAirport::getRunwayByIndex(unsigned int aIndex) const
138 assert(aIndex >= 0 && aIndex < mRunways.size());
139 return (FGRunway*) flightgear::NavDataCache::instance()->loadById(mRunways[aIndex]);
142 bool FGAirport::hasRunwayWithIdent(const string& aIdent) const
144 return flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::RUNWAY, aIdent) != 0;
147 FGRunway* FGAirport::getRunwayByIdent(const string& aIdent) const
149 PositionedID id = flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::RUNWAY, aIdent);
151 SG_LOG(SG_GENERAL, SG_ALERT, "no such runway '" << aIdent << "' at airport " << ident());
152 throw sg_range_exception("unknown runway " + aIdent + " at airport:" + ident(), "FGAirport::getRunwayByIdent");
155 return (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
159 FGRunway* FGAirport::findBestRunwayForHeading(double aHeading) const
163 FGRunway* result = NULL;
164 double currentBestQuality = 0.0;
166 SGPropertyNode *param = fgGetNode("/sim/airport/runways/search", true);
167 double lengthWeight = param->getDoubleValue("length-weight", 0.01);
168 double widthWeight = param->getDoubleValue("width-weight", 0.01);
169 double surfaceWeight = param->getDoubleValue("surface-weight", 10);
170 double deviationWeight = param->getDoubleValue("deviation-weight", 1);
172 BOOST_FOREACH(PositionedID id, mRunways) {
173 FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
174 double good = rwy->score(lengthWeight, widthWeight, surfaceWeight);
175 double dev = aHeading - rwy->headingDeg();
176 SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
177 double bad = fabs(deviationWeight * dev) + 1e-20;
178 double quality = good / bad;
180 if (quality > currentBestQuality) {
181 currentBestQuality = quality;
189 FGRunway* FGAirport::findBestRunwayForPos(const SGGeod& aPos) const
193 FGRunway* result = NULL;
194 double currentLowestDev = 180.0;
196 BOOST_FOREACH(PositionedID id, mRunways) {
197 FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
199 double inboundCourse = SGGeodesy::courseDeg(aPos, rwy->end());
200 double dev = inboundCourse - rwy->headingDeg();
201 SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
204 if (dev < currentLowestDev) { // new best match
205 currentLowestDev = dev;
208 } // of runway iteration
214 bool FGAirport::hasHardRunwayOfLengthFt(double aLengthFt) const
218 BOOST_FOREACH(PositionedID id, mRunways) {
219 FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
221 if (rwy->isReciprocal()) {
222 continue; // we only care about lengths, so don't do work twice
225 if (rwy->isHardSurface() && (rwy->lengthFt() >= aLengthFt)) {
226 return true; // we're done!
228 } // of runways iteration
233 unsigned int FGAirport::numTaxiways() const
236 return mTaxiways.size();
239 FGTaxiway* FGAirport::getTaxiwayByIndex(unsigned int aIndex) const
243 assert(aIndex >= 0 && aIndex < mTaxiways.size());
244 return (FGTaxiway*) flightgear::NavDataCache::instance()->loadById(mTaxiways[aIndex]);
247 unsigned int FGAirport::numPavements() const
250 return mPavements.size();
253 FGPavement* FGAirport::getPavementByIndex(unsigned int aIndex) const
256 assert(aIndex >= 0 && aIndex < mPavements.size());
257 return (FGPavement*) flightgear::NavDataCache::instance()->loadById(mPavements[aIndex]);
260 FGRunway* FGAirport::getActiveRunwayForUsage() const
262 FGEnvironmentMgr* envMgr = (FGEnvironmentMgr *) globals->get_subsystem("environment");
264 // This forces West-facing rwys to be used in no-wind situations
265 // which is consistent with Flightgear's initial setup.
269 FGEnvironment stationWeather(envMgr->getEnvironment(mPosition));
271 double windSpeed = stationWeather.get_wind_speed_kt();
272 if (windSpeed > 0.0) {
273 hdg = stationWeather.get_wind_from_heading_deg();
277 return findBestRunwayForHeading(hdg);
280 FGAirport* FGAirport::findClosest(const SGGeod& aPos, double aCuttofNm, Filter* filter)
282 AirportFilter aptFilter;
283 if (filter == NULL) {
287 FGPositionedRef r = FGPositioned::findClosest(aPos, aCuttofNm, filter);
292 return static_cast<FGAirport*>(r.ptr());
295 FGAirport::HardSurfaceFilter::HardSurfaceFilter(double minLengthFt) :
296 mMinLengthFt(minLengthFt)
298 if (minLengthFt < 0.0) {
299 mMinLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 0.0);
303 bool FGAirport::HardSurfaceFilter::passAirport(FGAirport* aApt) const
305 return aApt->hasHardRunwayOfLengthFt(mMinLengthFt);
308 FGAirport* FGAirport::findByIdent(const std::string& aIdent)
310 AirportCache::iterator it = airportCache.find(aIdent);
311 if (it != airportCache.end())
315 FGAirport* r = static_cast<FGAirport*> (FGPositioned::findFirstWithIdent(aIdent, &filter).get());
317 // add airport to the cache (even when it's NULL, so we don't need to search in vain again)
318 airportCache[aIdent] = r;
320 // we don't warn here when r==NULL, let the caller do that
324 FGAirport* FGAirport::getByIdent(const std::string& aIdent)
326 FGAirport* r = findByIdent(aIdent);
328 throw sg_range_exception("No such airport with ident: " + aIdent);
332 char** FGAirport::searchNamesAndIdents(const std::string& aFilter)
334 return NavDataCache::instance()->searchAirportNamesAndIdents(aFilter);
337 // find basic airport location info from airport database
338 const FGAirport *fgFindAirportID( const string& id)
344 return FGAirport::findByIdent(id);
347 void FGAirport::loadRunways() const
349 if (mRunwaysLoaded) {
350 return; // already loaded, great
353 loadSceneryDefinitions();
355 mRunwaysLoaded = true;
356 mRunways = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::RUNWAY);
359 void FGAirport::loadTaxiways() const
361 if (mTaxiwaysLoaded) {
362 return; // already loaded, great
365 mTaxiwaysLoaded = true;
366 mTaxiways = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::TAXIWAY);
369 void FGAirport::loadProcedures() const
371 if (mProceduresLoaded) {
375 mProceduresLoaded = true;
377 if (!XMLLoader::findAirportData(ident(), "procedures", path)) {
378 SG_LOG(SG_GENERAL, SG_INFO, "no procedures data available for " << ident());
382 SG_LOG(SG_GENERAL, SG_INFO, ident() << ": loading procedures from " << path.str());
383 RouteBase::loadAirportProcedures(path, const_cast<FGAirport*>(this));
386 void FGAirport::loadSceneryDefinitions() const
388 NavDataCache* cache = NavDataCache::instance();
390 if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
391 return; // no XML threshold data
394 if (!cache->isCachedFileModified(path)) {
395 // cached values are correct, we're all done
400 cache->beginTransaction();
401 SGPropertyNode_ptr rootNode = new SGPropertyNode;
402 readProperties(path.str(), rootNode);
403 const_cast<FGAirport*>(this)->readThresholdData(rootNode);
404 cache->stampCacheFile(path);
405 cache->commitTransaction();
406 } catch (sg_exception& e) {
407 cache->abortTransaction();
412 void FGAirport::readThresholdData(SGPropertyNode* aRoot)
414 SGPropertyNode* runway;
416 for (; (runway = aRoot->getChild("runway", runwayIndex)) != NULL; ++runwayIndex) {
417 SGPropertyNode* t0 = runway->getChild("threshold", 0),
418 *t1 = runway->getChild("threshold", 1);
420 assert(t1); // too strict? maybe we should finally allow single-ended runways
422 processThreshold(t0);
423 processThreshold(t1);
424 } // of runways iteration
427 void FGAirport::processThreshold(SGPropertyNode* aThreshold)
429 // first, let's identify the current runway
430 string rwyIdent(aThreshold->getStringValue("rwy"));
431 NavDataCache* cache = NavDataCache::instance();
432 PositionedID id = cache->airportItemWithIdent(guid(), FGPositioned::RUNWAY, rwyIdent);
434 SG_LOG(SG_GENERAL, SG_DEBUG, "FGAirport::processThreshold: "
435 "found runway not defined in the global data:" << ident() << "/" << rwyIdent);
439 double lon = aThreshold->getDoubleValue("lon"),
440 lat = aThreshold->getDoubleValue("lat");
441 SGGeod newThreshold(SGGeod::fromDegM(lon, lat, mPosition.getElevationM()));
443 double newHeading = aThreshold->getDoubleValue("hdg-deg");
444 double newDisplacedThreshold = aThreshold->getDoubleValue("displ-m") * SG_METER_TO_FEET;
445 double newStopway = aThreshold->getDoubleValue("stopw-m") * SG_METER_TO_FEET;
447 cache->updateRunwayThreshold(id, newThreshold,
448 newHeading, newDisplacedThreshold, newStopway);
451 SGGeod FGAirport::getTowerLocation() const
455 NavDataCache* cache = NavDataCache::instance();
456 PositionedIDVec towers = cache->airportItemsOfType(guid(), FGPositioned::TOWER);
457 if (towers.empty()) {
458 SG_LOG(SG_GENERAL, SG_ALERT, "No towers defined for:" <<ident());
462 FGPositionedRef tower = cache->loadById(towers.front());
463 return tower->geod();
466 void FGAirport::validateTowerData() const
468 if (mTowerDataLoaded) {
472 mTowerDataLoaded = true;
473 NavDataCache* cache = NavDataCache::instance();
475 if (!XMLLoader::findAirportData(ident(), "twr", path)) {
476 return; // no XML tower data
479 if (!cache->isCachedFileModified(path)) {
480 // cached values are correct, we're all done
484 SGPropertyNode_ptr rootNode = new SGPropertyNode;
485 readProperties(path.str(), rootNode);
486 const_cast<FGAirport*>(this)->readTowerData(rootNode);
487 cache->stampCacheFile(path);
490 void FGAirport::readTowerData(SGPropertyNode* aRoot)
492 SGPropertyNode* twrNode = aRoot->getChild("tower")->getChild("twr");
493 double lat = twrNode->getDoubleValue("lat"),
494 lon = twrNode->getDoubleValue("lon"),
495 elevM = twrNode->getDoubleValue("elev-m");
496 // tower elevation is AGL, not AMSL. Since we don't want to depend on the
497 // scenery for a precise terrain elevation, we use the field elevation
498 // (this is also what the apt.dat code does)
499 double fieldElevationM = geod().getElevationM();
500 SGGeod towerLocation(SGGeod::fromDegM(lon, lat, fieldElevationM + elevM));
502 NavDataCache* cache = NavDataCache::instance();
503 PositionedIDVec towers = cache->airportItemsOfType(guid(), FGPositioned::TOWER);
504 if (towers.empty()) {
505 cache->insertTower(guid(), towerLocation);
507 // update the position
508 cache->updatePosition(towers.front(), towerLocation);
512 bool FGAirport::validateILSData()
514 if (mILSDataLoaded) {
518 mILSDataLoaded = true;
519 NavDataCache* cache = NavDataCache::instance();
521 if (!XMLLoader::findAirportData(ident(), "ils", path)) {
522 return false; // no XML tower data
525 if (!cache->isCachedFileModified(path)) {
526 // cached values are correct, we're all done
530 SGPropertyNode_ptr rootNode = new SGPropertyNode;
531 readProperties(path.str(), rootNode);
532 readILSData(rootNode);
533 cache->stampCacheFile(path);
535 // we loaded data, tell the caller it might need to reload things
539 void FGAirport::readILSData(SGPropertyNode* aRoot)
541 NavDataCache* cache = NavDataCache::instance();
543 // find the entry matching the runway
544 SGPropertyNode* runwayNode, *ilsNode;
545 for (int i=0; (runwayNode = aRoot->getChild("runway", i)) != NULL; ++i) {
546 for (int j=0; (ilsNode = runwayNode->getChild("ils", j)) != NULL; ++j) {
547 // must match on both nav-ident and runway ident, to support the following:
548 // - runways with multiple distinct ILS installations (KEWD, for example)
549 // - runways where both ends share the same nav ident (LFAT, for example)
550 PositionedID ils = cache->findILS(guid(), ilsNode->getStringValue("rwy"),
551 ilsNode->getStringValue("nav-id"));
553 SG_LOG(SG_GENERAL, SG_INFO, "reading ILS data for " << ident() <<
554 ", couldn;t find runway/navaid for:" <<
555 ilsNode->getStringValue("rwy") << "/" <<
556 ilsNode->getStringValue("nav-id"));
560 double hdgDeg = ilsNode->getDoubleValue("hdg-deg"),
561 lon = ilsNode->getDoubleValue("lon"),
562 lat = ilsNode->getDoubleValue("lat"),
563 elevM = ilsNode->getDoubleValue("elev-m");
565 cache->updateILS(ils, SGGeod::fromDegM(lon, lat, elevM), hdgDeg);
566 } // of ILS iteration
567 } // of runway iteration
570 void FGAirport::addSID(flightgear::SID* aSid)
572 mSIDs.push_back(aSid);
575 void FGAirport::addSTAR(STAR* aStar)
577 mSTARs.push_back(aStar);
580 void FGAirport::addApproach(Approach* aApp)
582 mApproaches.push_back(aApp);
585 unsigned int FGAirport::numSIDs() const
591 flightgear::SID* FGAirport::getSIDByIndex(unsigned int aIndex) const
594 return mSIDs[aIndex];
597 flightgear::SID* FGAirport::findSIDWithIdent(const std::string& aIdent) const
600 for (unsigned int i=0; i<mSIDs.size(); ++i) {
601 if (mSIDs[i]->ident() == aIdent) {
609 unsigned int FGAirport::numSTARs() const
612 return mSTARs.size();
615 STAR* FGAirport::getSTARByIndex(unsigned int aIndex) const
618 return mSTARs[aIndex];
621 STAR* FGAirport::findSTARWithIdent(const std::string& aIdent) const
624 for (unsigned int i=0; i<mSTARs.size(); ++i) {
625 if (mSTARs[i]->ident() == aIdent) {
633 unsigned int FGAirport::numApproaches() const
636 return mApproaches.size();
639 Approach* FGAirport::getApproachByIndex(unsigned int aIndex) const
642 return mApproaches[aIndex];
645 Approach* FGAirport::findApproachWithIdent(const std::string& aIdent) const
648 for (unsigned int i=0; i<mApproaches.size(); ++i) {
649 if (mApproaches[i]->ident() == aIdent) {
650 return mApproaches[i];
658 FGAirport::commStations() const
660 NavDataCache* cache = NavDataCache::instance();
661 CommStationList result;
662 BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(),
663 FGPositioned::FREQ_GROUND,
664 FGPositioned::FREQ_UNICOM))
666 result.push_back((CommStation*) cache->loadById(pos));
673 FGAirport::commStationsOfType(FGPositioned::Type aTy) const
675 NavDataCache* cache = NavDataCache::instance();
676 CommStationList result;
677 BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(), aTy)) {
678 result.push_back((CommStation*) cache->loadById(pos));
684 // get airport elevation
685 double fgGetAirportElev( const string& id )
687 const FGAirport *a=fgFindAirportID( id);
689 return a->getElevation();
696 // get airport position
697 SGGeod fgGetAirportPos( const string& id )
699 const FGAirport *a = fgFindAirportID( id);
702 return SGGeod::fromDegM(a->getLongitude(), a->getLatitude(), a->getElevation());
704 return SGGeod::fromDegM(0.0, 0.0, -9999.0);