]> git.mxchange.org Git - flightgear.git/blob - src/ATC/AIMgr.cxx
7c9e60350241357f74c33c6ab6af56742372814e
[flightgear.git] / src / ATC / AIMgr.cxx
1 // AIMgr.cxx - implementation of FGAIMgr 
2 // - a global management class for FlightGear generated AI traffic
3 //
4 // Written by David Luff, started March 2002.
5 //
6 // Copyright (C) 2002  David C Luff - david.luff@nottingham.ac.uk
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 #include <simgear/misc/sg_path.hxx>
23
24 #include <Main/fg_props.hxx>
25 #include <Main/globals.hxx>
26 #include <simgear/math/sg_random.h>
27
28 #include <list>
29
30 #ifdef _MSC_VER
31 #  include <io.h>
32 #else
33 #  include <sys/types.h>        // for directory reading
34 #  include <dirent.h>           // for directory reading
35 #endif
36
37 #ifdef FG_WEATHERCM
38 # include <WeatherCM/FGLocalWeatherDatabase.h>
39 #else
40 # include <Environment/environment_mgr.hxx>
41 # include <Environment/environment.hxx>
42 #endif
43
44 #include "AIMgr.hxx"
45 #include "AILocalTraffic.hxx"
46 #include "AIGAVFRTraffic.hxx"
47 #include "ATCutils.hxx"
48 #include "commlist.hxx"
49
50 SG_USING_STD(list);
51 SG_USING_STD(cout);
52
53 FGAIMgr::FGAIMgr() {
54         ATC = globals->get_ATC_mgr();
55         initDone = false;
56         ai_callsigns_used["CFGFS"] = 1; // so we don't inadvertently use this
57         // TODO - use the proper user callsign when it becomes user settable.
58         removalList.clear();
59         activated.clear();
60 }
61
62 FGAIMgr::~FGAIMgr() {
63 }
64
65 void FGAIMgr::init() {
66         //cout << "AIMgr::init called..." << endl;
67         
68         // Pointers to user's position
69         lon_node = fgGetNode("/position/longitude-deg", true);
70         lat_node = fgGetNode("/position/latitude-deg", true);
71         elev_node = fgGetNode("/position/altitude-ft", true);
72
73         lon = lon_node->getDoubleValue();
74         lat = lat_node->getDoubleValue();
75         elev = elev_node->getDoubleValue();
76         
77         // Load up models at the start to avoid pausing later
78         // Hack alert - Hardwired paths!!
79         string planepath = "Aircraft/c172/Models/c172-dpm.ac";
80         _defaultModel = sgLoad3DModel( globals->get_fg_root(),
81                                           planepath.c_str(),
82                                           globals->get_props(),
83                                           globals->get_sim_time_sec() );
84                                                                           
85         planepath = "Aircraft/pa28-161/Models/pa28-161.ac";
86         _piperModel = sgLoad3DModel( globals->get_fg_root(),
87                                           planepath.c_str(),
88                                           globals->get_props(),
89                                           globals->get_sim_time_sec() );
90                                                                           
91         // go through the $FG_ROOT/ATC directory and find all *.taxi files
92         SGPath path(globals->get_fg_root());
93         path.append("ATC/");
94         string dir = path.dir();
95         string ext;
96         string file, f_ident;
97         int pos;
98         
99         // WARNING - I (DCL) haven't tested this on MSVC - this is simply cribbed from TerraGear
100 #ifdef _MSC_VER 
101         long hfile;
102         struct _finddata_t de;
103         string path_str;
104         
105         path_str = dir + "\\*.*";
106         
107         if ( ( hfile = _findfirst( path.c_str(), &de ) ) == -1 ) {
108                 SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir);
109         } else {                
110                 // load all .taxi files
111                 do {
112                         file = de.name;
113                         pos = file.find(".");
114                         ext = file.substr(pos + 1);
115                         if(ext == "taxi") {
116                                 f_ident = file.substr(0, pos);
117                                 FGAirport a;
118                                 if(dclFindAirportID(f_ident, &a)) {
119                                         SGBucket sgb(a.longitude, a.latitude);
120                                         int idx = sgb.gen_index();
121                                         if(facilities.find(idx) != facilities.end()) {
122                                                 facilities[idx]->push_back(f_ident);
123                                         } else {
124                                                 aptID_list_type* apts = new aptID_list_type;
125                                                 apts->push_back(f_ident);
126                                                 facilities[idx] = apts;
127                                         }
128                                         SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx); 
129                                 }
130                         }
131                 } while ( _findnext( hfile, &de ) == 0 );
132         }
133 #else
134
135     DIR *d;
136     struct dirent *de;
137
138     if ( (d = opendir( dir.c_str() )) == NULL ) {
139                 SG_LOG(SG_ATC, SG_WARN, "cannot open directory " << dir);
140         } else {
141                 // load all .taxi files
142                 while ( (de = readdir(d)) != NULL ) {
143                         file = de->d_name;
144                         pos = file.find(".");
145                         ext = file.substr(pos + 1);
146                         if(ext == "taxi") {
147                                 f_ident = file.substr(0, pos);
148                                 FGAirport a;
149                                 if(dclFindAirportID(f_ident, &a)) {
150                                         SGBucket sgb(a.longitude, a.latitude);
151                                         int idx = sgb.gen_index();
152                                         if(facilities.find(idx) != facilities.end()) {
153                                                 facilities[idx]->push_back(f_ident);
154                                         } else {
155                                                 ID_list_type* apts = new ID_list_type;
156                                                 apts->push_back(f_ident);
157                                                 facilities[idx] = apts;
158                                         }
159                                         SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx);
160                                 }
161                         }
162                 }               
163                 closedir(d);
164         }
165 #endif
166         
167         // See if are in range at startup and activate if necessary
168         SearchByPos(15.0);
169         
170         initDone = true;
171         
172         //cout << "AIMgr::init done..." << endl;
173         
174         /*
175         // TESTING
176         FGATCAlignedProjection ortho;
177         ortho.Init(dclGetAirportPos("KEMT"), 205.0);    // Guess of rwy19 heading
178         //Point3D ip = ortho.ConvertFromLocal(Point3D(6000, 1000, 1000));       // 90 deg entry
179         //Point3D ip = ortho.ConvertFromLocal(Point3D(-7000, 3000, 1000));      // 45 deg entry
180         Point3D ip = ortho.ConvertFromLocal(Point3D(1000, -7000, 1000));        // straight-in
181         ATC->AIRegisterAirport("KEMT");
182         FGAIGAVFRTraffic* p = new FGAIGAVFRTraffic();
183         p->SetModel(_defaultModel);
184         p->Init(ip, "KEMT", GenerateShortForm(GenerateUniqueCallsign()));
185         ai_list.push_back(p);
186         traffic[ident].push_back(p);
187         activated["KEMT"] = 1;
188         */      
189 }
190
191 void FGAIMgr::bind() {
192 }
193
194 void FGAIMgr::unbind() {
195 }
196
197 void FGAIMgr::update(double dt) {
198         if(!initDone) {
199                 init();
200                 SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()");
201         }
202         
203         //cout << activated.size() << '\n';
204         
205         Point3D userPos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
206         
207         // TODO - make these class variables!!
208         static int i = 0;
209         static int j = 0;
210
211         // Don't update any planes for first 50 runs through - this avoids some possible initialisation anomalies
212         // Might not need it now we have fade-in though?
213         if(i < 50) {
214                 ++i;
215                 return;
216         }
217         
218         if(j == 215) {
219                 SearchByPos(25.0);
220                 j = 0;
221         } else if(j == 200) {
222                 // Go through the list of activated airports and remove those out of range
223                 //cout << "The following airports have been activated by the AI system:\n";
224                 ai_activated_map_iterator apt_itr = activated.begin();
225                 while(apt_itr != activated.end()) {
226                         //cout << "FIRST IS " << (*apt_itr).first << '\n';
227                         if(dclGetHorizontalSeparation(userPos, dclGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) {
228                                 // Then get rid of it and make sure the iterator is left pointing to the next one!
229                                 string s = (*apt_itr).first;
230                                 if(traffic.find(s) != traffic.end()) {
231                                         //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n';
232                                         if(traffic[s].size()) {
233                                                 apt_itr++;
234                                         } else {
235                                                 //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n';
236                                                 activated.erase(apt_itr++);
237                                                 traffic.erase(s);
238                                         }
239                                 } else {
240                                                 //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n';
241                                                 activated.erase(apt_itr++);
242                                 }
243                         } else {
244                                 apt_itr++;
245                         }
246                 }
247         } else if(j == 180) {
248                 // Go through the list of activated airports and do the random airplane generation
249                 ai_traffic_map_iterator it = traffic.begin();
250                 while(it != traffic.end()) {
251                         string s = (*it).first;
252                         //cout << "s = " << s << " size = " << (*it).second.size() << '\n';
253                         // Only generate extra traffic if within a certain distance of the user,
254                         // TODO - maybe take users's tuned freq into account as well.
255                         double d = dclGetHorizontalSeparation(userPos, dclGetAirportPos(s)); 
256                         if(d < (15.0 * 1600.0)) {
257                                 double cd = 0.0;
258                                 bool gen = false;
259                                 //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n';
260                                 if((*it).second.size()) {
261                                         FGAIEntity* e = *((*it).second.rbegin());
262                                         cd = dclGetHorizontalSeparation(e->GetPos(), dclGetAirportPos(s));
263                                         if(cd < (d < 5000 ? 10000 : d + 5000)) {
264                                                 gen = true;
265                                         }
266                                 } else {
267                                         gen = true;
268                                         cd = 0.0;
269                                 }
270                                 if(gen) {
271                                         //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n";
272                                         //GenerateSimpleAirportTraffic(s, cd);
273                                         GenerateSimpleAirportTraffic(s, cd + 2000.0);   // The random seems a bit wierd - traffic could get far too bunched without the +2000.
274                                 }
275                         }
276                         ++it;
277                 }
278         }
279         
280         ++j;
281         
282         //cout << "Size of AI list is " << ai_list.size() << '\n';
283         
284         // TODO - need to add a check of if any activated airports have gone out of range
285         
286         string rs;      // plane to be removed, if one.
287         if(removalList.size()) {
288                 rs = *(removalList.begin());
289                 removalList.pop_front();
290         } else {
291                 rs = "";
292         }
293         
294         // Traverse the list of active planes and run all their update methods
295         // TODO - spread the load - not all planes should need updating every frame.
296         // Note that this will require dt to be calculated for each plane though
297         // since they rely on it to calculate distance travelled.
298         ai_list_itr = ai_list.begin();
299         while(ai_list_itr != ai_list.end()) {
300                 FGAIEntity *e = *ai_list_itr;
301                 if(rs.size() && e->GetCallsign() == rs) {
302                         //cout << "Removing " << rs << " from ai_list\n";
303                         ai_list_itr = ai_list.erase(ai_list_itr);
304                         delete e;
305                         // This is a hack - we should deref this plane from the airport count!
306                 } else {
307                         e->Update(dt);
308                         ++ai_list_itr;
309                 }
310         }
311
312         //cout << "Size of AI list is " << ai_list.size() << '\n';
313 }
314
315 void FGAIMgr::ScheduleRemoval(string s) {
316         //cout << "Scheduling removal of plane " << s << " from AIMgr\n";
317         removalList.push_back(s);
318 }
319
320 // Activate AI traffic at an airport
321 void FGAIMgr::ActivateAirport(string ident) {
322         ATC->AIRegisterAirport(ident);
323         // TODO - need to start the traffic more randomly
324         FGAILocalTraffic* local_traffic = new FGAILocalTraffic;
325         //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL);
326         local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident);
327         local_traffic->FlyCircuits(1, true);    // Fly 2 circuits with touch & go in between
328         ai_list.push_back(local_traffic);
329         traffic[ident].push_back(local_traffic);
330         //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n';
331         activated[ident] = 1;
332 }
333
334 // Hack - Generate AI traffic at an airport with no facilities file
335 void FGAIMgr::GenerateSimpleAirportTraffic(string ident, double min_dist) {
336         // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports
337         // This will go eventually once airport .xml files specify the traffic profile
338         if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK" 
339                            || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW"
340                                            || ident == "KSEA" || ident == "EHAM") {
341                 return;
342         }
343         
344         /*
345         // TODO - check for military airports - this should be in the current data.
346         // UGGH - there's no point at the moment - everything is labelled civil in basic.dat!
347         FGAirport a;
348         if(dclFindAirportID(ident, &a)) {
349                 cout << "CODE IS " << a.code << '\n';
350         } else {
351                 // UG - can't find the airport!
352                 return;
353         }
354         */
355         
356         Point3D aptpos = dclGetAirportPos(ident);       // TODO - check for elev of -9999
357         //cout << "ident = " << ident << ", elev = " << aptpos.elev() << '\n';
358         
359         // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude.
360         if(aptpos.elev() > 3000) {
361                 //cout << "High alt airports not yet supported - returning\n";
362                 return;
363         }
364         
365         // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers.
366         bool cessna = true;
367         
368         // Get the time and only operate VFR in the (approximate) daytime.
369         //SGTime *t = globals->get_time_params();
370         string time_str = fgGetString("sim/time/gmt-string");
371         int loc_time = atoi((time_str.substr(0,3)).c_str());
372         //cout << "gmt_time = " << loc_time << '\n';
373         loc_time += (int)((aptpos.lon() / 360.0) * 24.0);
374         while(loc_time < 0) loc_time += 24;
375         while(loc_time > 24) loc_time -= 24;
376         //cout << "loc_time = " << loc_time << '\n';
377         if(loc_time < 7 || loc_time > 19) return;
378         
379         // Check that the visibility is OK for IFR operation.
380         double visibility;
381         #ifdef FG_WEATHERCM
382         //sgVec3 position = { aptpos.lat(), aptpos.lon(), aptpos.elev() };
383         //FGPhysicalProperty stationweather = WeatherDatabase->get(position);
384         #else
385         FGEnvironment stationweather =
386             ((FGEnvironmentMgr *)globals->get_subsystem("environment"))
387               ->getEnvironment(aptpos.lat(), aptpos.lon(), aptpos.elev());      // TODO - check whether this should take ft or m for elev.
388         #endif
389         #ifdef FG_WEATHERCM
390         visibility = fgGetDouble("/environment/visibility-m");
391         #else
392         visibility = stationweather.get_visibility_m();
393         #endif
394         // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky!
395         //cout << "vis = " << visibility << '\n';
396         if(visibility < 3000) return;
397         
398         ATC->AIRegisterAirport(ident);
399         
400         // Next - get the distance from user to the airport.
401         Point3D userpos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
402         double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters
403         
404         int lev = fgGetInt("/sim/ai-traffic/level");
405         if(lev < 1 || lev > 3) lev = 2;
406         if(visibility < 6000) lev = 1;
407         //cout << "level = " << lev << '\n';
408         
409         // Next - generate any local / circuit traffic
410
411         /*
412         // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE ---------------
413         // Finally - generate VFR approaching traffic
414         //if(d > 2000) {
415         if(ident == "KPOC") {
416                 double ad = 2000.0;
417                 double avd = 3000.0;    // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
418                 //while(ad < (d < 10000 ? 12000 : d + 2000)) {
419                 for(int i=0; i<8; ++i) {
420                         double dd = sg_random() * avd;
421                         // put a minimum spacing in for now since I don't think tower will cope otherwise!
422                         if(dd < 1500) dd = 1500; 
423                         //ad += dd;
424                         ad += dd;
425                         double dir = int(sg_random() * 36);
426                         if(dir == 36) dir--;
427                         dir *= 10;
428                         //dir = 180;
429                         if(sg_random() < 0.3) cessna = false;
430                         else cessna = true;
431                         string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
432                         FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
433                         t->SetModel(cessna ? _defaultModel : _piperModel);
434                         //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
435                         Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
436                         if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);
437                         t->Init(tpos, ident, s);
438                         ai_list.push_back(t);
439                 }
440         }
441         activated[ident] = 1;
442         return;
443         //---------------------------------------------------------------------------------------------------
444         */
445         
446         double ad;   // Minimum distance out of first arriving plane in meters.
447         double mind; // Minimum spacing of traffic in meters
448         double avd;  // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
449         // Finally - generate VFR approaching traffic
450         //if(d > 2000) {
451         if(1) {
452                 if(lev == 3) {
453                         ad = 5000.0;
454                         mind = 2000.0;
455                         avd = 6000.0;
456                 } else if(lev == 2) {
457                         ad = 8000.0;
458                         mind = 4000.0;
459                         avd = 10000.0;
460                 } else {
461                         ad = 9000.0;    // Start the first aircraft at least 9K out for now.
462                         mind = 6000.0;
463                         avd = 15000.0;
464                 }
465                 /*
466                 // Check if there is already arriving traffic at this airport
467                 cout << "BING A " << ident << '\n';
468                 if(traffic.find(ident) != traffic.end()) {
469                         cout << "BING B " << ident << '\n';
470                         ai_list_type lst = traffic[ident];
471                         cout << "BING C " << ident << '\n';
472                         if(lst.size()) {
473                                 cout << "BING D " << ident << '\n';
474                                 double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos());
475                                 cout << "ident = " << ident << ", cd = " << cd << '\n';
476                                 if(cd > ad) ad = cd;
477                         }
478                 }
479                 */
480                 if(min_dist != 0) ad = min_dist;
481                 //cout << "ident = " << ident << ", ad = " << ad << '\n';
482                 while(ad < (d < 5000 ? 15000 : d + 10000)) {
483                         double dd = mind + (sg_random() * (avd - mind));
484                         ad += dd;
485                         double dir = int(sg_random() * 36);
486                         if(dir == 36) dir--;
487                         dir *= 10;
488                         if(sg_random() < 0.3) cessna = false;
489                         else cessna = true;
490                         string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
491                         FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
492                         t->SetModel(cessna ? _defaultModel : _piperModel);
493                         //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
494                         Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
495                         if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);        // FEET yuk :-(
496                         t->Init(tpos, ident, s);
497                         ai_list.push_back(t);
498                         traffic[ident].push_back(t);
499                 }
500         }       
501 }
502
503 /*
504 // Generate a VFR arrival at airport apt, at least distance d (meters) out.
505 void FGAIMgr::GenerateVFRArrival(string apt, double d) {
506 }
507 */
508
509 // Search for valid airports in the vicinity of the user and activate them if necessary
510 void FGAIMgr::SearchByPos(double range) {
511         //cout << "In SearchByPos(...)" << endl;
512         
513         // get bucket number for plane position
514         lon = lon_node->getDoubleValue();
515         lat = lat_node->getDoubleValue();
516         elev = elev_node->getDoubleValue() * SG_FEET_TO_METER;
517         SGBucket buck(lon, lat);
518
519         // get neigboring buckets
520         int bx = (int)( range*SG_NM_TO_METER / buck.get_width_m() / 2);
521         //cout << "bx = " << bx << endl;
522         int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 );
523         //cout << "by = " << by << endl;
524         
525         // Search for airports with facitities files --------------------------
526         // loop over bucket range 
527         for ( int i=-bx; i<=bx; i++) {
528                 //cout << "i loop\n";
529                 for ( int j=-by; j<=by; j++) {
530                         //cout << "j loop\n";
531                         buck = sgBucketOffset(lon, lat, i, j);
532                         long int bucket = buck.gen_index();
533                         //cout << "bucket is " << bucket << endl;
534                         if(facilities.find(bucket) != facilities.end()) {
535                                 ID_list_type* apts = facilities[bucket];
536                                 ID_list_iterator current = apts->begin();
537                                 ID_list_iterator last = apts->end();
538                                 
539                                 //cout << "Size of apts is " << apts->size() << endl;
540                                 
541                                 //double rlon = lon * SGD_DEGREES_TO_RADIANS;
542                                 //double rlat = lat * SGD_DEGREES_TO_RADIANS;
543                                 //Point3D aircraft = sgGeodToCart( Point3D(rlon, rlat, elev) );
544                                 //Point3D airport;
545                                 for(; current != last; ++current) {
546                                         //cout << "Found " << *current << endl;;
547                                         if(activated.find(*current) == activated.end()) {
548                                                 //cout << "Activating " << *current << endl;
549                                                 //FGAirport a;
550                                                 //if(dclFindAirportID(*current, &a)) {
551                                                         //      // We can do something here based on distance from the user if we wish.
552                                                 //}
553                                                 //string s = *current;
554                                                 //cout << "s = " << s << '\n';
555                                                 ActivateAirport(*current);
556                                                 //ActivateSimpleAirport(*current);      // TODO - put this back to ActivateAirport when that code is done.
557                                                 //cout << "Activation done" << endl;
558                                         } else {
559                                                 //cout << *current << " already activated" << endl;
560                                         }
561                                 }
562                         }
563                 }
564         }
565         //-------------------------------------------------------------
566         
567         // Search for any towered airports in the vicinity ------------
568         comm_list_type towered;
569         comm_list_iterator twd_itr;
570         
571         int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER);
572         if (num_twd != 0) {
573                 double closest = 1000000;
574                 string s = "";
575                 for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) {
576                         // Only activate the closest airport not already activated each time.
577                         if(activated.find(twd_itr->ident) == activated.end()) {
578                                 double sep = dclGetHorizontalSeparation(Point3D(lon, lat, elev), dclGetAirportPos(twd_itr->ident));
579                                 if(sep < closest) {
580                                         closest = sep;
581                                         s = twd_itr->ident;
582                                 }
583                                 
584                         }
585                 }
586                 if(s.size()) {
587                         // TODO - find out why empty strings come through here when all in-range airports done.
588                         GenerateSimpleAirportTraffic(s);
589                         //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n';
590                         activated[s] = 1;
591                 }
592         }
593 }
594
595 string FGAIMgr::GenerateCallsign() {
596         // For now we'll just generate US callsigns until we can regionally identify airports.
597         string s = "N";
598         // Add 3 to 5 numbers and make up to 5 with letters.
599         //sg_srandom_time();
600         double d = sg_random();
601         int n = int(d * 3);
602         if(n == 3) --n;
603         //cout << "First n, n = " << n << '\n';
604         int j = 3 + n;
605         //cout << "j = " << j << '\n';
606         for(int i=0; i<j; ++i) { 
607                 int n = int(sg_random() * 10);
608                 if(n == 10) --n;
609                 s += (char)('0' + n);
610         }
611         for(int i=j; i<5; ++i) {
612                 int n = int(sg_random() * 26);
613                 if(n == 26) --n;
614                 //cout << "Alpha, n = " << n << '\n';
615                 s += (char)('A' + n);
616         }
617         //cout << "s = " << s << '\n';
618         return(s);
619 }
620
621 string FGAIMgr::GenerateUniqueCallsign() {
622         while(1) {
623                 string s = GenerateCallsign();
624                 if(!ai_callsigns_used[s]) {
625                         ai_callsigns_used[s] = 1;
626                         return(s);
627                 }
628         }
629 }
630
631 // This will be moved somewhere else eventually!!!!
632 string FGAIMgr::GenerateShortForm(string callsign, string plane_str, bool local) {
633         //cout << callsign << '\n';
634         string s;
635         if(local) s = "Trainer-";
636         else s = plane_str;
637         for(int i=3; i>0; --i) {
638                 char c = callsign[callsign.size() - i];
639                 //cout << c << '\n';
640                 string tmp = "";
641                 tmp += c;
642                 if(isalpha(c)) s += GetPhoneticIdent(c);
643                 else s += ConvertNumToSpokenDigits(tmp);
644                 if(i > 1) s += '-';
645         }
646         return(s);
647 }