]> git.mxchange.org Git - flightgear.git/blob - src/Airports/simple.cxx
e862af61ea17ae7b1ed32abcb5d6baad0e4ee030
[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  * ScheduleTime
61  ***************e*************************************************************/
62 void ScheduleTime::clear()
63
64   start.clear();
65   end.clear();
66   scheduleNames.clear();
67 }
68
69
70 ScheduleTime::ScheduleTime(const ScheduleTime &other) 
71 {
72   //timeVec   start;
73   timeVecConstIterator i;
74   for (i = other.start.begin(); i != other.start.end(); i++)
75     start.push_back(*i);
76    for (i = other.end.begin(); i != other.end.end(); i++)
77     end.push_back(*i);
78    stringVecConstIterator k;
79    for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
80      scheduleNames.push_back(*k);
81   
82   //timeVec   end;
83   //stringVec scheduleNames;
84   tailWind = other.tailWind;
85   crssWind = other.tailWind;
86 }
87
88
89 ScheduleTime & ScheduleTime::operator= (const ScheduleTime &other) 
90 {
91   //timeVec   start;
92   clear();
93   timeVecConstIterator i;
94   for (i = other.start.begin(); i != other.start.end(); i++)
95     start.push_back(*i);
96    for (i = other.end.begin(); i != other.end.end(); i++)
97     end.push_back(*i);
98    stringVecConstIterator k;
99    for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
100      scheduleNames.push_back(*k);
101   
102   //timeVec   end;
103   //stringVec scheduleNames;
104   tailWind = other.tailWind;
105   crssWind = other.tailWind;
106   return *this;
107 }
108 string ScheduleTime::getName(time_t dayStart)
109 {
110   if ((start.size() != end.size()) || (start.size() != scheduleNames.size()))
111     {
112       cerr << "Unable to parse schedule times" << endl;
113       exit(1);
114     }
115   else
116     {
117       int nrItems = start.size();
118       //cerr << "Nr of items to process: " << nrItems << endl;
119       if (nrItems > 0)
120         {
121           for (unsigned int i = 0; i < start.size(); i++)
122             {
123               //cerr << i << endl;
124               if ((dayStart >= start[i]) && (dayStart <= end[i]))
125                 return scheduleNames[i];
126             }
127         }
128       //couldn't find one so return 0;
129       //cerr << "Returning 0 " << endl;
130       return string(0);
131     }
132 }                             
133 /******************************************************************************
134  * RunwayList
135  *****************************************************************************/
136
137 RunwayList::RunwayList(const RunwayList &other)
138 {
139   type = other.type;
140   stringVecConstIterator i;
141   for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
142     preferredRunways.push_back(*i);
143 }
144 RunwayList& RunwayList::operator= (const RunwayList &other)
145 {
146   type = other.type;
147   preferredRunways.clear();
148   stringVecConstIterator i;
149   for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
150     preferredRunways.push_back(*i);
151   return *this;
152 }
153 void RunwayList::set(string tp, string lst)
154 {
155   //weekday          = atoi(timeCopy.substr(0,1).c_str());
156   //    timeOffsetInDays = weekday - currTimeDate->getGmt()->tm_wday;
157   //    timeCopy = timeCopy.substr(2,timeCopy.length());
158   type = tp;
159   string rwys = lst;
160   string rwy;
161   while (rwys.find(",") != string::npos)
162     {
163       rwy = rwys.substr(0, rwys.find(",",0));
164       //cerr << "adding runway [" << rwy << "] to the list " << endl;
165       preferredRunways.push_back(rwy);
166       rwys.erase(0, rwys.find(",",0)+1); // erase until after the first whitspace
167       while (rwys[0] == ' ')
168         rwys.erase(0, 1); // Erase any leading whitespaces.
169       //cerr << "Remaining runway list " << rwys;
170     } 
171   preferredRunways.push_back(rwys);
172   //exit(1);
173 }
174
175 void RunwayList::clear() 
176 {
177   type = "";
178   preferredRunways.clear();
179 }
180 /****************************************************************************
181  *
182  ***************************************************************************/
183
184 RunwayGroup::RunwayGroup(const RunwayGroup &other)
185 {
186   name = other.name; 
187   RunwayListVecConstIterator i;
188   for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
189     rwyList.push_back(*i);
190   choice[0] = other.choice[0];
191   choice[1] = other.choice[1];
192   nrActive = other.nrActive;
193 }
194 RunwayGroup& RunwayGroup:: operator= (const RunwayGroup &other)
195
196   rwyList.clear();
197   name = other.name; 
198   RunwayListVecConstIterator i;
199   for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
200     rwyList.push_back(*i); 
201   choice[0] = other.choice[0];
202   choice[1] = other.choice[1];
203   nrActive = other.nrActive;
204   return *this;
205 }
206
207 void RunwayGroup::setActive(string aptId, 
208                             double windSpeed, 
209                             double windHeading, 
210                             double maxTail, 
211                             double maxCross)
212 {
213
214   FGRunway rwy;
215   int activeRwys = rwyList.size(); // get the number of runways active
216   int nrOfPreferences;
217   // bool found = true;
218   // double heading;
219   double hdgDiff;
220   double crossWind;
221   double tailWind;
222   string name;
223
224   if (activeRwys > 0)
225     {
226       nrOfPreferences = rwyList[0].getRwyList()->size();
227       for (int i = 0; i < nrOfPreferences; i++)
228         {
229           bool validSelection = true;
230           for (int j = 0; j < activeRwys; j++)
231             {
232               //cerr << "I J " << i << " " << j << endl;
233               name = rwyList[j].getRwyList(i);
234               //cerr << "Name of Runway: " << name << endl;
235               if (globals->get_runways()->search( aptId, 
236                                                   name, 
237                                                   &rwy))
238                 {
239                   //cerr << "Succes" << endl;
240                   hdgDiff = fabs(windHeading - rwy._heading);
241                   //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
242                   //cerr << "Wind Speed  : " << windSpeed << endl;
243                   if (hdgDiff > 180)
244                     hdgDiff = 360 - hdgDiff;
245                   //cerr << "Heading diff: " << hdgDiff << endl;
246                   hdgDiff *= ((2*M_PI)/360.0); // convert to radians
247                   crossWind = windSpeed * sin(hdgDiff);
248                   tailWind  = -windSpeed * cos(hdgDiff);
249                   //cerr << "Tailwind : " << tailWind << endl;
250                   //cerr << "Crosswnd : " << crossWind << endl;
251                   if ((tailWind > maxTail) || (crossWind > maxCross))
252                     validSelection = false;
253                 }else {
254                   cerr << "Failed to find runway " << name << " at " << aptId << endl;
255                   exit(1);
256                 }
257
258             }
259           if (validSelection)
260             {
261               //cerr << "Valid runay selection : " << i << endl;
262               nrActive = activeRwys;
263               active = i;
264               return;
265             }
266         }
267       // If this didn't work, due to heavy winds, try again
268       // but select only one landing and one takeoff runway. 
269       choice[0] = 0;
270       choice[1] = 0;
271       for (int i = activeRwys-1;  i; i--)
272         {
273           if (rwyList[i].getType() == string("landing"))
274             choice[0] = i;
275           if (rwyList[i].getType() == string("takeoff"))
276             choice[1] = i;
277         }
278       //cerr << "Choosing " << choice[0] << " for landing and " << choice[1] << "for takeoff" << endl;
279       nrOfPreferences = rwyList[0].getRwyList()->size();
280       for (int i = 0; i < nrOfPreferences; i++)
281         {
282           bool validSelection = true;
283           for (int j = 0; j < 2; j++)
284             {
285               //cerr << "I J " << i << " " << j << endl;
286               name = rwyList[choice[j]].getRwyList(i);
287               //cerr << "Name of Runway: " << name << endl;
288               if (globals->get_runways()->search( aptId, 
289                                                   name, 
290                                                   &rwy))
291                 {
292                   //cerr << "Succes" << endl;
293                   hdgDiff = fabs(windHeading - rwy._heading);
294                   //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
295                   //cerr << "Wind Speed  : " << windSpeed << endl;
296                   if (hdgDiff > 180)
297                     hdgDiff = 360 - hdgDiff;
298                   //cerr << "Heading diff: " << hdgDiff << endl;
299                   hdgDiff *= ((2*M_PI)/360.0); // convert to radians
300                   crossWind = windSpeed * sin(hdgDiff);
301                   tailWind  = -windSpeed * cos(hdgDiff);
302                   //cerr << "Tailwind : " << tailWind << endl;
303                   //cerr << "Crosswnd : " << crossWind << endl;
304                   if ((tailWind > maxTail) || (crossWind > maxCross))
305                     validSelection = false;
306                 }else {
307                   cerr << "Failed to find runway " << name << " at " << aptId << endl;
308                   exit(1);
309                 }
310
311             }
312           if (validSelection)
313             {
314               //cerr << "Valid runay selection : " << i << endl;
315               active = i;
316               nrActive = 2;
317               return;
318             }
319         }
320     }
321   active = -1;
322   //RunwayListVectorIterator i; // = rwlist.begin();
323   //stringVecIterator j;
324   //for (i = rwyList.begin(); i != rwyList.end(); i++)
325   //  {
326   //    cerr << i->getType();
327   //    for (j = i->getRwyList()->begin(); j != i->getRwyList()->end(); j++)
328   //    {                                 
329   //      cerr << (*j);
330   //    }
331   //    cerr << endl;
332   //  }
333   //for (int
334
335 }
336
337 void RunwayGroup::getActive(int i, string *name, string *type)
338 {
339   if (i == -1)
340     {
341       return;
342     }
343   if (nrActive == (int)rwyList.size())
344     {
345       *name = rwyList[i].getRwyList(active);
346       *type = rwyList[i].getType();
347     }
348   else
349     { 
350       *name = rwyList[choice[i]].getRwyList(active);
351       *type = rwyList[choice[i]].getType();
352     }
353 }
354 /*****************************************************************************
355  * FGRunway preference
356  ****************************************************************************/
357 FGRunwayPreference::FGRunwayPreference()
358 {
359   //cerr << "Running default Constructor" << endl;
360   initialized = false;
361 }
362
363 FGRunwayPreference::FGRunwayPreference(const FGRunwayPreference &other)
364 {
365   initialized = other.initialized;
366   value = other.value;
367   scheduleName = other.scheduleName;
368
369   comTimes = other.comTimes; // Commercial Traffic;
370   genTimes = other.genTimes; // General Aviation;
371   milTimes = other.milTimes; // Military Traffic;
372   currTimes= other.currTimes; // Needed for parsing;
373
374   rwyList = other.rwyList;
375   rwyGroup = other.rwyGroup;
376   PreferenceListConstIterator i;
377   for (i = other.preferences.begin(); i != other.preferences.end(); i++)
378     preferences.push_back(*i);
379 }
380   
381 FGRunwayPreference & FGRunwayPreference::operator= (const FGRunwayPreference &other)
382 {
383   initialized = other.initialized;
384   value = other.value;
385   scheduleName = other.scheduleName;
386   
387   comTimes = other.comTimes; // Commercial Traffic;
388   genTimes = other.genTimes; // General Aviation;
389   milTimes = other.milTimes; // Military Traffic;
390   currTimes= other.currTimes; // Needed for parsing;
391   
392   rwyList = other.rwyList;
393   rwyGroup = other.rwyGroup;
394   PreferenceListConstIterator i;
395   preferences.clear();
396   for (i = other.preferences.begin(); i != other.preferences.end(); i++)
397     preferences.push_back(*i);
398   return *this;
399 }
400
401 ScheduleTime *FGRunwayPreference::getSchedule(const char *trafficType)
402 {
403   if (!(strcmp(trafficType, "com"))) {
404     return &comTimes;
405   }
406   if (!(strcmp(trafficType, "gen"))) {
407     return &genTimes;
408   }
409   if (!(strcmp(trafficType, "mil"))) {
410     return &milTimes;
411   }
412   return 0;
413 }
414
415 RunwayGroup *FGRunwayPreference::getGroup(const string groupName)
416 {
417   PreferenceListIterator i = preferences.begin();
418   if (preferences.begin() == preferences.end())
419     return 0;
420   while (!(i == preferences.end() || i->getName() == groupName))
421     i++;
422   if (i != preferences.end())
423     return &(*i);
424   else
425     return 0;
426 }
427
428 void  FGRunwayPreference::startXML () {
429   //  cout << "Start XML" << endl;
430 }
431
432 void  FGRunwayPreference::endXML () {
433   cout << "End XML" << endl;
434 }
435
436 void  FGRunwayPreference::startElement (const char * name, const XMLAttributes &atts) {
437   //cout << "StartElement " << name << endl;
438   value = string("");
439   if (!(strcmp(name, "wind"))) {
440     //cerr << "Will be processing Wind" << endl;
441     for (int i = 0; i < atts.size(); i++)
442       {
443         //cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
444         //attname = atts.getName(i);
445         if (atts.getName(i) == string("tail")) {
446           //cerr << "Tail Wind = " << atts.getValue(i) << endl;
447           currTimes.setTailWind(atof(atts.getValue(i)));
448         }       
449         if (atts.getName(i) == string("cross")) {
450           //cerr << "Cross Wind = " << atts.getValue(i) << endl;
451           currTimes.setCrossWind(atof(atts.getValue(i)));
452         }
453      }
454   }
455     if (!(strcmp(name, "time"))) {
456       //cerr << "Will be processing time" << endl;      
457     for (int i = 0; i < atts.size(); i++)
458       {
459         if (atts.getName(i) == string("start")) {
460           //cerr << "Start Time = " << atts.getValue(i) << endl;
461           currTimes.addStartTime(processTime(atts.getValue(i)));
462         }
463         if (atts.getName(i) == string("end")) {
464           //cerr << "End time = " << atts.getValue(i) << endl;
465           currTimes.addEndTime(processTime(atts.getValue(i)));
466         }
467         if (atts.getName(i) == string("schedule")) {
468           //cerr << "Schedule Name  = " << atts.getValue(i) << endl;
469           currTimes.addScheduleName(atts.getValue(i));
470         }       
471     }
472   }
473   if (!(strcmp(name, "takeoff"))) {
474     rwyList.clear();
475   }
476   if  (!(strcmp(name, "landing")))
477     {
478       rwyList.clear();
479     }
480   if (!(strcmp(name, "schedule"))) {
481     for (int i = 0; i < atts.size(); i++)
482       {
483         //cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
484         //attname = atts.getName(i);
485         if (atts.getName(i) == string("name")) {
486           //cerr << "Schedule name = " << atts.getValue(i) << endl;
487           scheduleName = atts.getValue(i);
488         }
489       }
490   }
491 }
492
493 //based on a string containing hour and minute, return nr seconds since day start.
494 time_t FGRunwayPreference::processTime(string tme)
495 {
496   string hour   = tme.substr(0, tme.find(":",0));
497   string minute = tme.substr(tme.find(":",0)+1, tme.length());
498
499   //cerr << "hour = " << hour << " Minute = " << minute << endl;
500   return (atoi(hour.c_str()) * 3600 + atoi(minute.c_str()) * 60);
501 }
502
503 void  FGRunwayPreference::endElement (const char * name) {
504   //cout << "End element " << name << endl;
505   if (!(strcmp(name, "rwyuse"))) {
506     initialized = true;
507   }
508   if (!(strcmp(name, "com"))) { // Commercial Traffic
509     //cerr << "Setting time table for commerical traffic" << endl;
510     comTimes = currTimes;
511     currTimes.clear();
512   }
513   if (!(strcmp(name, "gen"))) { // General Aviation
514     //cerr << "Setting time table for general aviation" << endl;
515     genTimes = currTimes;
516     currTimes.clear();
517   }  
518   if (!(strcmp(name, "mil"))) { // Military Traffic
519     //cerr << "Setting time table for military traffic" << endl;
520     genTimes = currTimes;
521     currTimes.clear();
522   }
523
524   if (!(strcmp(name, "takeoff"))) {
525     //cerr << "Adding takeoff: " << value << endl;
526     rwyList.set(name, value);
527     rwyGroup.add(rwyList);
528   }
529   if (!(strcmp(name, "landing"))) {
530     //cerr << "Adding landing: " << value << endl;
531     rwyList.set(name, value);
532     rwyGroup.add(rwyList);
533   }
534   if (!(strcmp(name, "schedule"))) {
535     //cerr << "Adding schedule" << scheduleName << endl;
536     rwyGroup.setName(scheduleName);
537     //rwyGroup.addRunways(rwyList);
538     preferences.push_back(rwyGroup);
539     rwyGroup.clear();
540     //exit(1);
541   }
542 }
543
544 void  FGRunwayPreference::data (const char * s, int len) {
545   string token = string(s,len);
546   //cout << "Character data " << string(s,len) << endl;
547   //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
548   //  value += token;
549   //else
550   //  value = string("");
551   value += token;
552 }
553
554 void  FGRunwayPreference::pi (const char * target, const char * data) {
555   //cout << "Processing instruction " << target << ' ' << data << endl;
556 }
557
558 void  FGRunwayPreference::warning (const char * message, int line, int column) {
559   cout << "Warning: " << message << " (" << line << ',' << column << ')'   
560        << endl;
561 }
562
563 void  FGRunwayPreference::error (const char * message, int line, int column) {
564   cout << "Error: " << message << " (" << line << ',' << column << ')'
565        << endl;
566 }
567
568 /*****************************************************************************
569  * Helper function for parsing position string
570  ****************************************************************************/
571 double processPosition(string pos)
572 {
573   string prefix;
574   string subs;
575   string degree;
576   string decimal;
577   int sign = 1;
578   double value;
579   subs = pos;
580   prefix= subs.substr(0,1);
581   if (prefix == string("S") || (prefix == string("W")))
582     sign = -1;
583   subs    = subs.substr(1, subs.length());
584   degree  = subs.substr(0, subs.find(" ",0));
585   decimal = subs.substr(subs.find(" ",0), subs.length());
586   
587               
588   //cerr << sign << " "<< degree << " " << decimal << endl;
589   value = sign * (atof(degree.c_str()) + atof(decimal.c_str())/60.0);
590   //cerr << value <<endl;
591   //exit(1);
592   return value;
593 }
594
595
596 /*********************************************************************************
597  * FGParking
598  ********************************************************************************/
599 FGParking::FGParking(double lat,
600                      double lon,
601                      double hdg,
602                      double rad,
603                      int idx,
604                      string name,
605                      string tpe,
606                      string codes)
607 {
608   latitude     = lat;
609   longitude    = lon;
610   heading      = hdg;
611   parkingName  = name;
612   index        = idx;
613   type         = tpe;
614   airlineCodes = codes;
615 }
616
617
618 /***************************************************************************
619  * FGAirport
620  ***************************************************************************/
621 FGAirport::FGAirport() : _longitude(0), _latitude(0), _elevation(0)
622 {
623   lastUpdate = 0;
624   for (int i = 0; i < 10; i++)
625     {
626       avWindHeading [i] = 0;
627       avWindSpeed   [i] = 0;
628     }
629 }
630
631 FGAirport::FGAirport(const FGAirport& other)
632 {
633   _id = other._id;
634   _longitude = other._longitude;
635   _latitude  = other._latitude;
636   _elevation = other._elevation;
637   _name      = other._name;
638   _has_metar = other._has_metar;
639   for (FGParkingVecConstIterator ip= other.parkings.begin(); ip != other.parkings.end(); ip++)
640     parkings.push_back(*(ip));
641   rwyPrefs = other.rwyPrefs;
642   lastUpdate = other.lastUpdate;
643   
644   stringVecConstIterator il;
645   for (il = other.landing.begin(); il != other.landing.end(); il++)
646     landing.push_back(*il);
647   for (il = other.takeoff.begin(); il != other.takeoff.end(); il++)
648     takeoff.push_back(*il);
649   lastUpdate = other.lastUpdate;
650   for (int i = 0; i < 10; i++)
651     {
652       avWindHeading [i] = other.avWindHeading[i];
653       avWindSpeed   [i] = other.avWindSpeed  [i];
654     }
655 }
656
657 FGAirport::FGAirport(string id, double lon, double lat, double elev, string name, bool has_metar)
658 {
659   _id = id;
660   _longitude = lon;
661   _latitude  = lat;
662   _elevation = elev;
663   _name      = name;
664   _has_metar = has_metar; 
665   lastUpdate = 0;
666   for (int i = 0; i < 10; i++)
667     {
668       avWindHeading [i] = 0;
669       avWindSpeed   [i] = 0;
670     }
671  
672 }
673
674 // Initialization required after XMLRead
675 void FGAirport::init() 
676 {
677   // This may seem a bit weird to first randomly shuffle the parkings
678   // and then sort them again. However, parkings are sorted here by ascending 
679   // radius. Since many parkings have similar radii, with each radius class they will
680   // still be allocated relatively systematically. Randomizing prior to sorting will
681   // prevent any initial orderings to be destroyed, leading (hopefully) to a more 
682   // naturalistic gate assignment. 
683   random_shuffle(parkings.begin(), parkings.end());
684   sort(parkings.begin(), parkings.end());
685   // add the gate positions to the ground network. 
686   groundNetwork.addNodes(&parkings);
687   groundNetwork.init();
688 }
689
690 bool FGAirport::getAvailableParking(double *lat, double *lon, double *heading, int *gateId, double rad, string flType, string acType, string airline)
691 {
692   bool found = false;
693   bool available = false;
694   //string gateType;
695
696   FGParkingVecIterator i;
697 //   if (flType == "cargo")
698 //     {
699 //       gateType = "RAMP_CARGO";
700 //     }
701 //   else if (flType == "ga")
702 //     {
703 //       gateType = "RAMP_GA";
704 //     }
705 //   else gateType = "GATE";
706   
707   if (parkings.begin() == parkings.end())
708     {
709       //cerr << "Could not find parking spot at " << _id << endl;
710       *lat = _latitude;
711       *lon = _longitude;
712       *heading = 0;
713       found = true;
714     }
715   else
716     {
717       // First try finding a parking with a designated airline code
718       for (i = parkings.begin(); !(i == parkings.end() || found); i++)
719         {
720           //cerr << "Gate Id: " << i->getIndex()
721           //     << " Type  : " << i->getType()
722           //     << " Codes : " << i->getCodes()
723           //     << " Radius: " << i->getRadius()
724           //     << " Name  : " << i->getName()
725           //     << " Available: " << i->isAvailable() << endl;
726           available = true;
727           // Taken by another aircraft
728           if (!(i->isAvailable()))
729             {
730               available = false;
731               continue;
732             }
733           // No airline codes, so skip
734           if (i->getCodes().empty())
735             {
736               available = false;
737               continue;
738             }
739           else // Airline code doesn't match
740             if (i->getCodes().find(airline, 0) == string::npos)
741               {
742                 available = false;
743                 continue;
744               }
745           // Type doesn't match
746           if (i->getType() != flType)
747             {
748               available = false;
749               continue;
750             }
751           // too small
752           if (i->getRadius() < rad)
753             {
754               available = false;
755               continue;
756             }
757           
758           if (available)
759             {
760               *lat     = i->getLatitude ();
761               *lon     = i->getLongitude();
762               *heading = i->getHeading  ();
763               *gateId  = i->getIndex    ();
764               i->setAvailable(false);
765               found = true;
766             }
767         }
768       // then try again for those without codes. 
769       for (i = parkings.begin(); !(i == parkings.end() || found); i++)
770         {
771           available = true;
772           if (!(i->isAvailable()))
773             {
774               available = false;
775               continue;
776             }
777           if (!(i->getCodes().empty()))
778             {
779               if ((i->getCodes().find(airline,0) == string::npos))
780           {
781             available = false;
782             continue;
783           }
784             }
785           if (i->getType() != flType)
786             {
787               available = false;
788               continue;
789             }
790               
791           if (i->getRadius() < rad)
792             {
793               available = false;
794               continue;
795             }
796           
797           if (available)
798             {
799               *lat     = i->getLatitude ();
800               *lon     = i->getLongitude();
801               *heading = i->getHeading  ();
802               *gateId  = i->getIndex    ();
803               i->setAvailable(false);
804               found = true;
805             }
806         } 
807       // And finally once more if that didn't work. Now ignore the airline codes, as a last resort
808       for (i = parkings.begin(); !(i == parkings.end() || found); i++)
809         {
810           available = true;
811           if (!(i->isAvailable()))
812             {
813               available = false;
814               continue;
815             }
816           if (i->getType() != flType)
817             {
818               available = false;
819               continue;
820             }
821           
822           if (i->getRadius() < rad)
823             {
824               available = false;
825               continue;
826             }
827           
828           if (available)
829             {
830               *lat     = i->getLatitude ();
831               *lon     = i->getLongitude();
832               *heading = i->getHeading  ();
833               *gateId  = i->getIndex    ();
834               i->setAvailable(false);
835               found = true;
836             }
837         }
838     }
839   if (!found)
840     {
841       //cerr << "Traffic overflow at" << _id 
842       //           << ". flType = " << flType 
843       //           << ". airline = " << airline 
844       //           << " Radius = " <<rad
845       //           << endl;
846       *lat = _latitude;
847       *lon = _longitude;
848       *heading = 0;
849       *gateId  = -1;
850       //exit(1);
851     }
852   return found;
853 }
854
855 void FGAirport::getParking (int id, double *lat, double* lon, double *heading)
856 {
857   if (id < 0)
858     {
859       *lat = _latitude;
860       *lon = _longitude;
861       *heading = 0;
862     }
863   else
864     {
865       FGParkingVecIterator i = parkings.begin();
866       for (i = parkings.begin(); i != parkings.end(); i++)
867         {
868           if (id == i->getIndex())
869             {
870               *lat     = i->getLatitude();
871               *lon     = i->getLongitude();
872               *heading = i->getLongitude();
873             }
874         }
875     }
876
877
878 FGParking *FGAirport::getParking(int i) 
879
880   if (i < (int)parkings.size()) 
881     return &(parkings[i]); 
882   else 
883     return 0;
884 }
885 string FGAirport::getParkingName(int i) 
886
887   if (i < (int)parkings.size() && i >= 0) 
888     return (parkings[i].getName()); 
889   else 
890     return string("overflow");
891 }
892 void FGAirport::releaseParking(int id)
893 {
894   if (id >= 0)
895     {
896       
897       FGParkingVecIterator i = parkings.begin();
898       for (i = parkings.begin(); i != parkings.end(); i++)
899         {
900           if (id == i->getIndex())
901             {
902               i -> setAvailable(true);
903             }
904         }
905     }
906 }
907   
908 void  FGAirport::startXML () {
909   //cout << "Start XML" << endl;
910 }
911
912 void  FGAirport::endXML () {
913   //cout << "End XML" << endl;
914 }
915
916 void  FGAirport::startElement (const char * name, const XMLAttributes &atts) {
917   // const char *attval;
918   FGParking park;
919   FGTaxiNode taxiNode;
920   FGTaxiSegment taxiSegment;
921   int index = 0;
922   taxiSegment.setIndex(index);
923   //cout << "Start element " << name << endl;
924   string attname;
925   string value;
926   string gateName;
927   string gateNumber;
928   string lat;
929   string lon;
930   if (name == string("Parking"))
931     {
932       for (int i = 0; i < atts.size(); i++)
933         {
934           //cout << "  " << atts.getName(i) << '=' << atts.getValue(i) << endl; 
935           attname = atts.getName(i);
936           if (attname == string("index"))
937             park.setIndex(atoi(atts.getValue(i)));
938           else if (attname == string("type"))
939             park.setType(atts.getValue(i));
940          else if (attname == string("name"))
941            gateName = atts.getValue(i);
942           else if (attname == string("number"))
943             gateNumber = atts.getValue(i);
944           else if (attname == string("lat"))
945            park.setLatitude(atts.getValue(i));
946           else if (attname == string("lon"))
947             park.setLongitude(atts.getValue(i)); 
948           else if (attname == string("heading"))
949             park.setHeading(atof(atts.getValue(i)));
950           else if (attname == string("radius")) {
951             string radius = atts.getValue(i);
952             if (radius.find("M") != string::npos)
953               radius = radius.substr(0, radius.find("M",0));
954             //cerr << "Radius " << radius <<endl;
955             park.setRadius(atof(radius.c_str()));
956           }
957            else if (attname == string("airlineCodes"))
958              park.setCodes(atts.getValue(i));
959         }
960       park.setName((gateName+gateNumber));
961       parkings.push_back(park);
962     }
963   if (name == string("node")) 
964     {
965       for (int i = 0; i < atts.size() ; i++)
966         {
967           attname = atts.getName(i);
968           if (attname == string("index"))
969             taxiNode.setIndex(atoi(atts.getValue(i)));
970           if (attname == string("lat"))
971             taxiNode.setLatitude(atts.getValue(i));
972           if (attname == string("lon"))
973             taxiNode.setLongitude(atts.getValue(i));
974         }
975       groundNetwork.addNode(taxiNode);
976     }
977   if (name == string("arc")) 
978     {
979       taxiSegment.setIndex(++index);
980       for (int i = 0; i < atts.size() ; i++)
981         {
982           attname = atts.getName(i);
983           if (attname == string("begin"))
984             taxiSegment.setStartNodeRef(atoi(atts.getValue(i)));
985           if (attname == string("end"))
986             taxiSegment.setEndNodeRef(atoi(atts.getValue(i)));
987         }
988       groundNetwork.addSegment(taxiSegment);
989     }
990   // sort by radius, in asending order, so that smaller gates are first in the list
991 }
992
993 void  FGAirport::endElement (const char * name) {
994   //cout << "End element " << name << endl;
995
996 }
997
998 void  FGAirport::data (const char * s, int len) {
999   string token = string(s,len);
1000   //cout << "Character data " << string(s,len) << endl;
1001   //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
1002     //value += token;
1003   //else
1004     //value = string("");
1005 }
1006
1007 void  FGAirport::pi (const char * target, const char * data) {
1008   //cout << "Processing instruction " << target << ' ' << data << endl;
1009 }
1010
1011 void  FGAirport::warning (const char * message, int line, int column) {
1012   cout << "Warning: " << message << " (" << line << ',' << column << ')'   
1013        << endl;
1014 }
1015
1016 void  FGAirport::error (const char * message, int line, int column) {
1017   cout << "Error: " << message << " (" << line << ',' << column << ')'
1018        << endl;
1019 }
1020
1021 void FGAirport::setRwyUse(FGRunwayPreference& ref)
1022 {
1023   rwyPrefs = ref;
1024   //cerr << "Exiting due to not implemented yet" << endl;
1025   //exit(1);
1026 }
1027 void FGAirport::getActiveRunway(string trafficType, int action, string *runway)
1028 {
1029   double windSpeed;
1030   double windHeading;
1031   double maxTail;
1032   double maxCross;
1033   string name;
1034   string type;
1035
1036   if (!(rwyPrefs.available()))
1037     {
1038       chooseRunwayFallback(runway);
1039       return; // generic fall back goes here
1040     }
1041   else
1042     {
1043       RunwayGroup *currRunwayGroup = 0;
1044       int nrActiveRunways = 0;
1045       time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
1046       if (((dayStart - lastUpdate) > 600) || trafficType != prevTrafficType)
1047         {
1048           landing.clear();
1049           takeoff.clear();
1050           //lastUpdate = dayStart;
1051           prevTrafficType = trafficType;
1052
1053           FGEnvironment 
1054             stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1055             ->getEnvironment(getLatitude(), 
1056                              getLongitude(), 
1057                              getElevation());
1058           
1059           windSpeed = stationweather.get_wind_speed_kt();
1060           windHeading = stationweather.get_wind_from_heading_deg();
1061           double averageWindSpeed   = 0;
1062           double averageWindHeading = 0;
1063           double cosHeading         = 0;
1064           double sinHeading         = 0;
1065           // Initialize at the beginning of the next day or startup
1066           if ((lastUpdate == 0) || (dayStart < lastUpdate))
1067             {
1068               for (int i = 0; i < 10; i++)
1069                 {
1070                   avWindHeading [i] = windHeading;
1071                   avWindSpeed   [i] = windSpeed;
1072                 }
1073             }
1074           else
1075             {
1076               if (windSpeed != avWindSpeed[9]) // update if new metar data 
1077                 {
1078                   // shift the running average
1079                   for (int i = 0; i < 9 ; i++)
1080                     {
1081                       avWindHeading[i] = avWindHeading[i+1];
1082                       avWindSpeed  [i] = avWindSpeed  [i+1];
1083                     }
1084                 } 
1085               avWindHeading[9] = windHeading;
1086               avWindSpeed  [9] = windSpeed;
1087             }
1088           
1089           for (int i = 0; i < 10; i++)
1090             {
1091               averageWindSpeed   += avWindSpeed   [i];
1092               //averageWindHeading += avWindHeading [i];
1093               cosHeading += cos(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1094               sinHeading += sin(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1095             }
1096           averageWindSpeed   /= 10;
1097           //averageWindHeading /= 10;
1098           cosHeading /= 10;
1099           sinHeading /= 10;
1100           averageWindHeading = atan2(sinHeading, cosHeading) *SG_RADIANS_TO_DEGREES;
1101           if (averageWindHeading < 0)
1102             averageWindHeading += 360.0;
1103           //cerr << "Wind Heading " << windHeading << " average " << averageWindHeading << endl;
1104           //cerr << "Wind Speed   " << windSpeed   << " average " << averageWindSpeed   << endl;
1105           lastUpdate = dayStart;
1106               //if (wind_speed == 0) {
1107           //  wind_heading = 270;        This forces West-facing rwys to be used in no-wind situations
1108             // which is consistent with Flightgear's initial setup.
1109           //}
1110           
1111           //string rwy_no = globals->get_runways()->search(apt->getId(), int(wind_heading));
1112           string scheduleName;
1113           //cerr << "finding active Runway for" << _id << endl;
1114           //cerr << "Nr of seconds since day start << " << dayStart << endl;
1115           ScheduleTime *currSched;
1116           //cerr << "A"<< endl;
1117           currSched = rwyPrefs.getSchedule(trafficType.c_str());
1118           if (!(currSched))
1119             return;   
1120           //cerr << "B"<< endl;
1121           scheduleName = currSched->getName(dayStart);
1122           maxTail  = currSched->getTailWind  ();
1123           maxCross = currSched->getCrossWind ();
1124           //cerr << "SChedule anme = " << scheduleName << endl;
1125           if (scheduleName.empty())
1126             return;
1127           //cerr << "C"<< endl;
1128           currRunwayGroup = rwyPrefs.getGroup(scheduleName); 
1129           //cerr << "D"<< endl;
1130           if (!(currRunwayGroup))
1131             return;
1132           nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1133           //cerr << "Nr of Active Runways = " << nrActiveRunways << endl; 
1134           currRunwayGroup->setActive(_id, averageWindSpeed, averageWindHeading, maxTail, maxCross); 
1135           nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1136           for (int i = 0; i < nrActiveRunways; i++)
1137             {
1138               type = "unknown"; // initialize to something other than landing or takeoff
1139               currRunwayGroup->getActive(i, &name, &type);
1140               if (type == "landing")
1141                 {
1142                   landing.push_back(name);
1143                   //cerr << "Landing " << name << endl; 
1144                 }
1145               if (type == "takeoff")
1146                 {
1147                   takeoff.push_back(name);
1148                   //cerr << "takeoff " << name << endl;
1149                 }
1150             }
1151         }
1152       if (action == 1) // takeoff 
1153         {
1154           int nr = takeoff.size();
1155           if (nr)
1156             {
1157               *runway = takeoff[(rand() %  nr)];
1158             }
1159           else
1160             { // Fallback
1161               chooseRunwayFallback(runway);
1162             }
1163         } 
1164       if (action == 2) // landing
1165         {
1166           int nr = landing.size();
1167           if (nr)
1168             {
1169               *runway = landing[(rand() % nr)];
1170             }
1171           else
1172             {  //fallback
1173                chooseRunwayFallback(runway);
1174             }
1175         }
1176       
1177       //*runway = globals->get_runways()->search(_id, int(windHeading));
1178       //cerr << "Seleceted runway: " << *runway << endl;
1179     }
1180 }
1181
1182 void FGAirport::chooseRunwayFallback(string *runway)
1183 {   
1184   FGEnvironment 
1185     stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1186     ->getEnvironment(getLatitude(), 
1187                      getLongitude(),
1188                      getElevation());
1189   
1190   double windSpeed = stationweather.get_wind_speed_kt();
1191   double windHeading = stationweather.get_wind_from_heading_deg();
1192   if (windSpeed == 0) {
1193     windHeading = 270;  // This forces West-facing rwys to be used in no-wind situations
1194     //which is consistent with Flightgear's initial setup.
1195   }
1196   
1197   *runway = globals->get_runways()->search(_id, int(windHeading));
1198   return; // generic fall back goes here
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(FGTaxiSegment seg)
1287 {
1288   segments.push_back(seg);
1289 }
1290
1291 void FGGroundNetwork::addNode(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;
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       cerr << "Failed to find route from waypoint " << start << " to " << end << endl;
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       cerr << "4" << endl;
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         cerr << "Dent: " << dent->d_name; // DEBUG
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 }