]> git.mxchange.org Git - flightgear.git/blob - src/Airports/airport.cxx
Give the FGAirport class a sane filename.
[flightgear.git] / src / Airports / airport.cxx
1 //
2 // simple.cxx -- a really simplistic class to manage airport ID,
3 //               lat, lon of the center of one of it's runways, and
4 //               elevation in feet.
5 //
6 // Written by Curtis Olson, started April 1998.
7 // Updated by Durk Talsma, started December, 2004.
8 //
9 // Copyright (C) 1998  Curtis L. Olson  - http://www.flightgear.org/~curt
10 //
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.
15 //
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.
20 //
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.
24 //
25 // $Id$
26
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30
31 #include "airport.hxx"
32
33 #include <cassert>
34 #include <boost/foreach.hpp>
35
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>
42
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>
54
55 using std::vector;
56 using std::pair;
57
58 using namespace flightgear;
59
60
61 /***************************************************************************
62  * FGAirport
63  ***************************************************************************/
64
65 AirportCache FGAirport::airportCache;
66
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),
70     _name(name),
71     _has_metar(has_metar),
72     _dynamics(0),
73     mTowerDataLoaded(false),
74     mRunwaysLoaded(false),
75     mTaxiwaysLoaded(false),
76     mProceduresLoaded(false),
77     mILSDataLoaded(false)
78 {
79 }
80
81
82 FGAirport::~FGAirport()
83 {
84     delete _dynamics;
85 }
86
87 bool FGAirport::isAirport() const
88 {
89   return type() == AIRPORT;
90 }
91
92 bool FGAirport::isSeaport() const
93 {
94   return type() == SEAPORT;
95 }
96
97 bool FGAirport::isHeliport() const
98 {
99   return type() == HELIPORT;
100 }
101
102 bool FGAirport::isAirportType(FGPositioned* pos)
103 {
104     if (!pos) {
105         return false;
106     }
107     
108     return (pos->type() >= AIRPORT) && (pos->type() <= SEAPORT);
109 }
110
111 FGAirportDynamics * FGAirport::getDynamics()
112 {
113     if (_dynamics) {
114         return _dynamics;
115     }
116     
117     _dynamics = new FGAirportDynamics(this);
118     XMLLoader::load(_dynamics);
119     _dynamics->init();
120   
121     FGRunwayPreference rwyPrefs(this);
122     XMLLoader::load(&rwyPrefs);
123     _dynamics->setRwyUse(rwyPrefs);
124     
125     return _dynamics;
126 }
127
128 unsigned int FGAirport::numRunways() const
129 {
130   loadRunways();
131   return mRunways.size();
132 }
133
134 unsigned int FGAirport::numHelipads() const
135 {
136   loadHelipads();
137   return mHelipads.size();
138 }
139
140 FGRunway* FGAirport::getRunwayByIndex(unsigned int aIndex) const
141 {
142   loadRunways();
143   
144   assert(aIndex >= 0 && aIndex < mRunways.size());
145   return (FGRunway*) flightgear::NavDataCache::instance()->loadById(mRunways[aIndex]);
146 }
147
148 FGHelipad* FGAirport::getHelipadByIndex(unsigned int aIndex) const
149 {
150   loadHelipads();
151
152   assert(aIndex >= 0 && aIndex < mHelipads.size());
153   return (FGHelipad*) flightgear::NavDataCache::instance()->loadById(mHelipads[aIndex]);
154 }
155
156 bool FGAirport::hasRunwayWithIdent(const string& aIdent) const
157 {
158   return flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::RUNWAY, aIdent) != 0;
159 }
160
161 FGRunway* FGAirport::getRunwayByIdent(const string& aIdent) const
162 {
163   PositionedID id = flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::RUNWAY, aIdent);  
164   if (id == 0) {
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");
167   }
168   
169   return (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
170 }
171
172
173 FGRunway* FGAirport::findBestRunwayForHeading(double aHeading) const
174 {
175   loadRunways();
176   
177   FGRunway* result = NULL;
178   double currentBestQuality = 0.0;
179   
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);
185     
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;
193     
194     if (quality > currentBestQuality) {
195       currentBestQuality = quality;
196       result = rwy;
197     }
198   }
199
200   return result;
201 }
202
203 FGRunway* FGAirport::findBestRunwayForPos(const SGGeod& aPos) const
204 {
205   loadRunways();
206   
207   FGRunway* result = NULL;
208   double currentLowestDev = 180.0;
209   
210   BOOST_FOREACH(PositionedID id, mRunways) {
211     FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
212
213     double inboundCourse = SGGeodesy::courseDeg(aPos, rwy->end());
214     double dev = inboundCourse - rwy->headingDeg();
215     SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
216
217     dev = fabs(dev);
218     if (dev < currentLowestDev) { // new best match
219       currentLowestDev = dev;
220       result = rwy;
221     }
222   } // of runway iteration
223   
224   return result;
225
226 }
227
228 bool FGAirport::hasHardRunwayOfLengthFt(double aLengthFt) const
229 {
230   loadRunways();
231   
232   BOOST_FOREACH(PositionedID id, mRunways) {
233     FGRunway* rwy = (FGRunway*) flightgear::NavDataCache::instance()->loadById(id);
234
235     if (rwy->isReciprocal()) {
236       continue; // we only care about lengths, so don't do work twice
237     }
238
239     if (rwy->isHardSurface() && (rwy->lengthFt() >= aLengthFt)) {
240       return true; // we're done!
241     }
242   } // of runways iteration
243
244   return false;
245 }
246
247 unsigned int FGAirport::numTaxiways() const
248 {
249   loadTaxiways();
250   return mTaxiways.size();
251 }
252
253 FGTaxiway* FGAirport::getTaxiwayByIndex(unsigned int aIndex) const
254 {
255   loadTaxiways();
256   
257   assert(aIndex >= 0 && aIndex < mTaxiways.size());
258   return (FGTaxiway*) flightgear::NavDataCache::instance()->loadById(mTaxiways[aIndex]);
259 }
260
261 unsigned int FGAirport::numPavements() const
262 {
263   loadTaxiways();
264   return mPavements.size();
265 }
266
267 FGPavement* FGAirport::getPavementByIndex(unsigned int aIndex) const
268 {
269   loadTaxiways();
270   assert(aIndex >= 0 && aIndex < mPavements.size());
271   return (FGPavement*) flightgear::NavDataCache::instance()->loadById(mPavements[aIndex]);
272 }
273
274 FGRunway* FGAirport::getActiveRunwayForUsage() const
275 {
276   FGEnvironmentMgr* envMgr = (FGEnvironmentMgr *) globals->get_subsystem("environment");
277   
278   // This forces West-facing rwys to be used in no-wind situations
279   // which is consistent with Flightgear's initial setup.
280   double hdg = 270;
281   
282   if (envMgr) {
283     FGEnvironment stationWeather(envMgr->getEnvironment(mPosition));
284   
285     double windSpeed = stationWeather.get_wind_speed_kt();
286     if (windSpeed > 0.0) {
287       hdg = stationWeather.get_wind_from_heading_deg();
288     }
289   }
290   
291   return findBestRunwayForHeading(hdg);
292 }
293
294 FGAirport* FGAirport::findClosest(const SGGeod& aPos, double aCuttofNm, Filter* filter)
295 {
296   AirportFilter aptFilter;
297   if (filter == NULL) {
298     filter = &aptFilter;
299   }
300   
301   FGPositionedRef r = FGPositioned::findClosest(aPos, aCuttofNm, filter);
302   if (!r) {
303     return NULL;
304   }
305   
306   return static_cast<FGAirport*>(r.ptr());
307 }
308
309 FGAirport::HardSurfaceFilter::HardSurfaceFilter(double minLengthFt) :
310   mMinLengthFt(minLengthFt)
311 {
312   if (minLengthFt < 0.0) {
313     mMinLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 0.0);
314   }
315 }
316       
317 bool FGAirport::HardSurfaceFilter::passAirport(FGAirport* aApt) const
318 {
319   return aApt->hasHardRunwayOfLengthFt(mMinLengthFt);
320 }
321
322 FGAirport* FGAirport::findByIdent(const std::string& aIdent)
323 {
324   AirportCache::iterator it = airportCache.find(aIdent);
325   if (it != airportCache.end())
326    return it->second;
327
328   PortsFilter filter;
329   FGAirport* r = static_cast<FGAirport*> (FGPositioned::findFirstWithIdent(aIdent, &filter).get());
330
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;
333
334   // we don't warn here when r==NULL, let the caller do that
335   return r;
336 }
337
338 FGAirport* FGAirport::getByIdent(const std::string& aIdent)
339 {
340   FGAirport* r = findByIdent(aIdent);
341   if (!r)
342     throw sg_range_exception("No such airport with ident: " + aIdent);
343   return r;
344 }
345
346 char** FGAirport::searchNamesAndIdents(const std::string& aFilter)
347 {
348   return NavDataCache::instance()->searchAirportNamesAndIdents(aFilter);
349 }
350
351 // find basic airport location info from airport database
352 const FGAirport *fgFindAirportID( const string& id)
353 {
354     if ( id.empty() ) {
355         return NULL;
356     }
357     
358     return FGAirport::findByIdent(id);
359 }
360
361 void FGAirport::loadRunways() const
362 {
363   if (mRunwaysLoaded) {
364     return; // already loaded, great
365   }
366   
367   loadSceneryDefinitions();
368   
369   mRunwaysLoaded = true;
370   mRunways = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::RUNWAY);
371 }
372
373 void FGAirport::loadHelipads() const
374 {
375   if (mHelipadsLoaded) {
376     return; // already loaded, great
377   }
378
379   loadSceneryDefinitions();
380
381   mHelipadsLoaded = true;
382   mHelipads = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::HELIPAD);
383 }
384
385 void FGAirport::loadTaxiways() const
386 {
387   if (mTaxiwaysLoaded) {
388     return; // already loaded, great
389   }
390   
391   mTaxiwaysLoaded =  true;
392   mTaxiways = flightgear::NavDataCache::instance()->airportItemsOfType(guid(), FGPositioned::TAXIWAY);
393 }
394
395 void FGAirport::loadProcedures() const
396 {
397   if (mProceduresLoaded) {
398     return;
399   }
400   
401   mProceduresLoaded = true;
402   SGPath path;
403   if (!XMLLoader::findAirportData(ident(), "procedures", path)) {
404     SG_LOG(SG_GENERAL, SG_INFO, "no procedures data available for " << ident());
405     return;
406   }
407   
408   SG_LOG(SG_GENERAL, SG_INFO, ident() << ": loading procedures from " << path.str());
409   RouteBase::loadAirportProcedures(path, const_cast<FGAirport*>(this));
410 }
411
412 void FGAirport::loadSceneryDefinitions() const
413 {
414   NavDataCache* cache = NavDataCache::instance();
415   SGPath path;
416   if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
417     return; // no XML threshold data
418   }
419   
420   if (!cache->isCachedFileModified(path)) {
421     // cached values are correct, we're all done
422     return;
423   }
424   
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);
430     txn.commit();
431 }
432
433 void FGAirport::readThresholdData(SGPropertyNode* aRoot)
434 {
435   SGPropertyNode* runway;
436   int runwayIndex = 0;
437   for (; (runway = aRoot->getChild("runway", runwayIndex)) != NULL; ++runwayIndex) {
438     SGPropertyNode* t0 = runway->getChild("threshold", 0),
439       *t1 = runway->getChild("threshold", 1);
440     assert(t0);
441     assert(t1); // too strict? maybe we should finally allow single-ended runways
442     
443     processThreshold(t0);
444     processThreshold(t1);
445   } // of runways iteration
446 }
447
448 void FGAirport::processThreshold(SGPropertyNode* aThreshold)
449 {
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);
454   if (id == 0) {
455     SG_LOG(SG_GENERAL, SG_DEBUG, "FGAirport::processThreshold: "
456            "found runway not defined in the global data:" << ident() << "/" << rwyIdent);
457     return;
458   }
459   
460   double lon = aThreshold->getDoubleValue("lon"),
461   lat = aThreshold->getDoubleValue("lat");
462   SGGeod newThreshold(SGGeod::fromDegM(lon, lat, mPosition.getElevationM()));
463   
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;
467   
468   cache->updateRunwayThreshold(id, newThreshold,
469                                newHeading, newDisplacedThreshold, newStopway);
470 }
471
472 SGGeod FGAirport::getTowerLocation() const
473 {
474   validateTowerData();
475   
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());
480     return SGGeod();
481   }
482   
483   FGPositionedRef tower = cache->loadById(towers.front());
484   return tower->geod();
485 }
486
487 void FGAirport::validateTowerData() const
488 {
489   if (mTowerDataLoaded) {
490     return;
491   }
492
493   mTowerDataLoaded = true;
494   NavDataCache* cache = NavDataCache::instance();
495   SGPath path;
496   if (!XMLLoader::findAirportData(ident(), "twr", path)) {
497     return; // no XML tower data
498   }
499   
500   if (!cache->isCachedFileModified(path)) {
501   // cached values are correct, we're all done
502     return;
503   }
504    
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);
510   txn.commit();
511 }
512
513 void FGAirport::readTowerData(SGPropertyNode* aRoot)
514 {
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));
524   
525   NavDataCache* cache = NavDataCache::instance();
526   PositionedIDVec towers = cache->airportItemsOfType(guid(), FGPositioned::TOWER);
527   if (towers.empty()) {
528     cache->insertTower(guid(), towerLocation);
529   } else {
530     // update the position
531     cache->updatePosition(towers.front(), towerLocation);
532   }
533 }
534
535 bool FGAirport::validateILSData()
536 {
537   if (mILSDataLoaded) {
538     return false;
539   }
540   
541   mILSDataLoaded = true;
542   NavDataCache* cache = NavDataCache::instance();
543   SGPath path;
544   if (!XMLLoader::findAirportData(ident(), "ils", path)) {
545     return false; // no XML tower data
546   }
547   
548   if (!cache->isCachedFileModified(path)) {
549     // cached values are correct, we're all done
550     return false;
551   }
552   
553   SGPropertyNode_ptr rootNode = new SGPropertyNode;
554   readProperties(path.str(), rootNode);
555
556   flightgear::NavDataCache::Transaction txn(cache);
557   readILSData(rootNode);
558   cache->stampCacheFile(path);
559   txn.commit();
560     
561 // we loaded data, tell the caller it might need to reload things
562   return true;
563 }
564
565 void FGAirport::readILSData(SGPropertyNode* aRoot)
566 {
567   NavDataCache* cache = NavDataCache::instance();
568   
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"));
578       if (ils == 0) {
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"));
583         continue;
584       }
585       
586       double hdgDeg = ilsNode->getDoubleValue("hdg-deg"),
587         lon = ilsNode->getDoubleValue("lon"),
588         lat = ilsNode->getDoubleValue("lat"),
589         elevM = ilsNode->getDoubleValue("elev-m");
590  
591       cache->updateILS(ils, SGGeod::fromDegM(lon, lat, elevM), hdgDeg);
592     } // of ILS iteration
593   } // of runway iteration
594 }
595
596 void FGAirport::addSID(flightgear::SID* aSid)
597 {
598   mSIDs.push_back(aSid);
599 }
600
601 void FGAirport::addSTAR(STAR* aStar)
602 {
603   mSTARs.push_back(aStar);
604 }
605
606 void FGAirport::addApproach(Approach* aApp)
607 {
608   mApproaches.push_back(aApp);
609 }
610
611 unsigned int FGAirport::numSIDs() const
612 {
613   loadProcedures();
614   return mSIDs.size();
615 }
616
617 flightgear::SID* FGAirport::getSIDByIndex(unsigned int aIndex) const
618 {
619   loadProcedures();
620   return mSIDs[aIndex];
621 }
622
623 flightgear::SID* FGAirport::findSIDWithIdent(const std::string& aIdent) const
624 {
625   loadProcedures();
626   for (unsigned int i=0; i<mSIDs.size(); ++i) {
627     if (mSIDs[i]->ident() == aIdent) {
628       return mSIDs[i];
629     }
630   }
631   
632   return NULL;
633 }
634
635 unsigned int FGAirport::numSTARs() const
636 {
637   loadProcedures();
638   return mSTARs.size();
639 }
640
641 STAR* FGAirport::getSTARByIndex(unsigned int aIndex) const
642 {
643   loadProcedures();
644   return mSTARs[aIndex];
645 }
646
647 STAR* FGAirport::findSTARWithIdent(const std::string& aIdent) const
648 {
649   loadProcedures();
650   for (unsigned int i=0; i<mSTARs.size(); ++i) {
651     if (mSTARs[i]->ident() == aIdent) {
652       return mSTARs[i];
653     }
654   }
655   
656   return NULL;
657 }
658
659 unsigned int FGAirport::numApproaches() const
660 {
661   loadProcedures();
662   return mApproaches.size();
663 }
664
665 Approach* FGAirport::getApproachByIndex(unsigned int aIndex) const
666 {
667   loadProcedures();
668   return mApproaches[aIndex];
669 }
670
671 Approach* FGAirport::findApproachWithIdent(const std::string& aIdent) const
672 {
673   loadProcedures();
674   for (unsigned int i=0; i<mApproaches.size(); ++i) {
675     if (mApproaches[i]->ident() == aIdent) {
676       return mApproaches[i];
677     }
678   }
679   
680   return NULL;
681 }
682
683 CommStationList
684 FGAirport::commStations() const
685 {
686   NavDataCache* cache = NavDataCache::instance();
687   CommStationList result;
688   BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(),
689                                                             FGPositioned::FREQ_GROUND,
690                                                             FGPositioned::FREQ_UNICOM))
691   {
692     result.push_back((CommStation*) cache->loadById(pos));
693   }
694   
695   return result;
696 }
697
698 CommStationList
699 FGAirport::commStationsOfType(FGPositioned::Type aTy) const
700 {
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));
705   }
706   
707   return result;
708 }
709
710 // get airport elevation
711 double fgGetAirportElev( const string& id )
712 {
713     const FGAirport *a=fgFindAirportID( id);
714     if (a) {
715         return a->getElevation();
716     } else {
717         return -9999.0;
718     }
719 }
720
721
722 // get airport position
723 SGGeod fgGetAirportPos( const string& id )
724 {
725     const FGAirport *a = fgFindAirportID( id);
726
727     if (a) {
728         return SGGeod::fromDegM(a->getLongitude(), a->getLatitude(), a->getElevation());
729     } else {
730         return SGGeod::fromDegM(0.0, 0.0, -9999.0);
731     }
732 }