]> git.mxchange.org Git - flightgear.git/blob - src/Airports/simple.cxx
Adapt to simgear SGMath change.
[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   // allow users to disable the scenery data in the short-term
407   // longer term, this option can probably disappear
408   if (!fgGetBool("/sim/paths/use-custom-scenery-data")) {
409     return; 
410   }
411   
412   SGPath path;
413   SGPropertyNode_ptr rootNode = new SGPropertyNode;
414   if (XMLLoader::findAirportData(ident(), "threshold", path)) {
415     readProperties(path.str(), rootNode);
416     const_cast<FGAirport*>(this)->readThresholdData(rootNode);
417   }
418   
419   // repeat for the tower data
420   rootNode = new SGPropertyNode;
421   if (XMLLoader::findAirportData(ident(), "twr", path)) {
422     readProperties(path.str(), rootNode);
423     const_cast<FGAirport*>(this)->readTowerData(rootNode);
424   }
425 }
426
427 void FGAirport::readThresholdData(SGPropertyNode* aRoot)
428 {
429   SGPropertyNode* runway;
430   int runwayIndex = 0;
431   for (; (runway = aRoot->getChild("runway", runwayIndex)) != NULL; ++runwayIndex) {
432     SGPropertyNode* t0 = runway->getChild("threshold", 0),
433       *t1 = runway->getChild("threshold", 1);
434     assert(t0);
435     assert(t1); // too strict? mayeb we should finally allow single-ended runways
436     
437     processThreshold(t0);
438     processThreshold(t1);
439   } // of runways iteration
440 }
441
442 void FGAirport::processThreshold(SGPropertyNode* aThreshold)
443 {
444   // first, let's identify the current runway
445   string id(aThreshold->getStringValue("rwy"));
446   if (!hasRunwayWithIdent(id)) {
447     SG_LOG(SG_GENERAL, SG_DEBUG, "FGAirport::processThreshold: "
448       "found runway not defined in the global data:" << ident() << "/" << id);
449     return;
450   }
451   
452   FGRunway* rwy = getRunwayByIdent(id);
453   rwy->processThreshold(aThreshold);
454 }
455
456 void FGAirport::readTowerData(SGPropertyNode* aRoot)
457 {
458   SGPropertyNode* twrNode = aRoot->getChild("tower")->getChild("twr");
459   double lat = twrNode->getDoubleValue("lat"), 
460     lon = twrNode->getDoubleValue("lon"), 
461     elevM = twrNode->getDoubleValue("elev-m");  
462 // tower elevation is AGL, not AMSL. Since we don't want to depend on the
463 // scenery for a precise terrain elevation, we use the field elevation
464 // (this is also what the apt.dat code does)
465   double fieldElevationM = geod().getElevationM();
466   
467   _tower_location = SGGeod::fromDegM(lon, lat, fieldElevationM + elevM);
468 }
469
470 bool FGAirport::buildApproach(Waypt* aEnroute, STAR* aSTAR, FGRunway* aRwy, WayptVec& aRoute)
471 {
472   loadProcedures();
473
474   if ((aRwy && (aRwy->airport() != this))) {
475     throw sg_exception("invalid parameters", "FGAirport::buildApproach");
476   }
477   
478   if (aSTAR) {
479     bool ok = aSTAR->route(aRwy, aEnroute, aRoute);
480     if (!ok) {
481       SG_LOG(SG_GENERAL, SG_WARN, ident() << ": build approach, STAR " << aSTAR->ident() 
482          << " failed to route from transition " << aEnroute->ident());
483       return false;
484     }
485   } else if (aEnroute) {
486     // no a STAR specified, just use enroute point directly
487     aRoute.push_back(aEnroute);
488   }
489   
490   if (!aRwy) {
491     // no runway selected yet, but we loaded the STAR, so that's fine, we're done
492     return true;
493   }
494   
495 // build the approach (possibly including transition), and including the missed segment
496   vector<Approach*> aps;
497   for (unsigned int j=0; j<mApproaches.size();++j) {
498     if (mApproaches[j]->runway() == aRwy) {
499       aps.push_back(mApproaches[j]);
500     }
501   } // of approach filter by runway
502   
503   if (aps.empty()) {
504     SG_LOG(SG_GENERAL, SG_INFO, ident() << "; no approaches defined for runway " << aRwy->ident());
505     // could build a fallback approach here
506     return false;
507   }
508   
509   for (unsigned int k=0; k<aps.size(); ++k) {
510     if (aps[k]->route(aRoute.back(), aRoute)) {
511       return true;
512     }
513   } // of initial approach iteration
514   
515   SG_LOG(SG_GENERAL, SG_INFO, ident() << ": unable to find transition to runway "
516     << aRwy->ident() << ", assume vectors");
517   
518   WayptRef v(new ATCVectors(NULL, this));
519   aRoute.push_back(v);
520   return aps.front()->routeFromVectors(aRoute);
521 }
522
523 pair<flightgear::SID*, WayptRef>
524 FGAirport::selectSID(const SGGeod& aDest, FGRunway* aRwy)
525 {
526   loadProcedures();
527   
528   WayptRef enroute;
529   flightgear::SID* sid = NULL;
530   double d = 1e9;
531   
532   for (unsigned int i=0; i<mSIDs.size(); ++i) {
533     if (aRwy && !mSIDs[i]->isForRunway(aRwy)) {
534       continue;
535     }
536   
537     WayptRef e = mSIDs[i]->findBestTransition(aDest);
538     if (!e) {
539       continue; // strange, but let's not worry about it
540     }
541     
542     // assert(e->isFixedPosition());
543     double ed = SGGeodesy::distanceM(aDest, e->position());
544     if (ed < d) { // new best match
545       enroute = e;
546       d = ed;
547       sid = mSIDs[i];
548     }
549   } // of SID iteration
550   
551   if (!mSIDs.empty() && !sid) {
552     SG_LOG(SG_GENERAL, SG_INFO, ident() << "selectSID, no SID found (runway=" 
553       << (aRwy ? aRwy->ident() : "no runway preference"));
554   }
555   
556   return std::make_pair(sid, enroute);
557 }
558     
559 pair<STAR*, WayptRef>
560 FGAirport::selectSTAR(const SGGeod& aOrigin, FGRunway* aRwy)
561 {
562   loadProcedures();
563   
564   WayptRef enroute;
565   STAR* star = NULL;
566   double d = 1e9;
567   
568   for (unsigned int i=0; i<mSTARs.size(); ++i) {
569     if (!mSTARs[i]->isForRunway(aRwy)) {
570       continue;
571     }
572     
573     SG_LOG(SG_GENERAL, SG_INFO, "STAR " << mSTARs[i]->ident() << " is valid for runway");
574     WayptRef e = mSTARs[i]->findBestTransition(aOrigin);
575     if (!e) {
576       continue; // strange, but let's not worry about it
577     }
578     
579     // assert(e->isFixedPosition());
580     double ed = SGGeodesy::distanceM(aOrigin, e->position());
581     if (ed < d) { // new best match
582       enroute = e;
583       d = ed;
584       star = mSTARs[i];
585     }
586   } // of STAR iteration
587   
588   return std::make_pair(star, enroute);
589 }
590
591
592 void FGAirport::addSID(flightgear::SID* aSid)
593 {
594   mSIDs.push_back(aSid);
595 }
596
597 void FGAirport::addSTAR(STAR* aStar)
598 {
599   mSTARs.push_back(aStar);
600 }
601
602 void FGAirport::addApproach(Approach* aApp)
603 {
604   mApproaches.push_back(aApp);
605 }
606
607 unsigned int FGAirport::numSIDs() const
608 {
609   loadProcedures();
610   return mSIDs.size();
611 }
612
613 flightgear::SID* FGAirport::getSIDByIndex(unsigned int aIndex) const
614 {
615   loadProcedures();
616   return mSIDs[aIndex];
617 }
618
619 flightgear::SID* FGAirport::findSIDWithIdent(const std::string& aIdent) const
620 {
621   loadProcedures();
622   for (unsigned int i=0; i<mSIDs.size(); ++i) {
623     if (mSIDs[i]->ident() == aIdent) {
624       return mSIDs[i];
625     }
626   }
627   
628   return NULL;
629 }
630
631 unsigned int FGAirport::numSTARs() const
632 {
633   loadProcedures();
634   return mSTARs.size();
635 }
636
637 STAR* FGAirport::getSTARByIndex(unsigned int aIndex) const
638 {
639   loadProcedures();
640   return mSTARs[aIndex];
641 }
642
643 STAR* FGAirport::findSTARWithIdent(const std::string& aIdent) const
644 {
645   loadProcedures();
646   for (unsigned int i=0; i<mSTARs.size(); ++i) {
647     if (mSTARs[i]->ident() == aIdent) {
648       return mSTARs[i];
649     }
650   }
651   
652   return NULL;
653 }
654
655 unsigned int FGAirport::numApproaches() const
656 {
657   loadProcedures();
658   return mApproaches.size();
659 }
660
661 Approach* FGAirport::getApproachByIndex(unsigned int aIndex) const
662 {
663   loadProcedures();
664   return mApproaches[aIndex];
665 }
666
667 class AirportNodeListener : public SGPropertyChangeListener
668 {
669 public:
670     AirportNodeListener()
671     {
672         SGPropertyNode* airports = fgGetNode("/sim/airport");
673         airports->addChangeListener(this, false);
674     }
675
676     virtual void valueChanged(SGPropertyNode*)
677     {
678     }
679
680     virtual void childAdded(SGPropertyNode* pr, SGPropertyNode* child)
681     {
682        FGAirport* apt = FGAirport::findByIdent(child->getName());
683        if (!apt) {
684            return;
685        }
686        
687        flightgear::PositionedBinding::bind(apt, child);
688     }
689 };
690     
691 void FGAirport::installPropertyListener()
692 {
693     new AirportNodeListener;  
694 }
695
696 flightgear::PositionedBinding*
697 FGAirport::createBinding(SGPropertyNode* nd) const
698 {
699     return new flightgear::AirportBinding(this, nd);
700 }
701
702 void FGAirport::setCommStations(CommStationList& comms)
703 {
704     mCommStations.swap(comms);
705     for (unsigned int c=0; c<mCommStations.size(); ++c) {
706         mCommStations[c]->setAirport(this);
707     }
708 }
709
710 CommStationList
711 FGAirport::commStationsOfType(FGPositioned::Type aTy) const
712 {
713     CommStationList result;
714     for (unsigned int c=0; c<mCommStations.size(); ++c) {
715         if (mCommStations[c]->type() == aTy) {
716             result.push_back(mCommStations[c]);
717         }
718     }
719     return result;
720 }
721
722 // get airport elevation
723 double fgGetAirportElev( const string& id )
724 {
725     const FGAirport *a=fgFindAirportID( id);
726     if (a) {
727         return a->getElevation();
728     } else {
729         return -9999.0;
730     }
731 }
732
733
734 // get airport position
735 SGGeod fgGetAirportPos( const string& id )
736 {
737     const FGAirport *a = fgFindAirportID( id);
738
739     if (a) {
740         return SGGeod::fromDegM(a->getLongitude(), a->getLatitude(), a->getElevation());
741     } else {
742         return SGGeod::fromDegM(0.0, 0.0, -9999.0);
743     }
744 }