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