]> git.mxchange.org Git - flightgear.git/blob - src/Airports/simple.cxx
Remove /sim/paths/use-custom-scenery-data.
[flightgear.git] / src / Airports / simple.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 "simple.hxx"
32
33 #include <cassert>
34
35 #include <simgear/misc/sg_path.hxx>
36 #include <simgear/props/props.hxx>
37 #include <simgear/props/props_io.hxx>
38 #include <simgear/debug/logstream.hxx>
39 #include <simgear/sg_inlines.h>
40
41 #include <Environment/environment_mgr.hxx>
42 #include <Environment/environment.hxx>
43 #include <Main/fg_props.hxx>
44 #include <Airports/runways.hxx>
45 #include <Airports/pavement.hxx>
46 #include <Airports/dynamics.hxx>
47 #include <Airports/xmlloader.hxx>
48 #include <Navaids/procedure.hxx>
49 #include <Navaids/waypoint.hxx>
50 #include <Navaids/PositionedBinding.hxx>
51 #include <ATC/CommStation.hxx>
52
53 using std::vector;
54 using std::pair;
55
56 using namespace flightgear;
57
58 // magic import of a helper which uses FGPositioned internals
59 extern char** searchAirportNamesAndIdents(const std::string& aFilter);
60
61 /***************************************************************************
62  * FGAirport
63  ***************************************************************************/
64
65 FGAirport::FGAirport(const string &id, const SGGeod& location, const SGGeod& tower_location,
66         const string &name, bool has_metar, Type aType) :
67     FGPositioned(aType, id, location),
68     _tower_location(tower_location),
69     _name(name),
70     _has_metar(has_metar),
71     _dynamics(0),
72     mRunwaysLoaded(false),
73     mTaxiwaysLoaded(true)
74 {
75   init(true); // init FGPositioned
76 }
77
78
79 FGAirport::~FGAirport()
80 {
81     delete _dynamics;
82 }
83
84 bool FGAirport::isAirport() const
85 {
86   return type() == AIRPORT;
87 }
88
89 bool FGAirport::isSeaport() const
90 {
91   return type() == SEAPORT;
92 }
93
94 bool FGAirport::isHeliport() const
95 {
96   return type() == HELIPORT;
97 }
98
99 FGAirportDynamics * FGAirport::getDynamics()
100 {
101     if (_dynamics) {
102         return _dynamics;
103     }
104     
105     _dynamics = new FGAirportDynamics(this);
106     XMLLoader::load(_dynamics);
107
108     FGRunwayPreference rwyPrefs(this);
109     XMLLoader::load(&rwyPrefs);
110     _dynamics->setRwyUse(rwyPrefs);
111     XMLLoader::load(_dynamics->getSIDs());
112     
113     return _dynamics;
114 }
115
116 unsigned int FGAirport::numRunways() const
117 {
118   loadRunways();
119   return mRunways.size();
120 }
121
122 FGRunway* FGAirport::getRunwayByIndex(unsigned int aIndex) const
123 {
124   loadRunways();
125   
126   assert(aIndex >= 0 && aIndex < mRunways.size());
127   return mRunways[aIndex];
128 }
129
130 bool FGAirport::hasRunwayWithIdent(const string& aIdent) const
131 {
132   return (getIteratorForRunwayIdent(aIdent) != mRunways.end());
133 }
134
135 FGRunway* FGAirport::getRunwayByIdent(const string& aIdent) const
136 {
137   Runway_iterator it = getIteratorForRunwayIdent(aIdent);
138   if (it == mRunways.end()) {
139     SG_LOG(SG_GENERAL, SG_ALERT, "no such runway '" << aIdent << "' at airport " << ident());
140     throw sg_range_exception("unknown runway " + aIdent + " at airport:" + ident(), "FGAirport::getRunwayByIdent");
141   }
142   
143   return *it;
144 }
145
146 FGAirport::Runway_iterator
147 FGAirport::getIteratorForRunwayIdent(const string& aIdent) const
148 {
149   if (aIdent.empty())
150     return mRunways.end();
151
152   loadRunways();
153   
154   string ident(aIdent);
155   if ((aIdent.size() == 1) || !isdigit(aIdent[1])) {
156     ident = "0" + aIdent;
157   }
158
159   Runway_iterator it = mRunways.begin();
160   for (; it != mRunways.end(); ++it) {
161     if ((*it)->ident() == ident) {
162       return it;
163     }
164   }
165
166   return it; // end()
167 }
168
169 FGRunway* FGAirport::findBestRunwayForHeading(double aHeading) const
170 {
171   loadRunways();
172   
173   Runway_iterator it = mRunways.begin();
174   FGRunway* result = NULL;
175   double currentBestQuality = 0.0;
176   
177   SGPropertyNode *param = fgGetNode("/sim/airport/runways/search", true);
178   double lengthWeight = param->getDoubleValue("length-weight", 0.01);
179   double widthWeight = param->getDoubleValue("width-weight", 0.01);
180   double surfaceWeight = param->getDoubleValue("surface-weight", 10);
181   double deviationWeight = param->getDoubleValue("deviation-weight", 1);
182     
183   for (; it != mRunways.end(); ++it) {
184     double good = (*it)->score(lengthWeight, widthWeight, surfaceWeight);
185     
186     double dev = aHeading - (*it)->headingDeg();
187     SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
188     double bad = fabs(deviationWeight * dev) + 1e-20;
189     double quality = good / bad;
190     
191     if (quality > currentBestQuality) {
192       currentBestQuality = quality;
193       result = *it;
194     }
195   }
196
197   return result;
198 }
199
200 FGRunway* FGAirport::findBestRunwayForPos(const SGGeod& aPos) const
201 {
202   loadRunways();
203   
204   Runway_iterator it = mRunways.begin();
205   FGRunway* result = NULL;
206   double currentLowestDev = 180.0;
207   
208   for (; it != mRunways.end(); ++it) {
209     double inboundCourse = SGGeodesy::courseDeg(aPos, (*it)->end());
210     double dev = inboundCourse - (*it)->headingDeg();
211     SG_NORMALIZE_RANGE(dev, -180.0, 180.0);
212
213     dev = fabs(dev);
214     if (dev < currentLowestDev) { // new best match
215       currentLowestDev = dev;
216       result = *it;
217     }
218   } // of runway iteration
219   
220   return result;
221
222 }
223
224 bool FGAirport::hasHardRunwayOfLengthFt(double aLengthFt) const
225 {
226   loadRunways();
227   
228   unsigned int numRunways(mRunways.size());
229   for (unsigned int r=0; r<numRunways; ++r) {
230     FGRunway* rwy = mRunways[r];
231     if (rwy->isReciprocal()) {
232       continue; // we only care about lengths, so don't do work twice
233     }
234
235     if (rwy->isHardSurface() && (rwy->lengthFt() >= aLengthFt)) {
236       return true; // we're done!
237     }
238   } // of runways iteration
239
240   return false;
241 }
242
243 unsigned int FGAirport::numTaxiways() const
244 {
245   loadTaxiways();
246   return mTaxiways.size();
247 }
248
249 FGTaxiway* FGAirport::getTaxiwayByIndex(unsigned int aIndex) const
250 {
251   loadTaxiways();
252   assert(aIndex >= 0 && aIndex < mTaxiways.size());
253   return mTaxiways[aIndex];
254 }
255
256 unsigned int FGAirport::numPavements() const
257 {
258   loadTaxiways();
259   return mPavements.size();
260 }
261
262 FGPavement* FGAirport::getPavementByIndex(unsigned int aIndex) const
263 {
264   loadTaxiways();
265   assert(aIndex >= 0 && aIndex < mPavements.size());
266   return mPavements[aIndex];
267 }
268
269 void FGAirport::setRunwaysAndTaxiways(vector<FGRunwayPtr>& rwys,
270        vector<FGTaxiwayPtr>& txwys,
271        vector<FGPavementPtr>& pvts)
272 {
273   mRunways.swap(rwys);
274   Runway_iterator it = mRunways.begin();
275   for (; it != mRunways.end(); ++it) {
276     (*it)->setAirport(this);
277   }
278
279   mTaxiways.swap(txwys);
280   mPavements.swap(pvts);
281 }
282
283 FGRunway* FGAirport::getActiveRunwayForUsage() const
284 {
285   static FGEnvironmentMgr* envMgr = NULL;
286   if (!envMgr) {
287     envMgr = (FGEnvironmentMgr *) globals->get_subsystem("environment");
288   }
289   
290   // This forces West-facing rwys to be used in no-wind situations
291   // which is consistent with Flightgear's initial setup.
292   double hdg = 270;
293   
294   if (envMgr) {
295     FGEnvironment stationWeather(envMgr->getEnvironment(mPosition));
296   
297     double windSpeed = stationWeather.get_wind_speed_kt();
298     if (windSpeed > 0.0) {
299       hdg = stationWeather.get_wind_from_heading_deg();
300     }
301   }
302   
303   return findBestRunwayForHeading(hdg);
304 }
305
306 FGAirport* FGAirport::findClosest(const SGGeod& aPos, double aCuttofNm, Filter* filter)
307 {
308   AirportFilter aptFilter;
309   if (filter == NULL) {
310     filter = &aptFilter;
311   }
312   
313   FGPositionedRef r = FGPositioned::findClosest(aPos, aCuttofNm, filter);
314   if (!r) {
315     return NULL;
316   }
317   
318   return static_cast<FGAirport*>(r.ptr());
319 }
320
321 FGAirport::HardSurfaceFilter::HardSurfaceFilter(double minLengthFt) :
322   mMinLengthFt(minLengthFt)
323 {
324 }
325       
326 bool FGAirport::HardSurfaceFilter::passAirport(FGAirport* aApt) const
327 {
328   return aApt->hasHardRunwayOfLengthFt(mMinLengthFt);
329 }
330
331 FGAirport* FGAirport::findByIdent(const std::string& aIdent)
332 {
333   FGPositionedRef r;
334   PortsFilter filter;
335   r = FGPositioned::findNextWithPartialId(r, aIdent, &filter);
336   if (!r) {
337     return NULL; // we don't warn here, let the caller do that
338   }
339   return static_cast<FGAirport*>(r.ptr());
340 }
341
342 FGAirport* FGAirport::getByIdent(const std::string& aIdent)
343 {
344   FGPositionedRef r;
345   PortsFilter filter;
346   r = FGPositioned::findNextWithPartialId(r, aIdent, &filter);
347   if (!r) {
348     throw sg_range_exception("No such airport with ident: " + aIdent);
349   }
350   return static_cast<FGAirport*>(r.ptr());
351 }
352
353 char** FGAirport::searchNamesAndIdents(const std::string& aFilter)
354 {
355   // we delegate all the work to a horrible helper in FGPositioned, which can
356   // access the (private) index data.
357   return searchAirportNamesAndIdents(aFilter);
358 }
359
360 // find basic airport location info from airport database
361 const FGAirport *fgFindAirportID( const string& id)
362 {
363     if ( id.empty() ) {
364         return NULL;
365     }
366     
367     return FGAirport::findByIdent(id);
368 }
369
370 void FGAirport::loadRunways() const
371 {
372   if (mRunwaysLoaded) {
373     return; // already loaded, great
374   }
375   
376   mRunwaysLoaded = true;
377   loadSceneryDefinitions();
378 }
379
380 void FGAirport::loadTaxiways() const
381 {
382   if (mTaxiwaysLoaded) {
383     return; // already loaded, great
384   }
385 }
386
387 void FGAirport::loadProcedures() const
388 {
389   if (mProceduresLoaded) {
390     return;
391   }
392   
393   mProceduresLoaded = true;
394   SGPath path;
395   if (!XMLLoader::findAirportData(ident(), "procedures", path)) {
396     SG_LOG(SG_GENERAL, SG_INFO, "no procedures data available for " << ident());
397     return;
398   }
399   
400   SG_LOG(SG_GENERAL, SG_INFO, ident() << ": loading procedures from " << path.str());
401   Route::loadAirportProcedures(path, const_cast<FGAirport*>(this));
402 }
403
404 void FGAirport::loadSceneryDefinitions() const
405 {  
406   SGPath path;
407   SGPropertyNode_ptr rootNode = new SGPropertyNode;
408   if (XMLLoader::findAirportData(ident(), "threshold", path)) {
409     readProperties(path.str(), rootNode);
410     const_cast<FGAirport*>(this)->readThresholdData(rootNode);
411   }
412   
413   // repeat for the tower data
414   rootNode = new SGPropertyNode;
415   if (XMLLoader::findAirportData(ident(), "twr", path)) {
416     readProperties(path.str(), rootNode);
417     const_cast<FGAirport*>(this)->readTowerData(rootNode);
418   }
419 }
420
421 void FGAirport::readThresholdData(SGPropertyNode* aRoot)
422 {
423   SGPropertyNode* runway;
424   int runwayIndex = 0;
425   for (; (runway = aRoot->getChild("runway", runwayIndex)) != NULL; ++runwayIndex) {
426     SGPropertyNode* t0 = runway->getChild("threshold", 0),
427       *t1 = runway->getChild("threshold", 1);
428     assert(t0);
429     assert(t1); // too strict? mayeb we should finally allow single-ended runways
430     
431     processThreshold(t0);
432     processThreshold(t1);
433   } // of runways iteration
434 }
435
436 void FGAirport::processThreshold(SGPropertyNode* aThreshold)
437 {
438   // first, let's identify the current runway
439   string id(aThreshold->getStringValue("rwy"));
440   if (!hasRunwayWithIdent(id)) {
441     SG_LOG(SG_GENERAL, SG_DEBUG, "FGAirport::processThreshold: "
442       "found runway not defined in the global data:" << ident() << "/" << id);
443     return;
444   }
445   
446   FGRunway* rwy = getRunwayByIdent(id);
447   rwy->processThreshold(aThreshold);
448 }
449
450 void FGAirport::readTowerData(SGPropertyNode* aRoot)
451 {
452   SGPropertyNode* twrNode = aRoot->getChild("tower")->getChild("twr");
453   double lat = twrNode->getDoubleValue("lat"), 
454     lon = twrNode->getDoubleValue("lon"), 
455     elevM = twrNode->getDoubleValue("elev-m");  
456 // tower elevation is AGL, not AMSL. Since we don't want to depend on the
457 // scenery for a precise terrain elevation, we use the field elevation
458 // (this is also what the apt.dat code does)
459   double fieldElevationM = geod().getElevationM();
460   
461   _tower_location = SGGeod::fromDegM(lon, lat, fieldElevationM + elevM);
462 }
463
464 bool FGAirport::buildApproach(Waypt* aEnroute, STAR* aSTAR, FGRunway* aRwy, WayptVec& aRoute)
465 {
466   loadProcedures();
467
468   if ((aRwy && (aRwy->airport() != this))) {
469     throw sg_exception("invalid parameters", "FGAirport::buildApproach");
470   }
471   
472   if (aSTAR) {
473     bool ok = aSTAR->route(aRwy, aEnroute, aRoute);
474     if (!ok) {
475       SG_LOG(SG_GENERAL, SG_WARN, ident() << ": build approach, STAR " << aSTAR->ident() 
476          << " failed to route from transition " << aEnroute->ident());
477       return false;
478     }
479   } else if (aEnroute) {
480     // no a STAR specified, just use enroute point directly
481     aRoute.push_back(aEnroute);
482   }
483   
484   if (!aRwy) {
485     // no runway selected yet, but we loaded the STAR, so that's fine, we're done
486     return true;
487   }
488   
489 // build the approach (possibly including transition), and including the missed segment
490   vector<Approach*> aps;
491   for (unsigned int j=0; j<mApproaches.size();++j) {
492     if (mApproaches[j]->runway() == aRwy) {
493       aps.push_back(mApproaches[j]);
494     }
495   } // of approach filter by runway
496   
497   if (aps.empty()) {
498     SG_LOG(SG_GENERAL, SG_INFO, ident() << "; no approaches defined for runway " << aRwy->ident());
499     // could build a fallback approach here
500     return false;
501   }
502   
503   for (unsigned int k=0; k<aps.size(); ++k) {
504     if (aps[k]->route(aRoute.back(), aRoute)) {
505       return true;
506     }
507   } // of initial approach iteration
508   
509   SG_LOG(SG_GENERAL, SG_INFO, ident() << ": unable to find transition to runway "
510     << aRwy->ident() << ", assume vectors");
511   
512   WayptRef v(new ATCVectors(NULL, this));
513   aRoute.push_back(v);
514   return aps.front()->routeFromVectors(aRoute);
515 }
516
517 pair<flightgear::SID*, WayptRef>
518 FGAirport::selectSID(const SGGeod& aDest, FGRunway* aRwy)
519 {
520   loadProcedures();
521   
522   WayptRef enroute;
523   flightgear::SID* sid = NULL;
524   double d = 1e9;
525   
526   for (unsigned int i=0; i<mSIDs.size(); ++i) {
527     if (aRwy && !mSIDs[i]->isForRunway(aRwy)) {
528       continue;
529     }
530   
531     WayptRef e = mSIDs[i]->findBestTransition(aDest);
532     if (!e) {
533       continue; // strange, but let's not worry about it
534     }
535     
536     // assert(e->isFixedPosition());
537     double ed = SGGeodesy::distanceM(aDest, e->position());
538     if (ed < d) { // new best match
539       enroute = e;
540       d = ed;
541       sid = mSIDs[i];
542     }
543   } // of SID iteration
544   
545   if (!mSIDs.empty() && !sid) {
546     SG_LOG(SG_GENERAL, SG_INFO, ident() << "selectSID, no SID found (runway=" 
547       << (aRwy ? aRwy->ident() : "no runway preference"));
548   }
549   
550   return std::make_pair(sid, enroute);
551 }
552     
553 pair<STAR*, WayptRef>
554 FGAirport::selectSTAR(const SGGeod& aOrigin, FGRunway* aRwy)
555 {
556   loadProcedures();
557   
558   WayptRef enroute;
559   STAR* star = NULL;
560   double d = 1e9;
561   
562   for (unsigned int i=0; i<mSTARs.size(); ++i) {
563     if (!mSTARs[i]->isForRunway(aRwy)) {
564       continue;
565     }
566     
567     SG_LOG(SG_GENERAL, SG_INFO, "STAR " << mSTARs[i]->ident() << " is valid for runway");
568     WayptRef e = mSTARs[i]->findBestTransition(aOrigin);
569     if (!e) {
570       continue; // strange, but let's not worry about it
571     }
572     
573     // assert(e->isFixedPosition());
574     double ed = SGGeodesy::distanceM(aOrigin, e->position());
575     if (ed < d) { // new best match
576       enroute = e;
577       d = ed;
578       star = mSTARs[i];
579     }
580   } // of STAR iteration
581   
582   return std::make_pair(star, enroute);
583 }
584
585
586 void FGAirport::addSID(flightgear::SID* aSid)
587 {
588   mSIDs.push_back(aSid);
589 }
590
591 void FGAirport::addSTAR(STAR* aStar)
592 {
593   mSTARs.push_back(aStar);
594 }
595
596 void FGAirport::addApproach(Approach* aApp)
597 {
598   mApproaches.push_back(aApp);
599 }
600
601 unsigned int FGAirport::numSIDs() const
602 {
603   loadProcedures();
604   return mSIDs.size();
605 }
606
607 flightgear::SID* FGAirport::getSIDByIndex(unsigned int aIndex) const
608 {
609   loadProcedures();
610   return mSIDs[aIndex];
611 }
612
613 flightgear::SID* FGAirport::findSIDWithIdent(const std::string& aIdent) const
614 {
615   loadProcedures();
616   for (unsigned int i=0; i<mSIDs.size(); ++i) {
617     if (mSIDs[i]->ident() == aIdent) {
618       return mSIDs[i];
619     }
620   }
621   
622   return NULL;
623 }
624
625 unsigned int FGAirport::numSTARs() const
626 {
627   loadProcedures();
628   return mSTARs.size();
629 }
630
631 STAR* FGAirport::getSTARByIndex(unsigned int aIndex) const
632 {
633   loadProcedures();
634   return mSTARs[aIndex];
635 }
636
637 STAR* FGAirport::findSTARWithIdent(const std::string& aIdent) const
638 {
639   loadProcedures();
640   for (unsigned int i=0; i<mSTARs.size(); ++i) {
641     if (mSTARs[i]->ident() == aIdent) {
642       return mSTARs[i];
643     }
644   }
645   
646   return NULL;
647 }
648
649 unsigned int FGAirport::numApproaches() const
650 {
651   loadProcedures();
652   return mApproaches.size();
653 }
654
655 Approach* FGAirport::getApproachByIndex(unsigned int aIndex) const
656 {
657   loadProcedures();
658   return mApproaches[aIndex];
659 }
660
661 class AirportNodeListener : public SGPropertyChangeListener
662 {
663 public:
664     AirportNodeListener()
665     {
666         SGPropertyNode* airports = fgGetNode("/sim/airport");
667         airports->addChangeListener(this, false);
668     }
669
670     virtual void valueChanged(SGPropertyNode*)
671     {
672     }
673
674     virtual void childAdded(SGPropertyNode* pr, SGPropertyNode* child)
675     {
676        FGAirport* apt = FGAirport::findByIdent(child->getName());
677        if (!apt) {
678            return;
679        }
680        
681        flightgear::PositionedBinding::bind(apt, child);
682     }
683 };
684     
685 void FGAirport::installPropertyListener()
686 {
687     new AirportNodeListener;  
688 }
689
690 flightgear::PositionedBinding*
691 FGAirport::createBinding(SGPropertyNode* nd) const
692 {
693     return new flightgear::AirportBinding(this, nd);
694 }
695
696 void FGAirport::setCommStations(CommStationList& comms)
697 {
698     mCommStations.swap(comms);
699     for (unsigned int c=0; c<mCommStations.size(); ++c) {
700         mCommStations[c]->setAirport(this);
701     }
702 }
703
704 CommStationList
705 FGAirport::commStationsOfType(FGPositioned::Type aTy) const
706 {
707     CommStationList result;
708     for (unsigned int c=0; c<mCommStations.size(); ++c) {
709         if (mCommStations[c]->type() == aTy) {
710             result.push_back(mCommStations[c]);
711         }
712     }
713     return result;
714 }
715
716 // get airport elevation
717 double fgGetAirportElev( const string& id )
718 {
719     const FGAirport *a=fgFindAirportID( id);
720     if (a) {
721         return a->getElevation();
722     } else {
723         return -9999.0;
724     }
725 }
726
727
728 // get airport position
729 SGGeod fgGetAirportPos( const string& id )
730 {
731     const FGAirport *a = fgFindAirportID( id);
732
733     if (a) {
734         return SGGeod::fromDegM(a->getLongitude(), a->getLatitude(), a->getElevation());
735     } else {
736         return SGGeod::fromDegM(0.0, 0.0, -9999.0);
737     }
738 }