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