]> git.mxchange.org Git - flightgear.git/blob - src/Airports/simple.cxx
Cygwin fix.
[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., 675 Mass Ave, Cambridge, MA 02139, USA.
24 //
25 // $Id$
26
27 #ifdef HAVE_CONFIG_H
28 #  include <config.h>
29 #endif
30
31 #ifdef _MSC_VER
32 #  define _USE_MATH_DEFINES
33 #endif
34 #include <math.h>
35 #include <algorithm>
36
37 #include <simgear/compiler.h>
38
39 #include <plib/sg.h>
40 #include <plib/ul.h>
41
42 #include <Environment/environment_mgr.hxx>
43 #include <Environment/environment.hxx>
44 #include <simgear/misc/sg_path.hxx>
45 #include <simgear/props/props.hxx>
46 #include <simgear/structure/subsystem_mgr.hxx>
47 #include <simgear/debug/logstream.hxx>
48 #include <Main/globals.hxx>
49 #include <Main/fg_props.hxx>
50 #include <Airports/runways.hxx>
51
52 #include STL_STRING
53
54 #include "simple.hxx"
55
56 SG_USING_STD(sort);
57 SG_USING_STD(random_shuffle);
58
59 #ifdef __CYGWIN__
60 #define HUGE HUGE_VAL
61 #endif
62
63 /******************************************************************************
64  * ScheduleTime
65  ***************e*************************************************************/
66 void ScheduleTime::clear()
67
68   start.clear();
69   end.clear();
70   scheduleNames.clear();
71 }
72
73
74 ScheduleTime::ScheduleTime(const ScheduleTime &other) 
75 {
76   //timeVec   start;
77   timeVecConstIterator i;
78   for (i = other.start.begin(); i != other.start.end(); i++)
79     start.push_back(*i);
80    for (i = other.end.begin(); i != other.end.end(); i++)
81     end.push_back(*i);
82    stringVecConstIterator k;
83    for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
84      scheduleNames.push_back(*k);
85   
86   //timeVec   end;
87   //stringVec scheduleNames;
88   tailWind = other.tailWind;
89   crssWind = other.tailWind;
90 }
91
92
93 ScheduleTime & ScheduleTime::operator= (const ScheduleTime &other) 
94 {
95   //timeVec   start;
96   clear();
97   timeVecConstIterator i;
98   for (i = other.start.begin(); i != other.start.end(); i++)
99     start.push_back(*i);
100    for (i = other.end.begin(); i != other.end.end(); i++)
101     end.push_back(*i);
102    stringVecConstIterator k;
103    for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
104      scheduleNames.push_back(*k);
105   
106   //timeVec   end;
107   //stringVec scheduleNames;
108   tailWind = other.tailWind;
109   crssWind = other.tailWind;
110   return *this;
111 }
112 string ScheduleTime::getName(time_t dayStart)
113 {
114   if ((start.size() != end.size()) || (start.size() != scheduleNames.size()))
115     {
116       cerr << "Unable to parse schedule times" << endl;
117       exit(1);
118     }
119   else
120     {
121       int nrItems = start.size();
122       //cerr << "Nr of items to process: " << nrItems << endl;
123       if (nrItems > 0)
124         {
125           for (unsigned int i = 0; i < start.size(); i++)
126             {
127               //cerr << i << endl;
128               if ((dayStart >= start[i]) && (dayStart <= end[i]))
129                 return scheduleNames[i];
130             }
131         }
132       //couldn't find one so return 0;
133       //cerr << "Returning 0 " << endl;
134       return string(0);
135     }
136 }                             
137 /******************************************************************************
138  * RunwayList
139  *****************************************************************************/
140
141 RunwayList::RunwayList(const RunwayList &other)
142 {
143   type = other.type;
144   stringVecConstIterator i;
145   for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
146     preferredRunways.push_back(*i);
147 }
148 RunwayList& RunwayList::operator= (const RunwayList &other)
149 {
150   type = other.type;
151   preferredRunways.clear();
152   stringVecConstIterator i;
153   for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
154     preferredRunways.push_back(*i);
155   return *this;
156 }
157 void RunwayList::set(string tp, string lst)
158 {
159   //weekday          = atoi(timeCopy.substr(0,1).c_str());
160   //    timeOffsetInDays = weekday - currTimeDate->getGmt()->tm_wday;
161   //    timeCopy = timeCopy.substr(2,timeCopy.length());
162   type = tp;
163   string rwys = lst;
164   string rwy;
165   while (rwys.find(",") != string::npos)
166     {
167       rwy = rwys.substr(0, rwys.find(",",0));
168       //cerr << "adding runway [" << rwy << "] to the list " << endl;
169       preferredRunways.push_back(rwy);
170       rwys.erase(0, rwys.find(",",0)+1); // erase until after the first whitspace
171       while (rwys[0] == ' ')
172         rwys.erase(0, 1); // Erase any leading whitespaces.
173       //cerr << "Remaining runway list " << rwys;
174     } 
175   preferredRunways.push_back(rwys);
176   //exit(1);
177 }
178
179 void RunwayList::clear() 
180 {
181   type = "";
182   preferredRunways.clear();
183 }
184 /****************************************************************************
185  *
186  ***************************************************************************/
187
188 RunwayGroup::RunwayGroup(const RunwayGroup &other)
189 {
190   name = other.name; 
191   RunwayListVecConstIterator i;
192   for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
193     rwyList.push_back(*i);
194   choice[0] = other.choice[0];
195   choice[1] = other.choice[1];
196   nrActive = other.nrActive;
197 }
198 RunwayGroup& RunwayGroup:: operator= (const RunwayGroup &other)
199
200   rwyList.clear();
201   name = other.name; 
202   RunwayListVecConstIterator i;
203   for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
204     rwyList.push_back(*i); 
205   choice[0] = other.choice[0];
206   choice[1] = other.choice[1];
207   nrActive = other.nrActive;
208   return *this;
209 }
210
211 void RunwayGroup::setActive(string aptId, 
212                             double windSpeed, 
213                             double windHeading, 
214                             double maxTail, 
215                             double maxCross)
216 {
217
218   FGRunway rwy;
219   int activeRwys = rwyList.size(); // get the number of runways active
220   int nrOfPreferences;
221   // bool found = true;
222   // double heading;
223   double hdgDiff;
224   double crossWind;
225   double tailWind;
226   string name;
227
228   if (activeRwys > 0)
229     {
230       nrOfPreferences = rwyList[0].getRwyList()->size();
231       for (int i = 0; i < nrOfPreferences; i++)
232         {
233           bool validSelection = true;
234           for (int j = 0; j < activeRwys; j++)
235             {
236               //cerr << "I J " << i << " " << j << endl;
237               name = rwyList[j].getRwyList(i);
238               //cerr << "Name of Runway: " << name << endl;
239               if (globals->get_runways()->search( aptId, 
240                                                   name, 
241                                                   &rwy))
242                 {
243                   //cerr << "Succes" << endl;
244                   hdgDiff = fabs(windHeading - rwy._heading);
245                   //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
246                   //cerr << "Wind Speed  : " << windSpeed << endl;
247                   if (hdgDiff > 180)
248                     hdgDiff = 360 - hdgDiff;
249                   //cerr << "Heading diff: " << hdgDiff << endl;
250                   hdgDiff *= ((2*M_PI)/360.0); // convert to radians
251                   crossWind = windSpeed * sin(hdgDiff);
252                   tailWind  = -windSpeed * cos(hdgDiff);
253                   //cerr << "Tailwind : " << tailWind << endl;
254                   //cerr << "Crosswnd : " << crossWind << endl;
255                   if ((tailWind > maxTail) || (crossWind > maxCross))
256                     validSelection = false;
257                 }else {
258                   cerr << "Failed to find runway " << name << " at " << aptId << endl;
259                   exit(1);
260                 }
261
262             }
263           if (validSelection)
264             {
265               //cerr << "Valid runay selection : " << i << endl;
266               nrActive = activeRwys;
267               active = i;
268               return;
269             }
270         }
271       // If this didn't work, due to heavy winds, try again
272       // but select only one landing and one takeoff runway. 
273       choice[0] = 0;
274       choice[1] = 0;
275       for (int i = activeRwys-1;  i; i--)
276         {
277           if (rwyList[i].getType() == string("landing"))
278             choice[0] = i;
279           if (rwyList[i].getType() == string("takeoff"))
280             choice[1] = i;
281         }
282       //cerr << "Choosing " << choice[0] << " for landing and " << choice[1] << "for takeoff" << endl;
283       nrOfPreferences = rwyList[0].getRwyList()->size();
284       for (int i = 0; i < nrOfPreferences; i++)
285         {
286           bool validSelection = true;
287           for (int j = 0; j < 2; j++)
288             {
289               //cerr << "I J " << i << " " << j << endl;
290               name = rwyList[choice[j]].getRwyList(i);
291               //cerr << "Name of Runway: " << name << endl;
292               if (globals->get_runways()->search( aptId, 
293                                                   name, 
294                                                   &rwy))
295                 {
296                   //cerr << "Succes" << endl;
297                   hdgDiff = fabs(windHeading - rwy._heading);
298                   //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
299                   //cerr << "Wind Speed  : " << windSpeed << endl;
300                   if (hdgDiff > 180)
301                     hdgDiff = 360 - hdgDiff;
302                   //cerr << "Heading diff: " << hdgDiff << endl;
303                   hdgDiff *= ((2*M_PI)/360.0); // convert to radians
304                   crossWind = windSpeed * sin(hdgDiff);
305                   tailWind  = -windSpeed * cos(hdgDiff);
306                   //cerr << "Tailwind : " << tailWind << endl;
307                   //cerr << "Crosswnd : " << crossWind << endl;
308                   if ((tailWind > maxTail) || (crossWind > maxCross))
309                     validSelection = false;
310                 }else {
311                   cerr << "Failed to find runway " << name << " at " << aptId << endl;
312                   exit(1);
313                 }
314
315             }
316           if (validSelection)
317             {
318               //cerr << "Valid runay selection : " << i << endl;
319               active = i;
320               nrActive = 2;
321               return;
322             }
323         }
324     }
325   active = -1;
326   //RunwayListVectorIterator i; // = rwlist.begin();
327   //stringVecIterator j;
328   //for (i = rwyList.begin(); i != rwyList.end(); i++)
329   //  {
330   //    cerr << i->getType();
331   //    for (j = i->getRwyList()->begin(); j != i->getRwyList()->end(); j++)
332   //    {                                 
333   //      cerr << (*j);
334   //    }
335   //    cerr << endl;
336   //  }
337   //for (int
338
339 }
340
341 void RunwayGroup::getActive(int i, string *name, string *type)
342 {
343   if (i == -1)
344     {
345       return;
346     }
347   if (nrActive == (int)rwyList.size())
348     {
349       *name = rwyList[i].getRwyList(active);
350       *type = rwyList[i].getType();
351     }
352   else
353     { 
354       *name = rwyList[choice[i]].getRwyList(active);
355       *type = rwyList[choice[i]].getType();
356     }
357 }
358 /*****************************************************************************
359  * FGRunway preference
360  ****************************************************************************/
361 FGRunwayPreference::FGRunwayPreference()
362 {
363   //cerr << "Running default Constructor" << endl;
364   initialized = false;
365 }
366
367 FGRunwayPreference::FGRunwayPreference(const FGRunwayPreference &other)
368 {
369   initialized = other.initialized;
370   value = other.value;
371   scheduleName = other.scheduleName;
372
373   comTimes = other.comTimes; // Commercial Traffic;
374   genTimes = other.genTimes; // General Aviation;
375   milTimes = other.milTimes; // Military Traffic;
376   currTimes= other.currTimes; // Needed for parsing;
377
378   rwyList = other.rwyList;
379   rwyGroup = other.rwyGroup;
380   PreferenceListConstIterator i;
381   for (i = other.preferences.begin(); i != other.preferences.end(); i++)
382     preferences.push_back(*i);
383 }
384   
385 FGRunwayPreference & FGRunwayPreference::operator= (const FGRunwayPreference &other)
386 {
387   initialized = other.initialized;
388   value = other.value;
389   scheduleName = other.scheduleName;
390   
391   comTimes = other.comTimes; // Commercial Traffic;
392   genTimes = other.genTimes; // General Aviation;
393   milTimes = other.milTimes; // Military Traffic;
394   currTimes= other.currTimes; // Needed for parsing;
395   
396   rwyList = other.rwyList;
397   rwyGroup = other.rwyGroup;
398   PreferenceListConstIterator i;
399   preferences.clear();
400   for (i = other.preferences.begin(); i != other.preferences.end(); i++)
401     preferences.push_back(*i);
402   return *this;
403 }
404
405 ScheduleTime *FGRunwayPreference::getSchedule(const char *trafficType)
406 {
407   if (!(strcmp(trafficType, "com"))) {
408     return &comTimes;
409   }
410   if (!(strcmp(trafficType, "gen"))) {
411     return &genTimes;
412   }
413   if (!(strcmp(trafficType, "mil"))) {
414     return &milTimes;
415   }
416   return 0;
417 }
418
419 RunwayGroup *FGRunwayPreference::getGroup(const string groupName)
420 {
421   PreferenceListIterator i = preferences.begin();
422   if (preferences.begin() == preferences.end())
423     return 0;
424   while (!(i == preferences.end() || i->getName() == groupName))
425     i++;
426   if (i != preferences.end())
427     return &(*i);
428   else
429     return 0;
430 }
431
432 void  FGRunwayPreference::startXML () {
433   //  cout << "Start XML" << endl;
434 }
435
436 void  FGRunwayPreference::endXML () {
437   cout << "End XML" << endl;
438 }
439
440 void  FGRunwayPreference::startElement (const char * name, const XMLAttributes &atts) {
441   //cout << "StartElement " << name << endl;
442   value = string("");
443   if (!(strcmp(name, "wind"))) {
444     //cerr << "Will be processing Wind" << endl;
445     for (int i = 0; i < atts.size(); i++)
446       {
447         //cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
448         //attname = atts.getName(i);
449         if (atts.getName(i) == string("tail")) {
450           //cerr << "Tail Wind = " << atts.getValue(i) << endl;
451           currTimes.setTailWind(atof(atts.getValue(i)));
452         }       
453         if (atts.getName(i) == string("cross")) {
454           //cerr << "Cross Wind = " << atts.getValue(i) << endl;
455           currTimes.setCrossWind(atof(atts.getValue(i)));
456         }
457      }
458   }
459     if (!(strcmp(name, "time"))) {
460       //cerr << "Will be processing time" << endl;      
461     for (int i = 0; i < atts.size(); i++)
462       {
463         if (atts.getName(i) == string("start")) {
464           //cerr << "Start Time = " << atts.getValue(i) << endl;
465           currTimes.addStartTime(processTime(atts.getValue(i)));
466         }
467         if (atts.getName(i) == string("end")) {
468           //cerr << "End time = " << atts.getValue(i) << endl;
469           currTimes.addEndTime(processTime(atts.getValue(i)));
470         }
471         if (atts.getName(i) == string("schedule")) {
472           //cerr << "Schedule Name  = " << atts.getValue(i) << endl;
473           currTimes.addScheduleName(atts.getValue(i));
474         }       
475     }
476   }
477   if (!(strcmp(name, "takeoff"))) {
478     rwyList.clear();
479   }
480   if  (!(strcmp(name, "landing")))
481     {
482       rwyList.clear();
483     }
484   if (!(strcmp(name, "schedule"))) {
485     for (int i = 0; i < atts.size(); i++)
486       {
487         //cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
488         //attname = atts.getName(i);
489         if (atts.getName(i) == string("name")) {
490           //cerr << "Schedule name = " << atts.getValue(i) << endl;
491           scheduleName = atts.getValue(i);
492         }
493       }
494   }
495 }
496
497 //based on a string containing hour and minute, return nr seconds since day start.
498 time_t FGRunwayPreference::processTime(string tme)
499 {
500   string hour   = tme.substr(0, tme.find(":",0));
501   string minute = tme.substr(tme.find(":",0)+1, tme.length());
502
503   //cerr << "hour = " << hour << " Minute = " << minute << endl;
504   return (atoi(hour.c_str()) * 3600 + atoi(minute.c_str()) * 60);
505 }
506
507 void  FGRunwayPreference::endElement (const char * name) {
508   //cout << "End element " << name << endl;
509   if (!(strcmp(name, "rwyuse"))) {
510     initialized = true;
511   }
512   if (!(strcmp(name, "com"))) { // Commercial Traffic
513     //cerr << "Setting time table for commerical traffic" << endl;
514     comTimes = currTimes;
515     currTimes.clear();
516   }
517   if (!(strcmp(name, "gen"))) { // General Aviation
518     //cerr << "Setting time table for general aviation" << endl;
519     genTimes = currTimes;
520     currTimes.clear();
521   }  
522   if (!(strcmp(name, "mil"))) { // Military Traffic
523     //cerr << "Setting time table for military traffic" << endl;
524     genTimes = currTimes;
525     currTimes.clear();
526   }
527
528   if (!(strcmp(name, "takeoff"))) {
529     //cerr << "Adding takeoff: " << value << endl;
530     rwyList.set(name, value);
531     rwyGroup.add(rwyList);
532   }
533   if (!(strcmp(name, "landing"))) {
534     //cerr << "Adding landing: " << value << endl;
535     rwyList.set(name, value);
536     rwyGroup.add(rwyList);
537   }
538   if (!(strcmp(name, "schedule"))) {
539     //cerr << "Adding schedule" << scheduleName << endl;
540     rwyGroup.setName(scheduleName);
541     //rwyGroup.addRunways(rwyList);
542     preferences.push_back(rwyGroup);
543     rwyGroup.clear();
544     //exit(1);
545   }
546 }
547
548 void  FGRunwayPreference::data (const char * s, int len) {
549   string token = string(s,len);
550   //cout << "Character data " << string(s,len) << endl;
551   //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
552   //  value += token;
553   //else
554   //  value = string("");
555   value += token;
556 }
557
558 void  FGRunwayPreference::pi (const char * target, const char * data) {
559   //cout << "Processing instruction " << target << ' ' << data << endl;
560 }
561
562 void  FGRunwayPreference::warning (const char * message, int line, int column) {
563   cout << "Warning: " << message << " (" << line << ',' << column << ')'   
564        << endl;
565 }
566
567 void  FGRunwayPreference::error (const char * message, int line, int column) {
568   cout << "Error: " << message << " (" << line << ',' << column << ')'
569        << endl;
570 }
571
572 /*****************************************************************************
573  * Helper function for parsing position string
574  ****************************************************************************/
575 double processPosition(string pos)
576 {
577   string prefix;
578   string subs;
579   string degree;
580   string decimal;
581   int sign = 1;
582   double value;
583   subs = pos;
584   prefix= subs.substr(0,1);
585   if (prefix == string("S") || (prefix == string("W")))
586     sign = -1;
587   subs    = subs.substr(1, subs.length());
588   degree  = subs.substr(0, subs.find(" ",0));
589   decimal = subs.substr(subs.find(" ",0), subs.length());
590   
591               
592   //cerr << sign << " "<< degree << " " << decimal << endl;
593   value = sign * (atof(degree.c_str()) + atof(decimal.c_str())/60.0);
594   //cerr << value <<endl;
595   //exit(1);
596   return value;
597 }
598
599
600 /*********************************************************************************
601  * FGParking
602  ********************************************************************************/
603 FGParking::FGParking(double lat,
604                      double lon,
605                      double hdg,
606                      double rad,
607                      int idx,
608                      string name,
609                      string tpe,
610                      string codes)
611 {
612   latitude     = lat;
613   longitude    = lon;
614   heading      = hdg;
615   parkingName  = name;
616   index        = idx;
617   type         = tpe;
618   airlineCodes = codes;
619 }
620
621
622 /***************************************************************************
623  * FGAirport
624  ***************************************************************************/
625 FGAirport::FGAirport() : _longitude(0), _latitude(0), _elevation(0)
626 {
627   lastUpdate = 0;
628   for (int i = 0; i < 10; i++)
629     {
630       avWindHeading [i] = 0;
631       avWindSpeed   [i] = 0;
632     }
633 }
634
635 FGAirport::FGAirport(const FGAirport& other)
636 {
637   _id = other._id;
638   _longitude = other._longitude;
639   _latitude  = other._latitude;
640   _elevation = other._elevation;
641   _name      = other._name;
642   _has_metar = other._has_metar;
643   for (FGParkingVecConstIterator ip= other.parkings.begin(); ip != other.parkings.end(); ip++)
644     parkings.push_back(*(ip));
645   rwyPrefs = other.rwyPrefs;
646   lastUpdate = other.lastUpdate;
647   
648   stringVecConstIterator il;
649   for (il = other.landing.begin(); il != other.landing.end(); il++)
650     landing.push_back(*il);
651   for (il = other.takeoff.begin(); il != other.takeoff.end(); il++)
652     takeoff.push_back(*il);
653   lastUpdate = other.lastUpdate;
654   for (int i = 0; i < 10; i++)
655     {
656       avWindHeading [i] = other.avWindHeading[i];
657       avWindSpeed   [i] = other.avWindSpeed  [i];
658     }
659 }
660
661 FGAirport::FGAirport(string id, double lon, double lat, double elev, string name, bool has_metar)
662 {
663   _id = id;
664   _longitude = lon;
665   _latitude  = lat;
666   _elevation = elev;
667   _name      = name;
668   _has_metar = has_metar; 
669   lastUpdate = 0;
670   for (int i = 0; i < 10; i++)
671     {
672       avWindHeading [i] = 0;
673       avWindSpeed   [i] = 0;
674     }
675  
676 }
677
678 // Initialization required after XMLRead
679 void FGAirport::init() 
680 {
681   // This may seem a bit weird to first randomly shuffle the parkings
682   // and then sort them again. However, parkings are sorted here by ascending 
683   // radius. Since many parkings have similar radii, with each radius class they will
684   // still be allocated relatively systematically. Randomizing prior to sorting will
685   // prevent any initial orderings to be destroyed, leading (hopefully) to a more 
686   // naturalistic gate assignment. 
687   random_shuffle(parkings.begin(), parkings.end());
688   sort(parkings.begin(), parkings.end());
689   // add the gate positions to the ground network. 
690   groundNetwork.addNodes(&parkings);
691   groundNetwork.init();
692 }
693
694 bool FGAirport::getAvailableParking(double *lat, double *lon, double *heading, int *gateId, double rad, string flType, string acType, string airline)
695 {
696   bool found = false;
697   bool available = false;
698   //string gateType;
699
700   FGParkingVecIterator i;
701 //   if (flType == "cargo")
702 //     {
703 //       gateType = "RAMP_CARGO";
704 //     }
705 //   else if (flType == "ga")
706 //     {
707 //       gateType = "RAMP_GA";
708 //     }
709 //   else gateType = "GATE";
710   
711   if (parkings.begin() == parkings.end())
712     {
713       //cerr << "Could not find parking spot at " << _id << endl;
714       *lat = _latitude;
715       *lon = _longitude;
716       *heading = 0;
717       found = true;
718     }
719   else
720     {
721       // First try finding a parking with a designated airline code
722       for (i = parkings.begin(); !(i == parkings.end() || found); i++)
723         {
724           //cerr << "Gate Id: " << i->getIndex()
725           //     << " Type  : " << i->getType()
726           //     << " Codes : " << i->getCodes()
727           //     << " Radius: " << i->getRadius()
728           //     << " Name  : " << i->getName()
729           //     << " Available: " << i->isAvailable() << endl;
730           available = true;
731           // Taken by another aircraft
732           if (!(i->isAvailable()))
733             {
734               available = false;
735               continue;
736             }
737           // No airline codes, so skip
738           if (i->getCodes().empty())
739             {
740               available = false;
741               continue;
742             }
743           else // Airline code doesn't match
744             if (i->getCodes().find(airline, 0) == string::npos)
745               {
746                 available = false;
747                 continue;
748               }
749           // Type doesn't match
750           if (i->getType() != flType)
751             {
752               available = false;
753               continue;
754             }
755           // too small
756           if (i->getRadius() < rad)
757             {
758               available = false;
759               continue;
760             }
761           
762           if (available)
763             {
764               *lat     = i->getLatitude ();
765               *lon     = i->getLongitude();
766               *heading = i->getHeading  ();
767               *gateId  = i->getIndex    ();
768               i->setAvailable(false);
769               found = true;
770             }
771         }
772       // then try again for those without codes. 
773       for (i = parkings.begin(); !(i == parkings.end() || found); i++)
774         {
775           available = true;
776           if (!(i->isAvailable()))
777             {
778               available = false;
779               continue;
780             }
781           if (!(i->getCodes().empty()))
782             {
783               if ((i->getCodes().find(airline,0) == string::npos))
784           {
785             available = false;
786             continue;
787           }
788             }
789           if (i->getType() != flType)
790             {
791               available = false;
792               continue;
793             }
794               
795           if (i->getRadius() < rad)
796             {
797               available = false;
798               continue;
799             }
800           
801           if (available)
802             {
803               *lat     = i->getLatitude ();
804               *lon     = i->getLongitude();
805               *heading = i->getHeading  ();
806               *gateId  = i->getIndex    ();
807               i->setAvailable(false);
808               found = true;
809             }
810         } 
811       // And finally once more if that didn't work. Now ignore the airline codes, as a last resort
812       for (i = parkings.begin(); !(i == parkings.end() || found); i++)
813         {
814           available = true;
815           if (!(i->isAvailable()))
816             {
817               available = false;
818               continue;
819             }
820           if (i->getType() != flType)
821             {
822               available = false;
823               continue;
824             }
825           
826           if (i->getRadius() < rad)
827             {
828               available = false;
829               continue;
830             }
831           
832           if (available)
833             {
834               *lat     = i->getLatitude ();
835               *lon     = i->getLongitude();
836               *heading = i->getHeading  ();
837               *gateId  = i->getIndex    ();
838               i->setAvailable(false);
839               found = true;
840             }
841         }
842     }
843   if (!found)
844     {
845       //cerr << "Traffic overflow at" << _id 
846       //           << ". flType = " << flType 
847       //           << ". airline = " << airline 
848       //           << " Radius = " <<rad
849       //           << endl;
850       *lat = _latitude;
851       *lon = _longitude;
852       *heading = 0;
853       *gateId  = -1;
854       //exit(1);
855     }
856   return found;
857 }
858
859 void FGAirport::getParking (int id, double *lat, double* lon, double *heading)
860 {
861   if (id < 0)
862     {
863       *lat = _latitude;
864       *lon = _longitude;
865       *heading = 0;
866     }
867   else
868     {
869       FGParkingVecIterator i = parkings.begin();
870       for (i = parkings.begin(); i != parkings.end(); i++)
871         {
872           if (id == i->getIndex())
873             {
874               *lat     = i->getLatitude();
875               *lon     = i->getLongitude();
876               *heading = i->getLongitude();
877             }
878         }
879     }
880
881
882 FGParking *FGAirport::getParking(int i) 
883
884   if (i < (int)parkings.size()) 
885     return &(parkings[i]); 
886   else 
887     return 0;
888 }
889 string FGAirport::getParkingName(int i) 
890
891   if (i < (int)parkings.size() && i >= 0) 
892     return (parkings[i].getName()); 
893   else 
894     return string("overflow");
895 }
896 void FGAirport::releaseParking(int id)
897 {
898   if (id >= 0)
899     {
900       
901       FGParkingVecIterator i = parkings.begin();
902       for (i = parkings.begin(); i != parkings.end(); i++)
903         {
904           if (id == i->getIndex())
905             {
906               i -> setAvailable(true);
907             }
908         }
909     }
910 }
911   
912 void  FGAirport::startXML () {
913   //cout << "Start XML" << endl;
914 }
915
916 void  FGAirport::endXML () {
917   //cout << "End XML" << endl;
918 }
919
920 void  FGAirport::startElement (const char * name, const XMLAttributes &atts) {
921   // const char *attval;
922   FGParking park;
923   FGTaxiNode taxiNode;
924   FGTaxiSegment taxiSegment;
925   int index = 0;
926   taxiSegment.setIndex(index);
927   //cout << "Start element " << name << endl;
928   string attname;
929   string value;
930   string gateName;
931   string gateNumber;
932   string lat;
933   string lon;
934   if (name == string("Parking"))
935     {
936       for (int i = 0; i < atts.size(); i++)
937         {
938           //cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
939           attname = atts.getName(i);
940           if (attname == string("index"))
941             park.setIndex(atoi(atts.getValue(i)));
942           else if (attname == string("type"))
943             park.setType(atts.getValue(i));
944          else if (attname == string("name"))
945            gateName = atts.getValue(i);
946           else if (attname == string("number"))
947             gateNumber = atts.getValue(i);
948           else if (attname == string("lat"))
949            park.setLatitude(atts.getValue(i));
950           else if (attname == string("lon"))
951             park.setLongitude(atts.getValue(i)); 
952           else if (attname == string("heading"))
953             park.setHeading(atof(atts.getValue(i)));
954           else if (attname == string("radius")) {
955             string radius = atts.getValue(i);
956             if (radius.find("M") != string::npos)
957               radius = radius.substr(0, radius.find("M",0));
958             //cerr << "Radius " << radius <<endl;
959             park.setRadius(atof(radius.c_str()));
960           }
961            else if (attname == string("airlineCodes"))
962              park.setCodes(atts.getValue(i));
963         }
964       park.setName((gateName+gateNumber));
965       parkings.push_back(park);
966     }
967   if (name == string("node")) 
968     {
969       for (int i = 0; i < atts.size() ; i++)
970         {
971           attname = atts.getName(i);
972           if (attname == string("index"))
973             taxiNode.setIndex(atoi(atts.getValue(i)));
974           if (attname == string("lat"))
975             taxiNode.setLatitude(atts.getValue(i));
976           if (attname == string("lon"))
977             taxiNode.setLongitude(atts.getValue(i));
978         }
979       groundNetwork.addNode(taxiNode);
980     }
981   if (name == string("arc")) 
982     {
983       taxiSegment.setIndex(++index);
984       for (int i = 0; i < atts.size() ; i++)
985         {
986           attname = atts.getName(i);
987           if (attname == string("begin"))
988             taxiSegment.setStartNodeRef(atoi(atts.getValue(i)));
989           if (attname == string("end"))
990             taxiSegment.setEndNodeRef(atoi(atts.getValue(i)));
991         }
992       groundNetwork.addSegment(taxiSegment);
993     }
994   // sort by radius, in asending order, so that smaller gates are first in the list
995 }
996
997 void  FGAirport::endElement (const char * name) {
998   //cout << "End element " << name << endl;
999
1000 }
1001
1002 void  FGAirport::data (const char * s, int len) {
1003   string token = string(s,len);
1004   //cout << "Character data " << string(s,len) << endl;
1005   //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
1006     //value += token;
1007   //else
1008     //value = string("");
1009 }
1010
1011 void  FGAirport::pi (const char * target, const char * data) {
1012   //cout << "Processing instruction " << target << ' ' << data << endl;
1013 }
1014
1015 void  FGAirport::warning (const char * message, int line, int column) {
1016   cout << "Warning: " << message << " (" << line << ',' << column << ')'   
1017        << endl;
1018 }
1019
1020 void  FGAirport::error (const char * message, int line, int column) {
1021   cout << "Error: " << message << " (" << line << ',' << column << ')'
1022        << endl;
1023 }
1024
1025 void FGAirport::setRwyUse(FGRunwayPreference& ref)
1026 {
1027   rwyPrefs = ref;
1028   //cerr << "Exiting due to not implemented yet" << endl;
1029   //exit(1);
1030 }
1031 void FGAirport::getActiveRunway(string trafficType, int action, string *runway)
1032 {
1033   double windSpeed;
1034   double windHeading;
1035   double maxTail;
1036   double maxCross;
1037   string name;
1038   string type;
1039
1040   if (!(rwyPrefs.available()))
1041     {
1042       chooseRunwayFallback(runway);
1043       return; // generic fall back goes here
1044     }
1045   else
1046     {
1047       RunwayGroup *currRunwayGroup = 0;
1048       int nrActiveRunways = 0;
1049       time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
1050       if (((dayStart - lastUpdate) > 600) || trafficType != prevTrafficType)
1051         {
1052           landing.clear();
1053           takeoff.clear();
1054           //lastUpdate = dayStart;
1055           prevTrafficType = trafficType;
1056
1057           FGEnvironment 
1058             stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1059             ->getEnvironment(getLatitude(), 
1060                              getLongitude(), 
1061                              getElevation());
1062           
1063           windSpeed = stationweather.get_wind_speed_kt();
1064           windHeading = stationweather.get_wind_from_heading_deg();
1065           double averageWindSpeed   = 0;
1066           double averageWindHeading = 0;
1067           double cosHeading         = 0;
1068           double sinHeading         = 0;
1069           // Initialize at the beginning of the next day or startup
1070           if ((lastUpdate == 0) || (dayStart < lastUpdate))
1071             {
1072               for (int i = 0; i < 10; i++)
1073                 {
1074                   avWindHeading [i] = windHeading;
1075                   avWindSpeed   [i] = windSpeed;
1076                 }
1077             }
1078           else
1079             {
1080               if (windSpeed != avWindSpeed[9]) // update if new metar data 
1081                 {
1082                   // shift the running average
1083                   for (int i = 0; i < 9 ; i++)
1084                     {
1085                       avWindHeading[i] = avWindHeading[i+1];
1086                       avWindSpeed  [i] = avWindSpeed  [i+1];
1087                     }
1088                 } 
1089               avWindHeading[9] = windHeading;
1090               avWindSpeed  [9] = windSpeed;
1091             }
1092           
1093           for (int i = 0; i < 10; i++)
1094             {
1095               averageWindSpeed   += avWindSpeed   [i];
1096               //averageWindHeading += avWindHeading [i];
1097               cosHeading += cos(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1098               sinHeading += sin(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1099             }
1100           averageWindSpeed   /= 10;
1101           //averageWindHeading /= 10;
1102           cosHeading /= 10;
1103           sinHeading /= 10;
1104           averageWindHeading = atan2(sinHeading, cosHeading) *SG_RADIANS_TO_DEGREES;
1105           if (averageWindHeading < 0)
1106             averageWindHeading += 360.0;
1107           //cerr << "Wind Heading " << windHeading << " average " << averageWindHeading << endl;
1108           //cerr << "Wind Speed   " << windSpeed   << " average " << averageWindSpeed   << endl;
1109           lastUpdate = dayStart;
1110               //if (wind_speed == 0) {
1111           //  wind_heading = 270;        This forces West-facing rwys to be used in no-wind situations
1112             // which is consistent with Flightgear's initial setup.
1113           //}
1114           
1115           //string rwy_no = globals->get_runways()->search(apt->getId(), int(wind_heading));
1116           string scheduleName;
1117           //cerr << "finding active Runway for" << _id << endl;
1118           //cerr << "Nr of seconds since day start << " << dayStart << endl;
1119           ScheduleTime *currSched;
1120           //cerr << "A"<< endl;
1121           currSched = rwyPrefs.getSchedule(trafficType.c_str());
1122           if (!(currSched))
1123             return;   
1124           //cerr << "B"<< endl;
1125           scheduleName = currSched->getName(dayStart);
1126           maxTail  = currSched->getTailWind  ();
1127           maxCross = currSched->getCrossWind ();
1128           //cerr << "SChedule anme = " << scheduleName << endl;
1129           if (scheduleName.empty())
1130             return;
1131           //cerr << "C"<< endl;
1132           currRunwayGroup = rwyPrefs.getGroup(scheduleName); 
1133           //cerr << "D"<< endl;
1134           if (!(currRunwayGroup))
1135             return;
1136           nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1137           //cerr << "Nr of Active Runways = " << nrActiveRunways << endl; 
1138           currRunwayGroup->setActive(_id, averageWindSpeed, averageWindHeading, maxTail, maxCross); 
1139           nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1140           for (int i = 0; i < nrActiveRunways; i++)
1141             {
1142               type = "unknown"; // initialize to something other than landing or takeoff
1143               currRunwayGroup->getActive(i, &name, &type);
1144               if (type == "landing")
1145                 {
1146                   landing.push_back(name);
1147                   //cerr << "Landing " << name << endl; 
1148                 }
1149               if (type == "takeoff")
1150                 {
1151                   takeoff.push_back(name);
1152                   //cerr << "takeoff " << name << endl;
1153                 }
1154             }
1155         }
1156       if (action == 1) // takeoff 
1157         {
1158           int nr = takeoff.size();
1159           if (nr)
1160             {
1161               *runway = takeoff[(rand() %  nr)];
1162             }
1163           else
1164             { // Fallback
1165               chooseRunwayFallback(runway);
1166             }
1167         } 
1168       if (action == 2) // landing
1169         {
1170           int nr = landing.size();
1171           if (nr)
1172             {
1173               *runway = landing[(rand() % nr)];
1174             }
1175           else
1176             {  //fallback
1177                chooseRunwayFallback(runway);
1178             }
1179         }
1180       
1181       //*runway = globals->get_runways()->search(_id, int(windHeading));
1182       //cerr << "Seleceted runway: " << *runway << endl;
1183     }
1184 }
1185
1186 void FGAirport::chooseRunwayFallback(string *runway)
1187 {   
1188   FGEnvironment 
1189     stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1190     ->getEnvironment(getLatitude(), 
1191                      getLongitude(),
1192                      getElevation());
1193   
1194   double windSpeed = stationweather.get_wind_speed_kt();
1195   double windHeading = stationweather.get_wind_from_heading_deg();
1196   if (windSpeed == 0) {
1197     windHeading = 270;  // This forces West-facing rwys to be used in no-wind situations
1198     //which is consistent with Flightgear's initial setup.
1199   }
1200   
1201   *runway = globals->get_runways()->search(_id, int(windHeading));
1202   return; // generic fall back goes here
1203 }
1204
1205
1206
1207 /**************************************************************************
1208  * FGTaxiNode
1209  *************************************************************************/
1210 FGTaxiNode::FGTaxiNode()
1211 {
1212 }
1213
1214 /***************************************************************************
1215  * FGTaxiSegment
1216  **************************************************************************/
1217 FGTaxiSegment::FGTaxiSegment()
1218 {
1219 }
1220
1221 void FGTaxiSegment::setStart(FGTaxiNodeVector *nodes)
1222 {
1223   FGTaxiNodeVectorIterator i = nodes->begin();
1224   while (i != nodes->end())
1225     {
1226       if (i->getIndex() == startNode)
1227         {
1228           start = i->getAddress();
1229           i->addSegment(this);
1230           return;
1231         }
1232       i++;
1233     }
1234 }
1235
1236 void FGTaxiSegment::setEnd(FGTaxiNodeVector *nodes)
1237 {
1238   FGTaxiNodeVectorIterator i = nodes->begin();
1239   while (i != nodes->end())
1240     {
1241       if (i->getIndex() == endNode)
1242         {
1243           end = i->getAddress();
1244           return;
1245         }
1246       i++;
1247     }
1248 }
1249
1250 // There is probably a computationally cheaper way of 
1251 // doing this.
1252 void FGTaxiSegment::setTrackDistance()
1253 {
1254   double course;
1255   SGWayPoint first  (start->getLongitude(),
1256                      start->getLatitude(),
1257                      0);
1258   SGWayPoint second (end->getLongitude(),
1259                      end->getLatitude(),
1260                      0);
1261   first.CourseAndDistance(second, &course, &length);
1262   
1263 }
1264
1265 bool FGTaxiRoute::next(int *val) 
1266
1267   //for (intVecIterator i = nodes.begin(); i != nodes.end(); i++)
1268   //  cerr << "FGTaxiRoute contains : " << *(i) << endl;
1269   //cerr << "Offset from end: " << nodes.end() - currNode << endl;
1270   //if (currNode != nodes.end())
1271   //  cerr << "true" << endl;
1272   //else
1273   //  cerr << "false" << endl;
1274       
1275   if (currNode == nodes.end())
1276     return false;
1277   *val = *(currNode); 
1278   currNode++;
1279   return true;
1280 };
1281 /***************************************************************************
1282  * FGGroundNetwork()
1283  **************************************************************************/
1284
1285 FGGroundNetwork::FGGroundNetwork()
1286 {
1287   hasNetwork = false;
1288 }
1289
1290 void FGGroundNetwork::addSegment(FGTaxiSegment seg)
1291 {
1292   segments.push_back(seg);
1293 }
1294
1295 void FGGroundNetwork::addNode(FGTaxiNode node)
1296 {
1297   nodes.push_back(node);
1298 }
1299
1300 void FGGroundNetwork::addNodes(FGParkingVec *parkings)
1301 {
1302   FGTaxiNode n;
1303   FGParkingVecIterator i = parkings->begin();
1304   while (i != parkings->end())
1305     {
1306       n.setIndex(i->getIndex());
1307       n.setLatitude(i->getLatitude());
1308       n.setLongitude(i->getLongitude());
1309       nodes.push_back(n);
1310
1311       i++;
1312     }
1313 }
1314
1315
1316
1317 void FGGroundNetwork::init()
1318 {
1319   hasNetwork = true;
1320   FGTaxiSegmentVectorIterator i = segments.begin();
1321   while(i != segments.end()) {
1322     //cerr << "initializing node " << i->getIndex() << endl;
1323     i->setStart(&nodes);
1324     i->setEnd  (&nodes);
1325     i->setTrackDistance();
1326     //cerr << "Track distance = " << i->getLength() << endl;
1327     //cerr << "Track ends at"      << i->getEnd()->getIndex() << endl;
1328     i++;
1329   }
1330   //exit(1);
1331 }
1332
1333 int FGGroundNetwork::findNearestNode(double lat, double lon)
1334 {
1335   double minDist = HUGE;
1336   double course, dist;
1337   int index;
1338   SGWayPoint first  (lon,
1339                      lat,
1340                      0);
1341   
1342   for (FGTaxiNodeVectorIterator 
1343          itr = nodes.begin();
1344        itr != nodes.end(); itr++)
1345     {
1346       double course;
1347       SGWayPoint second (itr->getLongitude(),
1348                          itr->getLatitude(),
1349                          0);
1350       first.CourseAndDistance(second, &course, &dist);
1351       if (dist < minDist)
1352         {
1353           minDist = dist;
1354           index = itr->getIndex();
1355           //cerr << "Minimum distance of " << minDist << " for index " << index << endl;
1356         }
1357     }
1358   return index;
1359 }
1360
1361 FGTaxiNode *FGGroundNetwork::findNode(int idx)
1362 {
1363   for (FGTaxiNodeVectorIterator 
1364          itr = nodes.begin();
1365        itr != nodes.end(); itr++)
1366     {
1367       if (itr->getIndex() == idx)
1368         return itr->getAddress();
1369     }
1370   return 0;
1371 }
1372
1373 FGTaxiRoute FGGroundNetwork::findShortestRoute(int start, int end) 
1374 {
1375   foundRoute = false;
1376   totalDistance = 0;
1377   FGTaxiNode *firstNode = findNode(start);
1378   FGTaxiNode *lastNode  = findNode(end);
1379   //prevNode = prevPrevNode = -1;
1380   //prevNode = start;
1381   routes.clear();
1382   traceStack.clear();
1383   trace(firstNode, end, 0, 0);
1384   FGTaxiRoute empty;
1385   
1386   if (!foundRoute)
1387     {
1388       cerr << "Failed to find route from waypoint " << start << " to " << end << endl;
1389       exit(1);
1390     }
1391   sort(routes.begin(), routes.end());
1392   //for (intVecIterator i = route.begin(); i != route.end(); i++)
1393   //  {
1394   //    rte->push_back(*i);
1395   //  }
1396   
1397   if (routes.begin() != routes.end())
1398     return *(routes.begin());
1399   else
1400     return empty;
1401 }
1402
1403
1404 void FGGroundNetwork::trace(FGTaxiNode *currNode, int end, int depth, double distance)
1405 {
1406   traceStack.push_back(currNode->getIndex());
1407   totalDistance += distance;
1408   //cerr << "Starting trace " << depth << " total distance: " << totalDistance<< endl;
1409   //<< currNode->getIndex() << endl;
1410
1411   // If the current route matches the required end point we found a valid route
1412   // So we can add this to the routing table
1413   if (currNode->getIndex() == end)
1414     {
1415       //cerr << "Found route : " <<  totalDistance << "" << " " << *(traceStack.end()-1) << endl;
1416       routes.push_back(FGTaxiRoute(traceStack,totalDistance));
1417       traceStack.pop_back();
1418       if (!(foundRoute))
1419         maxDistance = totalDistance;
1420       else
1421         if (totalDistance < maxDistance)
1422           maxDistance = totalDistance;
1423       foundRoute = true;
1424       totalDistance -= distance;
1425       return;
1426     }
1427  
1428
1429   // search if the currentNode has been encountered before
1430   // if so, we should step back one level, because it is
1431   // rather rediculous to proceed further from here. 
1432   // if the current node has not been encountered before,
1433   // i should point to traceStack.end()-1; and we can continue
1434   // if i is not traceStack.end, the previous node was found, 
1435   // and we should return. 
1436   // This only works at trace levels of 1 or higher though
1437   if (depth > 0) {
1438     intVecIterator i = traceStack.begin();
1439     while ((*i) != currNode->getIndex()) {
1440       //cerr << "Route so far : " << (*i) << endl;
1441       i++;
1442     }
1443     if (i != traceStack.end()-1) {
1444       traceStack.pop_back();
1445       totalDistance -= distance;
1446       return;
1447     }
1448     // If the total distance from start to the current waypoint
1449     // is longer than that of a route we can also stop this trace 
1450     // and go back one level. 
1451     if ((totalDistance > maxDistance) && foundRoute)
1452       {
1453         //cerr << "Stopping rediculously long trace: " << totalDistance << endl;
1454         traceStack.pop_back();
1455         totalDistance -= distance;
1456         return;
1457       }
1458   }
1459   
1460   //cerr << "2" << endl;
1461   if (currNode->getBeginRoute() != currNode->getEndRoute())
1462     {
1463       //cerr << "3" << endl;
1464       for (FGTaxiSegmentPointerVectorIterator 
1465              i = currNode->getBeginRoute();
1466            i != currNode->getEndRoute();
1467            i++)
1468         {
1469           //cerr << (*i)->getLenght() << endl;
1470           trace((*i)->getEnd(), end, depth+1, (*i)->getLength());
1471         //  {
1472         //      // cerr << currNode -> getIndex() << " ";
1473         //      route.push_back(currNode->getIndex());
1474         //      return true;
1475         //    }
1476         }
1477     }
1478   else
1479     {
1480       cerr << "4" << endl;
1481     }
1482   traceStack.pop_back();
1483   totalDistance -= distance;
1484   return;
1485 }
1486
1487
1488
1489 /******************************************************************************
1490  * FGAirportList
1491  *****************************************************************************/
1492
1493 // Populates a list of subdirectories of $FG_ROOT/Airports/AI so that
1494 // the add() method doesn't have to try opening 2 XML files in each of
1495 // thousands of non-existent directories.  FIXME: should probably add
1496 // code to free this list after parsing of apt.dat is finished;
1497 // non-issue at the moment, however, as there are no AI subdirectories
1498 // in the base package.
1499 FGAirportList::FGAirportList()
1500 {
1501     ulDir* d;
1502     ulDirEnt* dent;
1503     SGPath aid( globals->get_fg_root() );
1504     aid.append( "/Airports/AI" );
1505     if((d = ulOpenDir(aid.c_str())) == NULL)
1506         return;
1507     while((dent = ulReadDir(d)) != NULL) {
1508         cerr << "Dent: " << dent->d_name; // DEBUG
1509         ai_dirs.insert(dent->d_name);
1510     }
1511     ulCloseDir(d);
1512 }
1513
1514
1515 FGAirportList::~FGAirportList( void ) {
1516     for(unsigned int i = 0; i < airports_array.size(); ++i) {
1517         delete airports_array[i];
1518     }
1519 }
1520
1521
1522 // add an entry to the list
1523 void FGAirportList::add( const string id, const double longitude,
1524                          const double latitude, const double elevation,
1525                          const string name, const bool has_metar )
1526 {
1527     FGRunwayPreference rwyPrefs;
1528     FGAirport* a = new FGAirport(id, longitude, latitude, elevation, name, has_metar);
1529     SGPath parkpath( globals->get_fg_root() );
1530     parkpath.append( "/Airports/AI/" );
1531     parkpath.append(id);
1532     parkpath.append("parking.xml"); 
1533     
1534     SGPath rwyPrefPath( globals->get_fg_root() );
1535     rwyPrefPath.append( "/Airports/AI/" );
1536     rwyPrefPath.append(id);
1537     rwyPrefPath.append("rwyuse.xml");
1538     if (ai_dirs.find(id.c_str()) != ai_dirs.end()
1539         && parkpath.exists()) 
1540     {
1541         try {
1542             readXML(parkpath.str(),*a);
1543             a->init();
1544         } 
1545         catch  (const sg_exception &e) {
1546             //cerr << "unable to read " << parkpath.str() << endl;
1547         }
1548     }
1549     if (ai_dirs.find(id.c_str()) != ai_dirs.end()
1550         && rwyPrefPath.exists()) 
1551     {
1552         try {
1553             readXML(rwyPrefPath.str(), rwyPrefs);
1554             a->setRwyUse(rwyPrefs);
1555         }
1556         catch  (const sg_exception &e) {
1557             //cerr << "unable to read " << rwyPrefPath.str() << endl;
1558             //exit(1);
1559         }
1560     }
1561     
1562     airports_by_id[a->getId()] = a;
1563     // try and read in an auxilary file
1564     
1565     airports_array.push_back( a );
1566     SG_LOG( SG_GENERAL, SG_BULK, "Adding " << id << " pos = " << longitude
1567             << ", " << latitude << " elev = " << elevation );
1568 }
1569
1570
1571 // search for the specified id
1572 FGAirport* FGAirportList::search( const string& id) {
1573     airport_map_iterator itr = airports_by_id.find(id); 
1574     return(itr == airports_by_id.end() ? NULL : itr->second);
1575 }
1576
1577
1578 // search for first subsequent alphabetically to supplied id
1579 const FGAirport* FGAirportList::findFirstById( const string& id, bool exact ) {
1580     airport_map_iterator itr;
1581     if(exact) {
1582         itr = airports_by_id.find(id);
1583     } else {
1584         itr = airports_by_id.lower_bound(id);
1585     }
1586     if(itr == airports_by_id.end()) {
1587         return(NULL);
1588     } else {
1589         return(itr->second);
1590     }
1591 }
1592
1593
1594 // search for the airport nearest the specified position
1595 FGAirport* FGAirportList::search( double lon_deg, double lat_deg,
1596                                  bool with_metar ) {
1597     int closest = -1;
1598     double min_dist = 360.0;
1599     unsigned int i;
1600     for ( i = 0; i < airports_array.size(); ++i ) {
1601         // crude manhatten distance based on lat/lon difference
1602         double d = fabs(lon_deg - airports_array[i]->getLongitude())
1603             + fabs(lat_deg - airports_array[i]->getLatitude());
1604         if ( d < min_dist ) {
1605             if ( !with_metar || (with_metar&&airports_array[i]->getMetar()) ) {
1606                 closest = i;
1607                 min_dist = d;
1608             }
1609         }
1610     }
1611
1612     return ( closest > -1 ? airports_array[closest] : NULL );
1613 }
1614
1615
1616 int
1617 FGAirportList::size () const
1618 {
1619     return airports_array.size();
1620 }
1621
1622 const FGAirport *FGAirportList::getAirport( unsigned int index ) const
1623 {
1624     if(index < airports_array.size()) {
1625         return(airports_array[index]);
1626     } else {
1627         return(NULL);
1628     }
1629 }
1630
1631
1632 /**
1633  * Mark the specified airport record as not having metar
1634  */
1635 void FGAirportList::no_metar( const string &id ) {
1636     if(airports_by_id.find(id) != airports_by_id.end()) { 
1637         airports_by_id[id]->setMetar(false);
1638     }
1639 }
1640
1641
1642 /**
1643  * Mark the specified airport record as (yes) having metar
1644  */
1645 void FGAirportList::has_metar( const string &id ) {
1646     if(airports_by_id.find(id) != airports_by_id.end()) { 
1647         airports_by_id[id]->setMetar(true);
1648     }
1649 }