]> git.mxchange.org Git - flightgear.git/blob - src/Airports/airport.cxx
Interim windows build fix
[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 <algorithm>
34 #include <cassert>
35 #include <boost/foreach.hpp>
36
37 #include <simgear/misc/sg_path.hxx>
38 #include <simgear/props/props.hxx>
39 #include <simgear/props/props_io.hxx>
40 #include <simgear/debug/logstream.hxx>
41 #include <simgear/sg_inlines.h>
42 #include <simgear/structure/exception.hxx>
43
44 #include <Environment/environment_mgr.hxx>
45 #include <Environment/environment.hxx>
46 #include <Main/fg_props.hxx>
47 #include <Airports/runways.hxx>
48 #include <Airports/pavement.hxx>
49 #include <Airports/xmlloader.hxx>
50 #include <Airports/dynamics.hxx>
51 #include <Airports/airportdynamicsmanager.hxx>
52 #include <Navaids/procedure.hxx>
53 #include <Navaids/waypoint.hxx>
54 #include <ATC/CommStation.hxx>
55 #include <Navaids/NavDataCache.hxx>
56 #include <Navaids/navrecord.hxx>
57
58 using std::vector;
59 using std::pair;
60
61 using namespace flightgear;
62
63 /***************************************************************************
64  * FGAirport
65  ***************************************************************************/
66
67 AirportCache FGAirport::airportCache;
68
69 FGAirport::FGAirport( PositionedID aGuid,
70                       const std::string &id,
71                       const SGGeod& location,
72                       const std::string &name,
73                       bool has_metar,
74                       Type aType ):
75     FGPositioned(aGuid, aType, id, location),
76     _name(name),
77     _has_metar(has_metar),
78     mTowerDataLoaded(false),
79     mHasTower(false),
80     mRunwaysLoaded(false),
81     mHelipadsLoaded(false),
82     mTaxiwaysLoaded(false),
83     mProceduresLoaded(false),
84     mThresholdDataLoaded(false),
85     mILSDataLoaded(false)
86 {
87     mIsClosed = (name.find("[x]") != std::string::npos);
88 }
89
90
91 FGAirport::~FGAirport()
92 {
93 }
94
95 bool FGAirport::isAirport() const
96 {
97   return type() == AIRPORT;
98 }
99
100 bool FGAirport::isSeaport() const
101 {
102   return type() == SEAPORT;
103 }
104
105 bool FGAirport::isHeliport() const
106 {
107   return type() == HELIPORT;
108 }
109
110 bool FGAirport::isAirportType(FGPositioned* pos)
111 {
112     if (!pos) {
113         return false;
114     }
115     
116     return (pos->type() >= AIRPORT) && (pos->type() <= SEAPORT);
117 }
118
119 //------------------------------------------------------------------------------
120 unsigned int FGAirport::numRunways() const
121 {
122   loadRunways();
123   return mRunways.size();
124 }
125
126 //------------------------------------------------------------------------------
127 unsigned int FGAirport::numHelipads() const
128 {
129   loadHelipads();
130   return mHelipads.size();
131 }
132
133 //------------------------------------------------------------------------------
134 FGRunwayRef FGAirport::getRunwayByIndex(unsigned int aIndex) const
135 {
136   loadRunways();
137   return mRunways.at(aIndex);
138 }
139
140 //------------------------------------------------------------------------------
141 FGHelipadRef FGAirport::getHelipadByIndex(unsigned int aIndex) const
142 {
143   loadHelipads();
144   return loadById<FGHelipad>(mHelipads, aIndex);
145 }
146
147 //------------------------------------------------------------------------------
148 FGRunwayMap FGAirport::getRunwayMap() const
149 {
150   loadRunways();
151   FGRunwayMap map;
152
153   double minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft");
154
155   BOOST_FOREACH(FGRunwayRef rwy, mRunways)
156   {
157     // ignore unusably short runways
158     // TODO other methods don't check this...
159     if( rwy->lengthFt() >= minLengthFt )
160       map[ rwy->ident() ] = rwy;
161   }
162
163   return map;
164 }
165
166 //------------------------------------------------------------------------------
167 FGHelipadMap FGAirport::getHelipadMap() const
168 {
169   loadHelipads();
170   FGHelipadMap map;
171
172   BOOST_FOREACH(PositionedID id, mHelipads)
173   {
174     FGHelipad* rwy = loadById<FGHelipad>(id);
175     map[ rwy->ident() ] = rwy;
176   }
177
178   return map;
179 }
180
181 //------------------------------------------------------------------------------
182 bool FGAirport::hasRunwayWithIdent(const std::string& aIdent) const
183 {
184   loadRunways();
185   BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
186     if (rwy->ident() == aIdent) {
187       return true;
188     }
189   }
190
191   return false;
192 }
193
194 //------------------------------------------------------------------------------
195 bool FGAirport::hasHelipadWithIdent(const std::string& aIdent) const
196 {
197   return flightgear::NavDataCache::instance()
198     ->airportItemWithIdent(guid(), FGPositioned::HELIPAD, aIdent) != 0;
199 }
200
201 //------------------------------------------------------------------------------
202 FGRunwayRef FGAirport::getRunwayByIdent(const std::string& aIdent) const
203 {
204   loadRunways();
205   BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
206     if (rwy->ident() == aIdent) {
207       return rwy;
208     }
209   }
210   
211   SG_LOG(SG_GENERAL, SG_ALERT, "no such runway '" << aIdent << "' at airport " << ident());
212   throw sg_range_exception("unknown runway " + aIdent + " at airport:" + ident(), "FGAirport::getRunwayByIdent");
213 }
214
215 //------------------------------------------------------------------------------
216 FGHelipadRef FGAirport::getHelipadByIdent(const std::string& aIdent) const
217 {
218   PositionedID id = flightgear::NavDataCache::instance()->airportItemWithIdent(guid(), FGPositioned::HELIPAD, aIdent);
219   if (id == 0) {
220     SG_LOG(SG_GENERAL, SG_ALERT, "no such helipad '" << aIdent << "' at airport " << ident());
221     throw sg_range_exception("unknown helipad " + aIdent + " at airport:" + ident(), "FGAirport::getRunwayByIdent");
222   }
223
224   return loadById<FGHelipad>(id);
225 }
226
227 //------------------------------------------------------------------------------
228 FGRunwayRef FGAirport::findBestRunwayForHeading(double aHeading, struct FindBestRunwayForHeadingParams * parms ) const
229 {
230   loadRunways();
231   
232   FGRunway* result = NULL;
233   double currentBestQuality = 0.0;
234   
235   struct FindBestRunwayForHeadingParams fbrfhp;
236   if( NULL != parms ) fbrfhp = *parms;
237
238   SGPropertyNode_ptr searchNode = fgGetNode("/sim/airport/runways/search");
239   if( searchNode.valid() ) {
240     fbrfhp.lengthWeight = searchNode->getDoubleValue("length-weight", fbrfhp.lengthWeight );
241     fbrfhp.widthWeight = searchNode->getDoubleValue("width-weight", fbrfhp.widthWeight );
242     fbrfhp.surfaceWeight = searchNode->getDoubleValue("surface-weight", fbrfhp.surfaceWeight );
243     fbrfhp.deviationWeight = searchNode->getDoubleValue("deviation-weight", fbrfhp.deviationWeight );
244     fbrfhp.ilsWeight = searchNode->getDoubleValue("ils-weight", fbrfhp.ilsWeight );
245   }
246     
247   BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
248     double good = rwy->score( fbrfhp.lengthWeight,  fbrfhp.widthWeight,  fbrfhp.surfaceWeight,  fbrfhp.ilsWeight );
249     double dev = aHeading - rwy->headingDeg();
250     SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
251     double bad = fabs( fbrfhp.deviationWeight * dev) + 1e-20;
252     double quality = good / bad;
253     
254     if (quality > currentBestQuality) {
255       currentBestQuality = quality;
256       result = rwy;
257     }
258   }
259
260   return result;
261 }
262
263 //------------------------------------------------------------------------------
264 FGRunwayRef FGAirport::findBestRunwayForPos(const SGGeod& aPos) const
265 {
266   loadRunways();
267   
268   FGRunway* result = NULL;
269   double currentLowestDev = 180.0;
270   
271   BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
272     double inboundCourse = SGGeodesy::courseDeg(aPos, rwy->end());
273     double dev = inboundCourse - rwy->headingDeg();
274     SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
275
276     dev = fabs(dev);
277     if (dev < currentLowestDev) { // new best match
278       currentLowestDev = dev;
279       result = rwy;
280     }
281   } // of runway iteration
282   
283   return result;
284
285 }
286
287 //------------------------------------------------------------------------------
288 bool FGAirport::hasHardRunwayOfLengthFt(double aLengthFt) const
289 {
290   loadRunways();
291   
292   BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
293     if (rwy->isHardSurface() && (rwy->lengthFt() >= aLengthFt)) {
294       return true; // we're done!
295     }
296   } // of runways iteration
297
298   return false;
299 }
300
301 FGRunwayRef FGAirport::longestRunway() const
302 {
303     FGRunwayRef r;
304     loadRunways();
305
306     BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
307         if (!r || (r->lengthFt() < rwy->lengthFt())) {
308              r = rwy;
309         }
310     } // of runways iteration
311
312     return r;
313 }
314
315 //------------------------------------------------------------------------------
316 FGRunwayList FGAirport::getRunways() const
317 {
318   loadRunways();
319
320   return mRunways;
321 }
322
323 //------------------------------------------------------------------------------
324 FGRunwayList FGAirport::getRunwaysWithoutReciprocals() const
325 {
326   loadRunways();
327   
328   FGRunwayList r;
329   
330   BOOST_FOREACH(FGRunwayRef rwy, mRunways) {
331     FGRunway* recip = rwy->reciprocalRunway();
332     if (recip) {
333       FGRunwayList::iterator it = std::find(r.begin(), r.end(), recip);
334       if (it != r.end()) {
335         continue; // reciprocal already in result set, don't include us
336       }
337     }
338     
339     r.push_back(rwy);
340   }
341   
342   return r;
343 }
344
345 //------------------------------------------------------------------------------
346 unsigned int FGAirport::numTaxiways() const
347 {
348   loadTaxiways();
349   return mTaxiways.size();
350 }
351
352 //------------------------------------------------------------------------------
353 FGTaxiwayRef FGAirport::getTaxiwayByIndex(unsigned int aIndex) const
354 {
355   loadTaxiways();
356   return loadById<FGTaxiway>(mTaxiways, aIndex);
357 }
358
359 //------------------------------------------------------------------------------
360 FGTaxiwayList FGAirport::getTaxiways() const
361 {
362   loadTaxiways();
363   return loadAllById<FGTaxiway>(mTaxiways);
364 }
365
366 //------------------------------------------------------------------------------
367 unsigned int FGAirport::numPavements() const
368 {
369   loadTaxiways();
370   return mPavements.size();
371 }
372
373 //------------------------------------------------------------------------------
374 FGPavementRef FGAirport::getPavementByIndex(unsigned int aIndex) const
375 {
376   loadTaxiways();
377   return loadById<FGPavement>(mPavements, aIndex);
378 }
379
380 //------------------------------------------------------------------------------
381 FGPavementList FGAirport::getPavements() const
382 {
383   loadTaxiways();
384   return loadAllById<FGPavement>(mPavements);
385 }
386
387 //------------------------------------------------------------------------------
388 FGRunwayRef FGAirport::getActiveRunwayForUsage() const
389 {
390   FGEnvironmentMgr* envMgr = (FGEnvironmentMgr *) globals->get_subsystem("environment");
391   
392   // This forces West-facing rwys to be used in no-wind situations
393   // which is consistent with Flightgear's initial setup.
394   double hdg = 270;
395   
396   if (envMgr) {
397     FGEnvironment stationWeather(envMgr->getEnvironment(geod()));
398   
399     double windSpeed = stationWeather.get_wind_speed_kt();
400     if (windSpeed > 0.0) {
401       hdg = stationWeather.get_wind_from_heading_deg();
402     }
403   }
404   
405   return findBestRunwayForHeading(hdg);
406 }
407
408 //------------------------------------------------------------------------------
409 FGAirportRef FGAirport::findClosest( const SGGeod& aPos,
410                                      double aCuttofNm,
411                                      Filter* filter )
412 {
413   AirportFilter aptFilter;
414   if( !filter )
415     filter = &aptFilter;
416   
417   return static_pointer_cast<FGAirport>
418   (
419     FGPositioned::findClosest(aPos, aCuttofNm, filter)
420   );
421 }
422
423 FGAirport::HardSurfaceFilter::HardSurfaceFilter(double minLengthFt) :
424   mMinLengthFt(minLengthFt)
425 {
426   if (minLengthFt < 0.0) {
427     mMinLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 0.0);
428   }
429 }
430
431 bool FGAirport::HardSurfaceFilter::passAirport(FGAirport* aApt) const
432 {
433   return aApt->hasHardRunwayOfLengthFt(mMinLengthFt);
434 }
435
436 //------------------------------------------------------------------------------
437 FGAirport::TypeRunwayFilter::TypeRunwayFilter():
438   _type(FGPositioned::AIRPORT),
439   _min_runway_length_ft( fgGetDouble("/sim/navdb/min-runway-length-ft", 0.0) )
440 {
441
442 }
443
444 //------------------------------------------------------------------------------
445 bool FGAirport::TypeRunwayFilter::fromTypeString(const std::string& type)
446 {
447   if(      type == "heliport" ) _type = FGPositioned::HELIPORT;
448   else if( type == "seaport"  ) _type = FGPositioned::SEAPORT;
449   else if( type == "airport"  ) _type = FGPositioned::AIRPORT;
450   else                          return false;
451
452   return true;
453 }
454
455 //------------------------------------------------------------------------------
456 bool FGAirport::TypeRunwayFilter::pass(FGPositioned* pos) const
457 {
458   FGAirport* apt = static_cast<FGAirport*>(pos);
459   if(  (apt->type() == FGPositioned::AIRPORT)
460     && !apt->hasHardRunwayOfLengthFt(_min_runway_length_ft)
461     )
462     return false;
463
464   return true;
465 }
466
467 //------------------------------------------------------------------------------
468 FGAirportRef FGAirport::findByIdent(const std::string& aIdent)
469 {
470   AirportCache::iterator it = airportCache.find(aIdent);
471   if (it != airportCache.end())
472    return it->second;
473
474   PortsFilter filter;
475   FGAirportRef r = static_pointer_cast<FGAirport>
476   (
477     FGPositioned::findFirstWithIdent(aIdent, &filter)
478   );
479
480   // add airport to the cache (even when it's NULL, so we don't need to search in vain again)
481   airportCache[aIdent] = r;
482
483   // we don't warn here when r==NULL, let the caller do that
484   return r;
485 }
486
487 //------------------------------------------------------------------------------
488 FGAirportRef FGAirport::getByIdent(const std::string& aIdent)
489 {
490   FGAirportRef r = findByIdent(aIdent);
491   if (!r)
492     throw sg_range_exception("No such airport with ident: " + aIdent);
493   return r;
494 }
495
496 char** FGAirport::searchNamesAndIdents(const std::string& aFilter)
497 {
498   return NavDataCache::instance()->searchAirportNamesAndIdents(aFilter);
499 }
500
501 // find basic airport location info from airport database
502 const FGAirport *fgFindAirportID( const std::string& id)
503 {
504     if ( id.empty() ) {
505         return NULL;
506     }
507     
508     return FGAirport::findByIdent(id);
509 }
510
511 PositionedIDVec FGAirport::itemsOfType(FGPositioned::Type ty) const
512 {
513   flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
514   return cache->airportItemsOfType(guid(), ty);
515 }
516
517 void FGAirport::loadRunways() const
518 {
519   if (mRunwaysLoaded) {
520     return; // already loaded, great
521   }
522   
523   loadSceneryDefinitions();
524   
525   mRunwaysLoaded = true;
526   PositionedIDVec rwys(itemsOfType(FGPositioned::RUNWAY));
527   BOOST_FOREACH(PositionedID id, rwys) {
528     mRunways.push_back(loadById<FGRunway>(id));
529   }
530 }
531
532 void FGAirport::loadHelipads() const
533 {
534   if (mHelipadsLoaded) {
535     return; // already loaded, great
536   }
537
538   mHelipadsLoaded = true;
539   mHelipads = itemsOfType(FGPositioned::HELIPAD);
540 }
541
542 void FGAirport::loadTaxiways() const
543 {
544   if (mTaxiwaysLoaded) {
545     return; // already loaded, great
546   }
547   
548   mTaxiwaysLoaded =  true;
549   mTaxiways = itemsOfType(FGPositioned::TAXIWAY);
550 }
551
552 void FGAirport::loadProcedures() const
553 {
554   if (mProceduresLoaded) {
555     return;
556   }
557   
558   mProceduresLoaded = true;
559   SGPath path;
560   if (!XMLLoader::findAirportData(ident(), "procedures", path)) {
561     SG_LOG(SG_GENERAL, SG_INFO, "no procedures data available for " << ident());
562     return;
563   }
564   
565   SG_LOG(SG_GENERAL, SG_INFO, ident() << ": loading procedures from " << path.str());
566   RouteBase::loadAirportProcedures(path, const_cast<FGAirport*>(this));
567 }
568
569 void FGAirport::loadSceneryDefinitions() const
570 {
571   if (mThresholdDataLoaded) {
572     return;
573   }
574   
575   mThresholdDataLoaded = true;
576   
577   SGPath path;
578   if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
579     return; // no XML threshold data
580   }
581   
582   try {
583     SGPropertyNode_ptr rootNode = new SGPropertyNode;
584     readProperties(path.str(), rootNode);
585     const_cast<FGAirport*>(this)->readThresholdData(rootNode);
586   } catch (sg_exception& e) {
587     SG_LOG(SG_NAVAID, SG_WARN, ident() << "loading threshold XML failed:" << e.getFormattedMessage());
588   }
589 }
590
591 void FGAirport::readThresholdData(SGPropertyNode* aRoot)
592 {
593   SGPropertyNode* runway;
594   int runwayIndex = 0;
595   for (; (runway = aRoot->getChild("runway", runwayIndex)) != NULL; ++runwayIndex) {
596     SGPropertyNode* t0 = runway->getChild("threshold", 0),
597       *t1 = runway->getChild("threshold", 1);
598     assert(t0);
599     assert(t1); // too strict? maybe we should finally allow single-ended runways
600     
601     processThreshold(t0);
602     processThreshold(t1);
603   } // of runways iteration
604 }
605
606 void FGAirport::processThreshold(SGPropertyNode* aThreshold)
607 {
608   // first, let's identify the current runway
609   std::string rwyIdent(aThreshold->getStringValue("rwy"));
610   NavDataCache* cache = NavDataCache::instance(); 
611   PositionedID id = cache->airportItemWithIdent(guid(), FGPositioned::RUNWAY, rwyIdent);
612   
613   double lon = aThreshold->getDoubleValue("lon"),
614   lat = aThreshold->getDoubleValue("lat");
615   SGGeod newThreshold(SGGeod::fromDegM(lon, lat, elevationM()));
616   
617   double newHeading = aThreshold->getDoubleValue("hdg-deg");
618   double newDisplacedThreshold = aThreshold->getDoubleValue("displ-m");
619   double newStopway = aThreshold->getDoubleValue("stopw-m");
620   
621   if (id == 0) {
622     SG_LOG(SG_GENERAL, SG_DEBUG, "FGAirport::processThreshold: "
623            "found runway not defined in the global data:" << ident() << "/" << rwyIdent);
624     // enable this code when threshold.xml contains sufficient data to
625     // fully specify a new runway, *and* we figure out how to assign runtime
626     // Positioned IDs and insert temporary items into the spatial map.
627 #if 0
628     double newLength = 0.0, newWidth = 0.0;
629     int surfaceCode = 0;
630     FGRunway* rwy = new FGRunway(id, guid(), rwyIdent, newThreshold,
631                        newHeading,
632                        newLength, newWidth,
633                        newDisplacedThreshold, newStopway,
634                        surfaceCode);
635     // insert into the spatial map too
636     mRunways.push_back(rwy);
637 #endif
638   } else {
639     FGRunway* rwy = loadById<FGRunway>(id);
640     rwy->updateThreshold(newThreshold, newHeading,
641                          newDisplacedThreshold, newStopway);
642
643   }
644 }
645
646 SGGeod FGAirport::getTowerLocation() const
647 {
648   validateTowerData();
649   return mTowerPosition;
650 }
651
652 void FGAirport::validateTowerData() const
653 {
654   if (mTowerDataLoaded) {
655     return;
656   }
657   
658   mTowerDataLoaded = true;
659
660 // first, load data from the cache (apt.dat)
661   NavDataCache* cache = NavDataCache::instance();
662   PositionedIDVec towers = cache->airportItemsOfType(guid(), FGPositioned::TOWER);
663   if (towers.empty()) {
664     mHasTower = false;
665     mTowerPosition = geod(); // use airport position
666
667     // offset the tower position away from the runway centerline, if
668     // airport has a single runway. Offset by eight times the runway width,
669     // an entirely guessed figure.
670     int runwayCount = numRunways();
671     if ((runwayCount > 0) && (runwayCount <= 2)) {
672         FGRunway* runway = getRunwayByIndex(0);
673         double hdg = runway->headingDeg() + 90;
674         mTowerPosition = SGGeodesy::direct(geod(), hdg, runway->widthM() * 8);
675     }
676
677     // increase tower elevation by 20 metres above the field elevation
678     mTowerPosition.setElevationM(geod().getElevationM() + 20.0);
679   } else {
680     FGPositionedRef tower = cache->loadById(towers.front());
681     mTowerPosition = tower->geod();
682     mHasTower = true;
683   }
684   
685   SGPath path;
686   if (!XMLLoader::findAirportData(ident(), "twr", path)) {
687     return; // no XML tower data, base position is fine
688   }
689   
690   try {
691     SGPropertyNode_ptr rootNode = new SGPropertyNode;
692     readProperties(path.str(), rootNode);
693     const_cast<FGAirport*>(this)->readTowerData(rootNode);
694     mHasTower = true;
695   } catch (sg_exception& e){
696     SG_LOG(SG_NAVAID, SG_WARN, ident() << "loading twr XML failed:" << e.getFormattedMessage());
697   }
698 }
699
700 void FGAirport::readTowerData(SGPropertyNode* aRoot)
701 {
702   SGPropertyNode* twrNode = aRoot->getChild("tower")->getChild("twr");
703   double lat = twrNode->getDoubleValue("lat"), 
704     lon = twrNode->getDoubleValue("lon"), 
705     elevM = twrNode->getDoubleValue("elev-m");  
706 // tower elevation is AGL, not AMSL. Since we don't want to depend on the
707 // scenery for a precise terrain elevation, we use the field elevation
708 // (this is also what the apt.dat code does)
709   double fieldElevationM = geod().getElevationM();
710   mTowerPosition = SGGeod::fromDegM(lon, lat, fieldElevationM + elevM);
711 }
712
713 void FGAirport::validateILSData()
714 {
715   if (mILSDataLoaded) {
716     return;
717   }
718   
719   // to avoid re-entrancy on this code-path, ensure we set loaded
720   // immediately.
721   mILSDataLoaded = true;
722     
723   SGPath path;
724   if (!XMLLoader::findAirportData(ident(), "ils", path)) {
725     return; // no XML tower data
726   }
727   
728   try {
729       SGPropertyNode_ptr rootNode = new SGPropertyNode;
730       readProperties(path.str(), rootNode);
731       readILSData(rootNode);
732   } catch (sg_exception& e){
733       SG_LOG(SG_NAVAID, SG_WARN, ident() << "loading ils XML failed:" << e.getFormattedMessage());
734   }
735 }
736
737 bool FGAirport::hasTower() const
738 {
739     validateTowerData();
740     return mHasTower;
741 }
742
743 void FGAirport::readILSData(SGPropertyNode* aRoot)
744 {  
745   NavDataCache* cache = NavDataCache::instance();
746   // find the entry matching the runway
747   SGPropertyNode* runwayNode, *ilsNode;
748   for (int i=0; (runwayNode = aRoot->getChild("runway", i)) != NULL; ++i) {
749     for (int j=0; (ilsNode = runwayNode->getChild("ils", j)) != NULL; ++j) {
750       // must match on both nav-ident and runway ident, to support the following:
751       // - runways with multiple distinct ILS installations (KEWD, for example)
752       // - runways where both ends share the same nav ident (LFAT, for example)
753       PositionedID ils = cache->findILS(guid(), ilsNode->getStringValue("rwy"),
754                                         ilsNode->getStringValue("nav-id"));
755       if (ils == 0) {
756         SG_LOG(SG_GENERAL, SG_INFO, "reading ILS data for " << ident() <<
757                ", couldn't find runway/navaid for:" <<
758                ilsNode->getStringValue("rwy") << "/" <<
759                ilsNode->getStringValue("nav-id"));
760         continue;
761       }
762       
763       double hdgDeg = ilsNode->getDoubleValue("hdg-deg"),
764         lon = ilsNode->getDoubleValue("lon"),
765         lat = ilsNode->getDoubleValue("lat"),
766         elevM = ilsNode->getDoubleValue("elev-m");
767  
768       FGNavRecordRef nav(FGPositioned::loadById<FGNavRecord>(ils));
769       assert(nav.valid());
770       nav->updateFromXML(SGGeod::fromDegM(lon, lat, elevM), hdgDeg);
771     } // of ILS iteration
772   } // of runway iteration
773 }
774
775 void FGAirport::addSID(flightgear::SID* aSid)
776 {
777   mSIDs.push_back(aSid);
778 }
779
780 void FGAirport::addSTAR(STAR* aStar)
781 {
782   mSTARs.push_back(aStar);
783 }
784
785 void FGAirport::addApproach(Approach* aApp)
786 {
787   mApproaches.push_back(aApp);
788 }
789
790 //------------------------------------------------------------------------------
791 unsigned int FGAirport::numSIDs() const
792 {
793   loadProcedures();
794   return mSIDs.size();
795 }
796
797 //------------------------------------------------------------------------------
798 flightgear::SID* FGAirport::getSIDByIndex(unsigned int aIndex) const
799 {
800   loadProcedures();
801   return mSIDs[aIndex];
802 }
803
804 //------------------------------------------------------------------------------
805 flightgear::SID* FGAirport::findSIDWithIdent(const std::string& aIdent) const
806 {
807   loadProcedures();
808   for (unsigned int i=0; i<mSIDs.size(); ++i) {
809     if (mSIDs[i]->ident() == aIdent) {
810       return mSIDs[i];
811     }
812   }
813   
814   return NULL;
815 }
816
817 //------------------------------------------------------------------------------
818 flightgear::SIDList FGAirport::getSIDs() const
819 {
820   loadProcedures();
821   return flightgear::SIDList(mSIDs.begin(), mSIDs.end());
822 }
823
824 //------------------------------------------------------------------------------
825 unsigned int FGAirport::numSTARs() const
826 {
827   loadProcedures();
828   return mSTARs.size();
829 }
830
831 //------------------------------------------------------------------------------
832 STAR* FGAirport::getSTARByIndex(unsigned int aIndex) const
833 {
834   loadProcedures();
835   return mSTARs[aIndex];
836 }
837
838 //------------------------------------------------------------------------------
839 STAR* FGAirport::findSTARWithIdent(const std::string& aIdent) const
840 {
841   loadProcedures();
842   for (unsigned int i=0; i<mSTARs.size(); ++i) {
843     if (mSTARs[i]->ident() == aIdent) {
844       return mSTARs[i];
845     }
846   }
847   
848   return NULL;
849 }
850
851 //------------------------------------------------------------------------------
852 STARList FGAirport::getSTARs() const
853 {
854   loadProcedures();
855   return STARList(mSTARs.begin(), mSTARs.end());
856 }
857
858 unsigned int FGAirport::numApproaches() const
859 {
860   loadProcedures();
861   return mApproaches.size();
862 }
863
864 //------------------------------------------------------------------------------
865 Approach* FGAirport::getApproachByIndex(unsigned int aIndex) const
866 {
867   loadProcedures();
868   return mApproaches[aIndex];
869 }
870
871 //------------------------------------------------------------------------------
872 Approach* FGAirport::findApproachWithIdent(const std::string& aIdent) const
873 {
874   loadProcedures();
875   for (unsigned int i=0; i<mApproaches.size(); ++i) {
876     if (mApproaches[i]->ident() == aIdent) {
877       return mApproaches[i];
878     }
879   }
880   
881   return NULL;
882 }
883
884 //------------------------------------------------------------------------------
885 ApproachList FGAirport::getApproaches(ProcedureType type) const
886 {
887   loadProcedures();
888   if( type == PROCEDURE_INVALID )
889     return ApproachList(mApproaches.begin(), mApproaches.end());
890
891   ApproachList ret;
892   for(size_t i = 0; i < mApproaches.size(); ++i)
893   {
894     if( mApproaches[i]->type() == type )
895       ret.push_back(mApproaches[i]);
896   }
897   return ret;
898 }
899
900 CommStationList
901 FGAirport::commStations() const
902 {
903   NavDataCache* cache = NavDataCache::instance();
904   CommStationList result;
905   BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(),
906                                                             FGPositioned::FREQ_GROUND,
907                                                             FGPositioned::FREQ_UNICOM))
908   {
909     result.push_back( loadById<CommStation>(pos) );
910   }
911   
912   return result;
913 }
914
915 CommStationList
916 FGAirport::commStationsOfType(FGPositioned::Type aTy) const
917 {
918   NavDataCache* cache = NavDataCache::instance();
919   CommStationList result;
920   BOOST_FOREACH(PositionedID pos, cache->airportItemsOfType(guid(), aTy)) {
921     result.push_back( loadById<CommStation>(pos) );
922   }
923   
924   return result;
925 }
926
927 class AirportWithSize
928 {
929 public:
930     AirportWithSize(FGPositionedRef pos) :
931         _pos(pos),
932         _sizeMetric(0)
933     {
934         assert(pos->type() == FGPositioned::AIRPORT);
935         FGAirport* apt = static_cast<FGAirport*>(pos.get());
936         BOOST_FOREACH(FGRunway* rwy, apt->getRunwaysWithoutReciprocals()) {
937             _sizeMetric += static_cast<int>(rwy->lengthFt());
938         }
939     }
940     
941     bool operator<(const AirportWithSize& other) const
942     {
943         return _sizeMetric < other._sizeMetric;
944     }
945     
946     FGPositionedRef pos() const
947     { return _pos; }
948 private:
949     FGPositionedRef _pos;
950     unsigned int _sizeMetric;
951     
952 };
953
954 void FGAirport::sortBySize(FGPositionedList& airportList)
955 {
956     std::vector<AirportWithSize> annotated;
957     BOOST_FOREACH(FGPositionedRef p, airportList) {
958         annotated.push_back(AirportWithSize(p));
959     }
960     std::sort(annotated.begin(), annotated.end());
961     
962     for (unsigned int i=0; i<annotated.size(); ++i) {
963         airportList[i] = annotated[i].pos();
964     }
965 }
966
967 FGAirportDynamicsRef FGAirport::getDynamics() const
968 {
969     return flightgear::AirportDynamicsManager::find(const_cast<FGAirport*>(this));
970 }
971
972 // get airport elevation
973 double fgGetAirportElev( const std::string& id )
974 {
975     const FGAirport *a=fgFindAirportID( id);
976     if (a) {
977         return a->getElevation();
978     } else {
979         return -9999.0;
980     }
981 }
982
983
984 // get airport position
985 SGGeod fgGetAirportPos( const std::string& id )
986 {
987     const FGAirport *a = fgFindAirportID( id);
988
989     if (a) {
990         return SGGeod::fromDegM(a->getLongitude(), a->getLatitude(), a->getElevation());
991     } else {
992         return SGGeod::fromDegM(0.0, 0.0, -9999.0);
993     }
994 }