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