]> git.mxchange.org Git - flightgear.git/blob - src/ATC/trafficcontrol.cxx
reduce distance to 300 km
[flightgear.git] / src / ATC / trafficcontrol.cxx
1 // trafficrecord.cxx - Implementation of AIModels ATC code.
2 //
3 // Written by Durk Talsma, started September 2006.
4 //
5 // Copyright (C) 2006 Durk Talsma.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include <config.h>
25 #endif
26
27 #include <algorithm>
28 #include <math.h>
29 #include <stdlib.h>
30 #include <deque>
31
32 #include <osg/Geode>
33 #include <osg/Geometry>
34 #include <osg/MatrixTransform>
35 #include <osg/Shape>
36
37 #include <simgear/scene/material/EffectGeode.hxx>
38 #include <simgear/scene/material/matlib.hxx>
39 #include <simgear/scene/material/mat.hxx>
40 #include <Scenery/scenery.hxx>
41
42 #include "trafficcontrol.hxx"
43 #include "atc_mgr.hxx"
44 #include <AIModel/AIAircraft.hxx>
45 #include <AIModel/AIFlightPlan.hxx>
46 #include <AIModel/performancedata.hxx>
47 #include <AIModel/performancedb.hxx>
48 #include <ATC/atc_mgr.hxx>
49 #include <Traffic/TrafficMgr.hxx>
50 #include <Airports/groundnetwork.hxx>
51 #include <Airports/dynamics.hxx>
52 #include <Airports/simple.hxx>
53 #define WITH_POINT_TO_POINT
54 #include "itm.cpp"
55
56 using std::sort;
57
58 /***************************************************************************
59  * ActiveRunway
60  **************************************************************************/
61 time_t ActiveRunway::requestTimeSlot(time_t eta)
62 {
63     time_t newEta;
64     time_t separation = 90;
65     bool found = false;
66     if (estimatedArrivalTimes.size() == 0) {
67         estimatedArrivalTimes.push_back(eta);
68         return eta;
69     } else {
70         TimeVectorIterator i = estimatedArrivalTimes.begin();
71         //cerr << "Checking eta slots " << eta << ": " << endl;
72         for (i = estimatedArrivalTimes.begin();
73              i != estimatedArrivalTimes.end(); i++) {
74             //cerr << "Stored time : " << (*i) << endl;
75         }
76         i = estimatedArrivalTimes.begin();
77         if ((eta + separation) < (*i)) {
78             newEta = eta;
79             found = true;
80             //cerr << "Storing at beginning" << endl;
81         }
82         while ((i != estimatedArrivalTimes.end()) && (!found)) {
83             TimeVectorIterator j = i + 1;
84             if (j == estimatedArrivalTimes.end()) {
85                 if (((*i) + separation) < eta) {
86                     //cerr << "Storing at end" << endl;
87                     newEta = eta;
88                 } else {
89                     newEta = (*i) + separation;
90                     //cerr << "Storing at end + separation" << endl;
91                 }
92             } else {
93                 if ((((*j) - (*i)) > (separation * 2))) {       // found a potential slot
94                     // now check whether this slot is usable:
95                     // 1) eta should fall between the two points
96                     //    i.e. eta > i AND eta < j
97                     //
98                     //cerr << "Found potential slot after " << (*i) << endl;
99                     if (eta > (*i) && (eta < (*j))) {
100                         found = true;
101                         if (eta < ((*i) + separation)) {
102                             newEta = (*i) + separation;
103                             //cerr << "Using  original" << (*i) << " + separation " << endl;
104                         } else {
105                             newEta = eta;
106                             //cerr << "Using original after " << (*i) << endl;
107                         }
108                     } else if (eta < (*i)) {
109                         found = true;
110                         newEta = (*i) + separation;
111                         //cerr << "Using delayed slot after " << (*i) << endl;
112                     }
113                     /*
114                        if (((*j) - separation) < eta) {
115                        found = true;
116                        if (((*i) + separation) < eta) {
117                        newEta = eta;
118                        cerr << "Using original after " << (*i) << endl;
119                        } else {
120                        newEta = (*i) + separation;
121                        cerr << "Using  " << (*i) << " + separation " << endl;
122                        }
123                        } */
124                 }
125             }
126             i++;
127         }
128     }
129     //cerr << ". done. New ETA : " << newEta << endl;
130
131     estimatedArrivalTimes.push_back(newEta);
132     sort(estimatedArrivalTimes.begin(), estimatedArrivalTimes.end());
133     // do some housekeeping : remove any timestamps that are past
134     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
135     TimeVectorIterator i = estimatedArrivalTimes.begin();
136     while (i != estimatedArrivalTimes.end()) {
137         if ((*i) < now) {
138             //cerr << "Deleting timestamp " << (*i) << " (now = " << now << "). " << endl;
139             estimatedArrivalTimes.erase(i);
140             i = estimatedArrivalTimes.begin();
141         } else {
142             i++;
143         }
144     }
145     return newEta;
146 }
147
148 /***************************************************************************
149  * FGTrafficRecord
150  **************************************************************************/
151 FGTrafficRecord::FGTrafficRecord():
152 id(0), waitsForId(0),
153 currentPos(0),
154 leg(0),
155 frequencyId(0),
156 state(0),
157 allowTransmission(true),
158 latitude(0), longitude(0), heading(0), speed(0), altitude(0), radius(0)
159 {
160 }
161
162 void FGTrafficRecord::setPositionAndIntentions(int pos,
163                                                FGAIFlightPlan * route)
164 {
165
166     currentPos = pos;
167     if (intentions.size()) {
168         intVecIterator i = intentions.begin();
169         if ((*i) != pos) {
170             SG_LOG(SG_GENERAL, SG_ALERT,
171                    "Error in FGTrafficRecord::setPositionAndIntentions");
172             //cerr << "Pos : " << pos << " Curr " << *(intentions.begin())  << endl;
173             for (intVecIterator i = intentions.begin();
174                  i != intentions.end(); i++) {
175                 //cerr << (*i) << " ";
176             }
177             //cerr << endl;
178         }
179         intentions.erase(i);
180     } else {
181         //FGAIFlightPlan::waypoint* const wpt= route->getCurrentWaypoint();
182         int size = route->getNrOfWayPoints();
183         //cerr << "Setting pos" << pos << " ";
184         //cerr << "setting intentions ";
185         for (int i = 0; i < size; i++) {
186             int val = route->getRouteIndex(i);
187             //cerr << val<< " ";
188             if ((val) && (val != pos)) {
189                 intentions.push_back(val);
190                 //cerr << "[set] ";
191             }
192         }
193         //cerr << endl;
194         //while (route->next(&legNr, &routeNr)) {
195         //intentions.push_back(routeNr);
196         //}
197         //route->rewind(currentPos);
198     }
199     //exit(1);
200 }
201 /**
202  * Check if another aircraft is ahead of the current one, and on the same 
203  * return true / false is the is/isn't the case.
204  *
205  ****************************************************************************/
206
207 bool FGTrafficRecord::checkPositionAndIntentions(FGTrafficRecord & other)
208 {
209     bool result = false;
210     //cerr << "Start check 1" << endl;
211     if (currentPos == other.currentPos) {
212         //cerr << callsign << ": Check Position and intentions: we are on the same taxiway" << other.callsign << "Index = " << currentPos << endl;
213         result = true;
214     }
215     //  else if (other.intentions.size()) 
216     //     {
217     //       cerr << "Start check 2" << endl;
218     //       intVecIterator i = other.intentions.begin(); 
219     //       while (!((i == other.intentions.end()) || ((*i) == currentPos)))
220     //     i++;
221     //       if (i != other.intentions.end()) {
222     //     cerr << "Check Position and intentions: current matches other.intentions" << endl;
223     //     result = true;
224     //       }
225     else if (intentions.size()) {
226         //cerr << "Start check 3" << endl;
227         intVecIterator i = intentions.begin();
228         //while (!((i == intentions.end()) || ((*i) == other.currentPos)))
229         while (i != intentions.end()) {
230             if ((*i) == other.currentPos) {
231                 break;
232             }
233             i++;
234         }
235         if (i != intentions.end()) {
236             //cerr << callsign << ": Check Position and intentions: .other.current matches" << other.callsign << "Index = " << (*i) << endl;
237             result = true;
238         }
239     }
240     //cerr << "Done !!" << endl;
241     return result;
242 }
243
244 void FGTrafficRecord::setPositionAndHeading(double lat, double lon,
245                                             double hdg, double spd,
246                                             double alt)
247 {
248     latitude = lat;
249     longitude = lon;
250     heading = hdg;
251     speed = spd;
252     altitude = alt;
253 }
254
255 int FGTrafficRecord::crosses(FGGroundNetwork * net,
256                              FGTrafficRecord & other)
257 {
258     if (checkPositionAndIntentions(other)
259         || (other.checkPositionAndIntentions(*this)))
260         return -1;
261     intVecIterator i, j;
262     int currentTargetNode = 0, otherTargetNode = 0;
263     if (currentPos > 0)
264         currentTargetNode = net->findSegment(currentPos)->getEnd()->getIndex(); // OKAY,... 
265     if (other.currentPos > 0)
266         otherTargetNode = net->findSegment(other.currentPos)->getEnd()->getIndex();     // OKAY,...
267     if ((currentTargetNode == otherTargetNode) && currentTargetNode > 0)
268         return currentTargetNode;
269     if (intentions.size()) {
270         for (i = intentions.begin(); i != intentions.end(); i++) {
271             if ((*i) > 0) {
272                 if ((currentTargetNode ==
273                      net->findSegment(*i)->getEnd()->getIndex())) {
274                     //cerr << "Current crosses at " << currentTargetNode <<endl;
275                     return currentTargetNode;
276                 }
277             }
278         }
279     }
280     if (other.intentions.size()) {
281         for (i = other.intentions.begin(); i != other.intentions.end();
282              i++) {
283             if ((*i) > 0) {
284                 if (otherTargetNode ==
285                     net->findSegment(*i)->getEnd()->getIndex()) {
286                     //cerr << "Other crosses at " << currentTargetNode <<endl;
287                     return otherTargetNode;
288                 }
289             }
290         }
291     }
292     if (intentions.size() && other.intentions.size()) {
293         for (i = intentions.begin(); i != intentions.end(); i++) {
294             for (j = other.intentions.begin(); j != other.intentions.end();
295                  j++) {
296                 //cerr << "finding segment " << *i << " and " << *j << endl;
297                 if (((*i) > 0) && ((*j) > 0)) {
298                     currentTargetNode =
299                         net->findSegment(*i)->getEnd()->getIndex();
300                     otherTargetNode =
301                         net->findSegment(*j)->getEnd()->getIndex();
302                     if (currentTargetNode == otherTargetNode) {
303                         //cerr << "Routes will cross at " << currentTargetNode << endl;
304                         return currentTargetNode;
305                     }
306                 }
307             }
308         }
309     }
310     return -1;
311 }
312
313 bool FGTrafficRecord::onRoute(FGGroundNetwork * net,
314                               FGTrafficRecord & other)
315 {
316     int node = -1, othernode = -1;
317     if (currentPos > 0)
318         node = net->findSegment(currentPos)->getEnd()->getIndex();
319     if (other.currentPos > 0)
320         othernode =
321             net->findSegment(other.currentPos)->getEnd()->getIndex();
322     if ((node == othernode) && (node != -1))
323         return true;
324     if (other.intentions.size()) {
325         for (intVecIterator i = other.intentions.begin();
326              i != other.intentions.end(); i++) {
327             if (*i > 0) {
328                 othernode = net->findSegment(*i)->getEnd()->getIndex();
329                 if ((node == othernode) && (node > -1))
330                     return true;
331             }
332         }
333     }
334     //if (other.currentPos > 0)
335     //  othernode = net->findSegment(other.currentPos)->getEnd()->getIndex();
336     //if (intentions.size())
337     //  {
338     //    for (intVecIterator i = intentions.begin(); i != intentions.end(); i++)
339     //    {
340     //      if (*i > 0) 
341     //        {
342     //          node = net->findSegment(*i)->getEnd()->getIndex();
343     //          if ((node == othernode) && (node > -1))
344     //            return true;
345     //        }
346     //    }
347     //  }
348     return false;
349 }
350
351
352 bool FGTrafficRecord::isOpposing(FGGroundNetwork * net,
353                                  FGTrafficRecord & other, int node)
354 {
355     // Check if current segment is the reverse segment for the other aircraft
356     FGTaxiSegment *opp;
357     //cerr << "Current segment " << currentPos << endl;
358     if ((currentPos > 0) && (other.currentPos > 0)) {
359         opp = net->findSegment(currentPos)->opposite();
360         if (opp) {
361             if (opp->getIndex() == other.currentPos)
362                 return true;
363         }
364
365         for (intVecIterator i = intentions.begin(); i != intentions.end();
366              i++) {
367             if ((opp = net->findSegment(other.currentPos)->opposite())) {
368                 if ((*i) > 0)
369                     if (opp->getIndex() ==
370                         net->findSegment(*i)->getIndex()) {
371                         if (net->findSegment(*i)->getStart()->getIndex() ==
372                             node) {
373                             {
374                                 //cerr << "Found the node " << node << endl;
375                                 return true;
376                             }
377                         }
378                     }
379             }
380             if (other.intentions.size()) {
381                 for (intVecIterator j = other.intentions.begin();
382                      j != other.intentions.end(); j++) {
383                     // cerr << "Current segment 1 " << (*i) << endl;
384                     if ((*i) > 0) {
385                         if ((opp = net->findSegment(*i)->opposite())) {
386                             if (opp->getIndex() ==
387                                 net->findSegment(*j)->getIndex()) {
388                                 //cerr << "Nodes " << net->findSegment(*i)->getIndex()
389                                 //   << " and  " << net->findSegment(*j)->getIndex()
390                                 //   << " are opposites " << endl;
391                                 if (net->findSegment(*i)->getStart()->
392                                     getIndex() == node) {
393                                     {
394                                         //cerr << "Found the node " << node << endl;
395                                         return true;
396                                     }
397                                 }
398                             }
399                         }
400                     }
401                 }
402             }
403         }
404     }
405     return false;
406 }
407
408 void FGTrafficRecord::setSpeedAdjustment(double spd)
409 {
410     instruction.setChangeSpeed(true);
411     instruction.setSpeed(spd);
412 }
413
414 void FGTrafficRecord::setHeadingAdjustment(double heading)
415 {
416     instruction.setChangeHeading(true);
417     instruction.setHeading(heading);
418 }
419
420 bool FGTrafficRecord::pushBackAllowed()
421 {
422     // With the user ATC / AI integration, checking whether the user's aircraft is near no longer works, because
423     // this will effectively block the user's aircraft itself from receiving pushback clearance. 
424     // So, what can we do?
425     /*
426     double course, az2, dist;
427     SGGeod curr(SGGeod::fromDegM(getLongitude(),
428                                  getLatitude(), getAltitude()));
429
430     double userLatitude = fgGetDouble("/position/latitude-deg");
431     double userLongitude = fgGetDouble("/position/longitude-deg");
432     SGGeod user(SGGeod::fromDeg(userLongitude, userLatitude));
433     SGGeodesy::inverse(curr, user, course, az2, dist);
434     //cerr << "Distance to user : " << dist << endl;
435     return (dist > 250);
436     */
437
438
439     // In essence, we should check whether the pusbback route itself, as well as the associcated
440     // taxiways near the pushback point are free of traffic. 
441     // To do so, we need to 
442     return true;
443 }
444
445
446
447 /***************************************************************************
448  * FGATCInstruction
449  *
450  **************************************************************************/
451 FGATCInstruction::FGATCInstruction()
452 {
453     holdPattern = false;
454     holdPosition = false;
455     changeSpeed = false;
456     changeHeading = false;
457     changeAltitude = false;
458     resolveCircularWait = false;
459
460     speed = 0;
461     heading = 0;
462     alt = 0;
463 }
464
465
466 bool FGATCInstruction::hasInstruction()
467 {
468     return (holdPattern || holdPosition || changeSpeed || changeHeading
469             || changeAltitude || resolveCircularWait);
470 }
471
472 /***************************************************************************
473  * FGATCController
474  *
475  **************************************************************************/
476
477
478
479
480 FGATCController::FGATCController()
481 {
482     //cerr << "running FGATController constructor" << endl;
483     dt_count = 0;
484     available = true;
485     lastTransmission = 0;
486     initialized = false;
487 }
488
489 FGATCController::~FGATCController()
490 {
491      //cerr << "running FGATController destructor" << endl;
492 }
493
494 string FGATCController::getGateName(FGAIAircraft * ref)
495 {
496     return ref->atGate();
497 }
498
499 bool FGATCController::isUserAircraft(FGAIAircraft* ac) 
500
501     return (ac->getCallSign() == fgGetString("/sim/multiplay/callsign")) ? true : false; 
502 };
503
504 void FGATCController::transmit(FGTrafficRecord * rec, FGAirportDynamics *parent, AtcMsgId msgId,
505                                AtcMsgDir msgDir, bool audible)
506 {
507     string sender, receiver;
508     int stationFreq = 0;
509     int taxiFreq = 0;
510     int towerFreq = 0;
511     int freqId = 0;
512     string atisInformation;
513     string text;
514     string taxiFreqStr;
515     string towerFreqStr;
516     double heading = 0;
517     string activeRunway;
518     string fltType;
519     string rwyClass;
520     string SID;
521     string transponderCode;
522     FGAIFlightPlan *fp;
523     string fltRules;
524     string instructionText;
525     int ground_to_air=0;
526
527     //double commFreqD;
528     sender = rec->getAircraft()->getTrafficRef()->getCallSign();
529     if (rec->getAircraft()->getTaxiClearanceRequest()) {
530         instructionText = "push-back and taxi";
531     } else {
532         instructionText = "taxi";
533     }
534     //cerr << "transmitting for: " << sender << "Leg = " << rec->getLeg() << endl;
535     switch (rec->getLeg()) {
536     case 1:
537     case 2:
538         freqId = rec->getNextFrequency();
539         stationFreq =
540             rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
541             getDynamics()->getGroundFrequency(rec->getLeg() + freqId);
542         taxiFreq =
543             rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
544             getDynamics()->getGroundFrequency(2);
545         towerFreq = 
546             rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
547             getDynamics()->getTowerFrequency(2);
548         receiver =
549             rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
550             getName() + "-Ground";
551         atisInformation =
552             rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
553             getDynamics()->getAtisSequence();
554         break;
555     case 3:
556         receiver =
557             rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
558             getName() + "-Tower";
559         break;
560     }
561     // Swap sender and receiver value in case of a ground to air transmission
562     if (msgDir == ATC_GROUND_TO_AIR) {
563         string tmp = sender;
564         sender = receiver;
565         receiver = tmp;
566         ground_to_air=1;
567     }
568     switch (msgId) {
569     case MSG_ANNOUNCE_ENGINE_START:
570         text = sender + ". Ready to Start up";
571         break;
572     case MSG_REQUEST_ENGINE_START:
573         text =
574             receiver + ", This is " + sender + ". Position " +
575             getGateName(rec->getAircraft()) + ". Information " +
576             atisInformation + ". " +
577             rec->getAircraft()->getTrafficRef()->getFlightRules() +
578             " to " +
579             rec->getAircraft()->getTrafficRef()->getArrivalAirport()->
580             getName() + ". Request start-up";
581         break;
582         // Acknowledge engine startup permission
583         // Assign departure runway
584         // Assign SID, if necessery (TODO)
585     case MSG_PERMIT_ENGINE_START:
586         taxiFreqStr = formatATCFrequency3_2(taxiFreq);
587
588         heading = rec->getAircraft()->getTrafficRef()->getCourse();
589         fltType = rec->getAircraft()->getTrafficRef()->getFlightType();
590         rwyClass =
591             rec->getAircraft()->GetFlightPlan()->
592             getRunwayClassFromTrafficType(fltType);
593
594         rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
595             getDynamics()->getActiveRunway(rwyClass, 1, activeRunway,
596                                            heading);
597         rec->getAircraft()->GetFlightPlan()->setRunway(activeRunway);
598         fp = rec->getAircraft()->getTrafficRef()->getDepartureAirport()->
599             getDynamics()->getSID(activeRunway, heading);
600         rec->getAircraft()->GetFlightPlan()->setSID(fp);
601         if (fp) {
602             SID = fp->getName() + " departure";
603         } else {
604             SID = "fly runway heading ";
605         }
606         //snprintf(buffer, 7, "%3.2f", heading);
607         fltRules = rec->getAircraft()->getTrafficRef()->getFlightRules();
608         transponderCode = genTransponderCode(fltRules);
609         rec->getAircraft()->SetTransponderCode(transponderCode);
610         text =
611             receiver + ". Start-up approved. " + atisInformation +
612             " correct, runway " + activeRunway + ", " + SID + ", squawk " +
613             transponderCode + ". " +
614             "For "+ instructionText + " clearance call " + taxiFreqStr + ". " +
615             sender + " control.";
616         break;
617     case MSG_DENY_ENGINE_START:
618         text = receiver + ". Standby";
619         break;
620     case MSG_ACKNOWLEDGE_ENGINE_START:
621         fp = rec->getAircraft()->GetFlightPlan()->getSID();
622         if (fp) {
623             SID =
624                 rec->getAircraft()->GetFlightPlan()->getSID()->getName() +
625                 " departure";
626         } else {
627             SID = "fly runway heading ";
628         }
629         taxiFreqStr = formatATCFrequency3_2(taxiFreq);
630         activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
631         transponderCode = rec->getAircraft()->GetTransponderCode();
632
633         text =
634             receiver + ". Start-up approved. " + atisInformation +
635             " correct, runway " + activeRunway + ", " + SID + ", squawk " +
636             transponderCode + ". " +
637             "For " + instructionText + " clearance call " + taxiFreqStr + ". " +
638             sender;
639         break;
640     case MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY:
641         taxiFreqStr = formatATCFrequency3_2(taxiFreq);
642         text = receiver + ". Switching to " + taxiFreqStr + ". " + sender;
643         break;
644     case MSG_INITIATE_CONTACT:
645         text = receiver + ". With you. " + sender;
646         break;
647     case MSG_ACKNOWLEDGE_INITIATE_CONTACT:
648         text = receiver + ". Roger. " + sender;
649         break;
650     case MSG_REQUEST_PUSHBACK_CLEARANCE:
651         if (rec->getAircraft()->getTaxiClearanceRequest()) {
652             text = receiver + ". Request push-back. " + sender;
653         } else {
654             text = receiver + ". Request Taxi clearance. " + sender;
655         }
656         break;
657     case MSG_PERMIT_PUSHBACK_CLEARANCE:
658         if (rec->getAircraft()->getTaxiClearanceRequest()) {
659             text = receiver + ". Push-back approved. " + sender;
660         } else {
661             text = receiver + ". Cleared to Taxi." + sender;
662         }
663         break;
664     case MSG_HOLD_PUSHBACK_CLEARANCE:
665         text = receiver + ". Standby. " + sender;
666         break;
667     case MSG_REQUEST_TAXI_CLEARANCE:
668         text = receiver + ". Ready to Taxi. " + sender;
669         break;
670     case MSG_ISSUE_TAXI_CLEARANCE:
671         text = receiver + ". Cleared to taxi. " + sender;
672         break;
673     case MSG_ACKNOWLEDGE_TAXI_CLEARANCE:
674         text = receiver + ". Cleared to taxi. " + sender;
675         break;
676     case MSG_HOLD_POSITION:
677         text = receiver + ". Hold Position. " + sender;
678         break;
679     case MSG_ACKNOWLEDGE_HOLD_POSITION:
680         text = receiver + ". Holding Position. " + sender;
681         break;
682     case MSG_RESUME_TAXI:
683         text = receiver + ". Resume Taxiing. " + sender;
684         break;
685     case MSG_ACKNOWLEDGE_RESUME_TAXI:
686         text = receiver + ". Continuing Taxi. " + sender;
687         break;
688     case MSG_REPORT_RUNWAY_HOLD_SHORT:
689         activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
690         //activeRunway = "test";
691         text = receiver + ". Holding short runway " 
692                         + activeRunway 
693                         + ". " + sender;
694         //text = "test1";
695         //cerr << "1 Currently at leg " << rec->getLeg() << endl;
696         break;
697     case MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT:
698         activeRunway = rec->getAircraft()->GetFlightPlan()->getRunway();
699         text = receiver + "Roger. Holding short runway " 
700         //                + activeRunway 
701                         + ". " + sender;
702         //text = "test2";
703         //cerr << "2 Currently at leg " << rec->getLeg() << endl;
704         break;
705     case MSG_SWITCH_TOWER_FREQUENCY:
706         towerFreqStr = formatATCFrequency3_2(towerFreq);
707         text = receiver + "Contact Tower at " + towerFreqStr + ". " + sender;
708         //text = "test3";
709         //cerr << "3 Currently at leg " << rec->getLeg() << endl;
710         break;
711     case MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY:
712         towerFreqStr = formatATCFrequency3_2(towerFreq);
713         text = receiver + "Roger, switching to tower at " + towerFreqStr + ". " + sender;
714         //text = "test4";
715         //cerr << "4 Currently at leg " << rec->getLeg() << endl;
716         break;
717     default:
718         //text = "test3";
719         text = text + sender + ". Transmitting unknown Message";
720         break;
721     }
722     if (audible) {
723         double onBoardRadioFreq0 =
724             fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
725         double onBoardRadioFreq1 =
726             fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz");
727         int onBoardRadioFreqI0 = (int) floor(onBoardRadioFreq0 * 100 + 0.5);
728         int onBoardRadioFreqI1 = (int) floor(onBoardRadioFreq1 * 100 + 0.5);
729         //cerr << "Using " << onBoardRadioFreq0 << ", " << onBoardRadioFreq1 << " and " << stationFreq << " for " << text << endl;
730
731         // Display ATC message only when one of the radios is tuned
732         // the relevant frequency.
733         // Note that distance attenuation is currently not yet implemented
734                 
735         if ((onBoardRadioFreqI0 == stationFreq)
736             || (onBoardRadioFreqI1 == stationFreq)) {
737                 
738             if (rec->allowTransmissions()) {
739                 double snr = calculate_attenuation(rec, parent, ground_to_air);
740                 if (snr <= 0)
741                         return;
742                 if (snr > 0 && snr < 12) {
743                         //for low SNR values implement a way to make the conversation
744                         //hard to understand but audible
745                         //how this works in the real world, is the receiver AGC fails to capture the slope
746                         //and the signal, due to being amplitude modulated, decreases volume after demodulation
747                         //the implementation below is more akin to what would happen on a FM transmission
748                         //therefore the correct way would be to work on the volume
749                         string hash_noise = " ";
750                         int reps = fabs((int)snr - 11);
751                         int t_size = text.size();
752                         for (int n=1;n<=reps * 2;n++) {
753                                 int pos = rand() % t_size -1;
754                                 text.replace(pos,1, hash_noise);
755                         }
756                         
757                 }
758                 fgSetString("/sim/messages/atc", text.c_str());
759             }
760         }
761     } else {
762         FGATCManager *atc = (FGATCManager*) globals->get_subsystem("atc");
763         atc->getATCDialog()->addEntry(1, text);
764         
765     }
766 }
767
768 double FGATCController::calculate_attenuation(FGTrafficRecord * rec, FGAirportDynamics *parent,
769                                int ground_to_air) {
770
771         ///  Implement radio attenuation                
772         ///  based on the Longley-Rice propagation model
773        
774         FGScenery * scenery = globals->get_scenery();
775         // player aircraft position
776         double own_lat = fgGetDouble("/position/latitude-deg");
777         double own_lon = fgGetDouble("/position/longitude-deg");
778         double own_alt_ft = fgGetDouble("/position/altitude-ft");
779         double own_alt= own_alt_ft * SG_FEET_TO_METER;
780         
781         //cerr << "ITM:: pilot Lat: " << own_lat << ", Lon: " << own_lon << ", Alt: " << own_alt << endl;
782         
783         SGGeod own_pos = SGGeod::fromDegM( own_lon, own_lat, own_alt );
784         SGGeod max_own_pos = SGGeod::fromDegM( own_lon, own_lat, SG_MAX_ELEVATION_M );
785         SGGeoc center = SGGeoc::fromGeod( max_own_pos );
786         SGGeoc own_pos_c = SGGeoc::fromGeod( own_pos );
787         
788         // position of sender radio antenna (HAAT)
789         // sender can be aircraft or ground station
790         double ATC_HAAT = 30.0;
791         double Aircraft_HAAT = 5.0;
792         double sender_alt_ft,sender_alt;
793         double transmitter_height=0.0;
794         double receiver_height=0.0;
795         SGGeod sender_pos;
796         SGGeod max_sender_pos;
797         if(ground_to_air) {
798                 sender_alt_ft = parent->getElevation();
799                 sender_alt = sender_alt_ft * SG_FEET_TO_METER;
800                 sender_pos= SGGeod::fromDegM( parent->getLongitude(),
801                         parent->getLatitude(), sender_alt );
802                 max_sender_pos = SGGeod::fromDegM( parent->getLongitude(),
803                         parent->getLatitude(), SG_MAX_ELEVATION_M );
804         }
805         else {
806                 sender_alt_ft = rec->getAltitude();
807                 sender_alt = sender_alt_ft * SG_FEET_TO_METER;
808                 sender_pos= SGGeod::fromDegM( rec->getLongitude(),
809                         rec->getLatitude(), sender_alt );
810                 max_sender_pos = SGGeod::fromDegM( rec->getLongitude(),
811                         rec->getLatitude(), SG_MAX_ELEVATION_M );
812         }
813         SGGeoc sender_pos_c = SGGeoc::fromGeod( sender_pos );
814         //cerr << "ITM:: sender Lat: " << parent->getLatitude() << ", Lon: " << parent->getLongitude() << ", Alt: " << sender_alt << endl;
815         
816         double point_distance= 90.0; // regular SRTM is 90 meters
817         double course = SGGeodesy::courseRad(own_pos_c, sender_pos_c);
818         double distance_m = SGGeodesy::distanceM(own_pos, sender_pos);
819         double probe_distance = 0.0;
820         // If distance larger than this value (300 km), assume reception imposssible
821         // technically 300 km is no problem if LOS conditions exist,
822         // but we do this to spare resources
823         if (distance_m > 300000)
824                 return -1.0;
825         
826         
827         double max_points = distance_m / point_distance;
828         deque<double> _elevations;
829         
830         
831         double elevation_under_pilot = 0.0;
832         if (scenery->get_elevation_m( max_own_pos, elevation_under_pilot, NULL )) {
833                 receiver_height = own_alt - elevation_under_pilot + 3; //assume antenna located 3 meters above ground
834         }
835         
836
837         double elevation_under_sender = 0.0;
838         if (scenery->get_elevation_m( max_sender_pos, elevation_under_sender, NULL )) {
839                 transmitter_height = sender_alt - elevation_under_sender;
840         }
841         else {
842                 transmitter_height = sender_alt;
843         }
844         if(ground_to_air) 
845                 transmitter_height += ATC_HAAT;
846         else
847                 transmitter_height += Aircraft_HAAT;
848         
849         cerr << "ITM:: RCVhgt: " << receiver_height << ", TRXhgt: " << transmitter_height << ", Distance: " << distance_m << endl;
850         
851         
852         unsigned int e_size = (deque<unsigned>::size_type)max_points;
853         
854         while (_elevations.size() <= e_size) {
855                 probe_distance += point_distance;
856                 SGGeod probe = SGGeod::fromGeoc(center.advanceRadM( course, probe_distance ));
857                 
858                 double elevation_m = 0.0;
859         
860                 if (scenery->get_elevation_m( probe, elevation_m, NULL )) {
861                          _elevations.push_front(elevation_m);
862                          //cerr << "ITM:: Probe elev: " << elevation_m << endl;
863                 }
864                 else {
865                         _elevations.push_front(0.0);
866                 }
867         }
868         _elevations.push_back(elevation_under_pilot);
869         _elevations.push_front(elevation_under_sender);
870         double max_alt_between=0.0;
871         for( deque<double>::size_type i = 0; i < _elevations.size(); i++ ) {
872                 if (_elevations[i] > max_alt_between) {
873                         max_alt_between = _elevations[i];
874                 }
875         }
876         
877         double num_points= (double)_elevations.size();
878         //cerr << "ITM:: Max alt between: " << max_alt_between << ", num points:" << num_points << endl;
879         _elevations.push_front(point_distance);
880         _elevations.push_front(num_points -1);
881         int size = _elevations.size();
882         double itm_elev[size];
883         for(int i=0;i<size;i++) {
884                 itm_elev[i]=_elevations[i];
885                 //cerr << "ITM:: itm_elev: " << _elevations[i] << endl;
886         }
887         
888         ////////////// ITM default parameters //////////////
889         // later perhaps take them from tile materials?
890         double eps_dielect=15.0;
891         double sgm_conductivity = 0.005;
892         double eno = 301.0;
893         double frq_mhz = 125.0;         // middle of bandplan
894         int radio_climate = 5;          // continental temperate
895         int pol=1;      // assuming vertical polarization although this is more complex in reality
896         double conf = 0.90;     // my own tests in Radiomobile have worked best with these values
897         double rel = 0.80;      // ^^
898         double dbloss;
899         char strmode[150];
900         int errnum;
901         
902         /////////// radio parameters ///////////
903         double receiver_sensitivity = -110.0;   // typical AM receiver sensitivity seems to be 0.8 microVolt at 12dB SINAD
904         // AM transmitter power in dBm.
905         // Note this value is calculated from the typical final transistor stage output
906         // !!! small aircraft have portable transmitters which operate at 36 dBm output (4 Watts)
907         // later store this value in aircraft description
908         // ATC comms usually operate high power equipment, thus making the link asymetrical; this is ignored for now
909         double transmitter_power = 43.0;
910         double antenna_gain = 2.0;      //real-life gain for conventional monopole/dipole antenna
911         if(ground_to_air)
912                 transmitter_power = 49.0;
913         else
914                 transmitter_power = 43.0;
915         if(ground_to_air)
916                 antenna_gain = 5.0; //pilot plane's antenna gain + ground station antenna gain
917         else
918                 antenna_gain = 2.0; //pilot plane's antenna gain + AI aircraft antenna gain
919         double link_budget = transmitter_power - receiver_sensitivity + antenna_gain;   
920         
921         
922         // first Fresnel zone radius
923         // frequency in the middle of the bandplan, more accuracy is not necessary
924         double fz_clr= 8.657 * sqrt(distance_m / 0.125);
925         
926         // TODO: If we clear the first Fresnel zone, we are into line of sight teritory
927
928         // else we need to calculate point to point link loss
929
930         point_to_point(itm_elev, transmitter_height, receiver_height,
931                 eps_dielect, sgm_conductivity, eno, frq_mhz, radio_climate,
932                 pol, conf, rel, dbloss, strmode, errnum);
933
934         cerr << "ITM:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm, " << strmode << ", Error: " << errnum << endl;
935         
936         //if (errnum !=0 && errnum !=1)
937         //      return -1;
938         double snr = link_budget - dbloss;
939         return snr;
940
941 }
942
943 string FGATCController::formatATCFrequency3_2(int freq)
944 {
945     char buffer[7];
946     snprintf(buffer, 7, "%3.2f", ((float) freq / 100.0));
947     return string(buffer);
948 }
949
950 // TODO: Set transponder codes according to real-world routes.
951 // The current version just returns a random string of four octal numbers. 
952 string FGATCController::genTransponderCode(string fltRules)
953 {
954     if (fltRules == "VFR") {
955         return string("1200");
956     } else {
957         char buffer[5];
958         snprintf(buffer, 5, "%d%d%d%d", rand() % 8, rand() % 8, rand() % 8,
959                  rand() % 8);
960         return string(buffer);
961     }
962 }
963
964 void FGATCController::init() 
965 {
966    if (!initialized) {
967        FGATCManager *mgr = (FGATCManager*) globals->get_subsystem("ATC");
968        mgr->addController(this);
969        initialized = true;
970     }
971 }
972
973 /***************************************************************************
974  * class FGTowerController
975  *
976  **************************************************************************/
977 FGTowerController::FGTowerController(FGAirportDynamics *par) :
978 FGATCController()
979 {
980     parent = par;
981 }
982
983 // 
984 void FGTowerController::announcePosition(int id,
985                                          FGAIFlightPlan * intendedRoute,
986                                          int currentPosition, double lat,
987                                          double lon, double heading,
988                                          double speed, double alt,
989                                          double radius, int leg,
990                                          FGAIAircraft * ref)
991 {
992     init();
993     TrafficVectorIterator i = activeTraffic.begin();
994     // Search whether the current id alread has an entry
995     // This might be faster using a map instead of a vector, but let's start by taking a safe route
996     if (activeTraffic.size()) {
997         //while ((i->getId() != id) && i != activeTraffic.end()) {
998         while (i != activeTraffic.end()) {
999             if (i->getId() == id) {
1000                 break;
1001             }
1002             i++;
1003         }
1004     }
1005     // Add a new TrafficRecord if no one exsists for this aircraft.
1006     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1007         FGTrafficRecord rec;
1008         rec.setId(id);
1009
1010         rec.setPositionAndHeading(lat, lon, heading, speed, alt);
1011         rec.setRunway(intendedRoute->getRunway());
1012         rec.setLeg(leg);
1013         //rec.setCallSign(callsign);
1014         rec.setRadius(radius);
1015         rec.setAircraft(ref);
1016         activeTraffic.push_back(rec);
1017         // Don't just schedule the aircraft for the tower controller, also assign if to the correct active runway. 
1018         ActiveRunwayVecIterator rwy = activeRunways.begin();
1019         if (activeRunways.size()) {
1020             while (rwy != activeRunways.end()) {
1021                 if (rwy->getRunwayName() == intendedRoute->getRunway()) {
1022                     break;
1023                 }
1024                 rwy++;
1025             }
1026         }
1027         if (rwy == activeRunways.end()) {
1028             ActiveRunway aRwy(intendedRoute->getRunway(), id);
1029             aRwy.addToDepartureCue(ref);
1030             activeRunways.push_back(aRwy);
1031             rwy = (activeRunways.end()-1);
1032         } else {
1033             rwy->addToDepartureCue(ref);
1034         }
1035
1036         //cerr << ref->getTrafficRef()->getCallSign() << " You are number " << rwy->getDepartureCueSize() <<  " for takeoff " << endl;
1037     } else {
1038         i->setPositionAndHeading(lat, lon, heading, speed, alt);
1039     }
1040 }
1041
1042 void FGTowerController::updateAircraftInformation(int id, double lat, double lon,
1043                                                   double heading, double speed, double alt,
1044                                                   double dt)
1045 {
1046     TrafficVectorIterator i = activeTraffic.begin();
1047     // Search whether the current id has an entry
1048     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1049     TrafficVectorIterator current, closest;
1050     if (activeTraffic.size()) {
1051         //while ((i->getId() != id) && i != activeTraffic.end()) {
1052         while (i != activeTraffic.end()) {
1053             if (i->getId() == id) {
1054                 break;
1055             }
1056             i++;
1057         }
1058     }
1059 //    // update position of the current aircraft
1060     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1061         SG_LOG(SG_GENERAL, SG_ALERT,
1062                "AI error: updating aircraft without traffic record");
1063     } else {
1064         i->setPositionAndHeading(lat, lon, heading, speed, alt);
1065         current = i;
1066     }
1067     setDt(getDt() + dt);
1068
1069     // see if we already have a clearance record for the currently active runway
1070     // NOTE: dd. 2011-08-07: Because the active runway has been constructed in the announcePosition function, we may safely assume that is
1071     // already exists here. So, we can simplify the current code. 
1072     ActiveRunwayVecIterator rwy = activeRunways.begin();
1073     while (rwy != activeRunways.end()) {
1074         if (rwy->getRunwayName() == current->getRunway()) {
1075             break;
1076         }
1077         rwy++;
1078     }
1079
1080     // only bother running the following code if the current aircraft is the
1081     // first in line for depature
1082     /* if (current->getAircraft() == rwy->getFirstAircraftInDepartureCue()) {
1083         if (rwy->getCleared()) {
1084             if (id == rwy->getCleared()) {
1085                 current->setHoldPosition(false);
1086             } else {
1087                 current->setHoldPosition(true);
1088             }
1089         } else {
1090             // For now. At later stages, this will probably be the place to check for inbound traffc.
1091             rwy->setCleared(id);
1092         }
1093     } */
1094     // only bother with aircraft that have a takeoff status of 2, since those are essentially under tower control
1095     if (current->getAircraft()->getTakeOffStatus() == 2) {
1096         current->setHoldPosition(true);
1097         int clearanceId = rwy->getCleared();
1098         if (clearanceId) {
1099             if (id == clearanceId) {
1100                 current->setHoldPosition(false);
1101             }
1102         } else {
1103             if (current->getAircraft() == rwy->getFirstAircraftInDepartureCue()) {
1104                 rwy->setCleared(id);
1105             }
1106         }
1107     }
1108 }
1109
1110
1111 void FGTowerController::signOff(int id)
1112 {
1113     TrafficVectorIterator i = activeTraffic.begin();
1114     // Search search if the current id alread has an entry
1115     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1116     if (activeTraffic.size()) {
1117         //while ((i->getId() != id) && i != activeTraffic.end()) {
1118         while (i != activeTraffic.end()) {
1119             if (i->getId() == id) {
1120                 break;
1121             }
1122             i++;
1123         }
1124     }
1125     // If this aircraft has left the runway, we can clear the departure record for this runway
1126     ActiveRunwayVecIterator rwy = activeRunways.begin();
1127     if (activeRunways.size()) {
1128         //while ((rwy->getRunwayName() != i->getRunway()) && (rwy != activeRunways.end())) {
1129         while (rwy != activeRunways.end()) {
1130             if (rwy->getRunwayName() == i->getRunway()) {
1131                 break;
1132             }
1133             rwy++;
1134         }
1135         if (rwy != activeRunways.end()) {
1136             rwy->setCleared(0);
1137             rwy->updateDepartureCue();
1138         } else {
1139             SG_LOG(SG_GENERAL, SG_ALERT,
1140                    "AI error: Attempting to erase non-existing runway clearance record in FGTowerController::signoff");
1141         }
1142     }
1143     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1144         SG_LOG(SG_GENERAL, SG_ALERT,
1145                "AI error: Aircraft without traffic record is signing off from tower");
1146     } else {
1147         i->getAircraft()->resetTakeOffStatus();
1148         i = activeTraffic.erase(i);
1149         //cerr << "Signing off from tower controller" << endl;
1150     }
1151 }
1152
1153 // NOTE:
1154 // IF WE MAKE TRAFFICRECORD A MEMBER OF THE BASE CLASS
1155 // THE FOLLOWING THREE FUNCTIONS: SIGNOFF, HAS INSTRUCTION AND GETINSTRUCTION CAN 
1156 // BECOME DEVIRTUALIZED AND BE A MEMBER OF THE BASE ATCCONTROLLER CLASS
1157 // WHICH WOULD SIMPLIFY CODE MAINTENANCE.
1158 // Note that this function is probably obsolete
1159 bool FGTowerController::hasInstruction(int id)
1160 {
1161     TrafficVectorIterator i = activeTraffic.begin();
1162     // Search search if the current id has an entry
1163     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1164     if (activeTraffic.size()) {
1165         //while ((i->getId() != id) && i != activeTraffic.end()) {
1166         while (i != activeTraffic.end()) {
1167             if (i->getId() == id) {
1168                 break;
1169             }
1170             i++;
1171         }
1172     }
1173     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1174         SG_LOG(SG_GENERAL, SG_ALERT,
1175                "AI error: checking ATC instruction for aircraft without traffic record");
1176     } else {
1177         return i->hasInstruction();
1178     }
1179     return false;
1180 }
1181
1182
1183 FGATCInstruction FGTowerController::getInstruction(int id)
1184 {
1185     TrafficVectorIterator i = activeTraffic.begin();
1186     // Search search if the current id has an entry
1187     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1188     if (activeTraffic.size()) {
1189         //while ((i->getId() != id) && i != activeTraffic.end()) {
1190         while (i != activeTraffic.end()) {
1191             if (i->getId() == id) {
1192                 break;
1193             }
1194             i++;
1195         }
1196     }
1197     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1198         SG_LOG(SG_GENERAL, SG_ALERT,
1199                "AI error: requesting ATC instruction for aircraft without traffic record");
1200     } else {
1201         return i->getInstruction();
1202     }
1203     return FGATCInstruction();
1204 }
1205
1206 void FGTowerController::render(bool visible) {
1207     //cerr << "FGTowerController::render function not yet implemented" << endl;
1208 }
1209
1210 string FGTowerController::getName() {
1211     return string(parent->getId() + "-tower");
1212 }
1213
1214
1215
1216 /***************************************************************************
1217  * class FGStartupController
1218  *
1219  **************************************************************************/
1220 FGStartupController::FGStartupController(FGAirportDynamics *par):
1221     FGATCController()
1222 {
1223     parent = par;
1224 }
1225
1226 void FGStartupController::announcePosition(int id,
1227                                            FGAIFlightPlan * intendedRoute,
1228                                            int currentPosition, double lat,
1229                                            double lon, double heading,
1230                                            double speed, double alt,
1231                                            double radius, int leg,
1232                                            FGAIAircraft * ref)
1233 {
1234     init();
1235     TrafficVectorIterator i = activeTraffic.begin();
1236     // Search whether the current id alread has an entry
1237     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1238     if (activeTraffic.size()) {
1239         //while ((i->getId() != id) && i != activeTraffic.end()) {
1240         while (i != activeTraffic.end()) {
1241             if (i->getId() == id) {
1242                 break;
1243             }
1244             i++;
1245         }
1246     }
1247     // Add a new TrafficRecord if no one exsists for this aircraft.
1248     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1249         FGTrafficRecord rec;
1250         rec.setId(id);
1251
1252         rec.setPositionAndHeading(lat, lon, heading, speed, alt);
1253         rec.setRunway(intendedRoute->getRunway());
1254         rec.setLeg(leg);
1255         rec.setPositionAndIntentions(currentPosition, intendedRoute);
1256         //rec.setCallSign(callsign);
1257         rec.setAircraft(ref);
1258         rec.setHoldPosition(true);
1259         activeTraffic.push_back(rec);
1260     } else {
1261         i->setPositionAndIntentions(currentPosition, intendedRoute);
1262         i->setPositionAndHeading(lat, lon, heading, speed, alt);
1263
1264     }
1265 }
1266
1267 // NOTE:
1268 // IF WE MAKE TRAFFICRECORD A MEMBER OF THE BASE CLASS
1269 // THE FOLLOWING THREE FUNCTIONS: SIGNOFF, HAS INSTRUCTION AND GETINSTRUCTION CAN 
1270 // BECOME DEVIRTUALIZED AND BE A MEMBER OF THE BASE ATCCONTROLLER CLASS
1271 // WHICH WOULD SIMPLIFY CODE MAINTENANCE.
1272 // Note that this function is probably obsolete
1273 bool FGStartupController::hasInstruction(int id)
1274 {
1275     TrafficVectorIterator i = activeTraffic.begin();
1276     // Search search if the current id has an entry
1277     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1278     if (activeTraffic.size()) {
1279         //while ((i->getId() != id) && i != activeTraffic.end()) {
1280         while (i != activeTraffic.end()) {
1281             if (i->getId() == id) {
1282                 break;
1283             }
1284             i++;
1285         }
1286     }
1287     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1288         SG_LOG(SG_GENERAL, SG_ALERT,
1289                "AI error: checking ATC instruction for aircraft without traffic record");
1290     } else {
1291         return i->hasInstruction();
1292     }
1293     return false;
1294 }
1295
1296
1297 FGATCInstruction FGStartupController::getInstruction(int id)
1298 {
1299     TrafficVectorIterator i = activeTraffic.begin();
1300     // Search search if the current id has an entry
1301     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1302     if (activeTraffic.size()) {
1303         //while ((i->getId() != id) && i != activeTraffic.end()) {
1304         while (i != activeTraffic.end()) {
1305             if (i->getId() == id) {
1306                 break;
1307             }
1308             i++;
1309         }
1310     }
1311     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1312         SG_LOG(SG_GENERAL, SG_ALERT,
1313                "AI error: requesting ATC instruction for aircraft without traffic record");
1314     } else {
1315         return i->getInstruction();
1316     }
1317     return FGATCInstruction();
1318 }
1319
1320 void FGStartupController::signOff(int id)
1321 {
1322     TrafficVectorIterator i = activeTraffic.begin();
1323     // Search search if the current id alread has an entry
1324     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1325     if (activeTraffic.size()) {
1326         //while ((i->getId() != id) && i != activeTraffic.end()) {
1327         while (i != activeTraffic.end()) {
1328             if (i->getId() == id) {
1329                 break;
1330             }
1331             i++;
1332         }
1333     }
1334     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1335         SG_LOG(SG_GENERAL, SG_ALERT,
1336                "AI error: Aircraft without traffic record is signing off from tower");
1337     } else {
1338         //cerr << i->getAircraft()->getCallSign() << " signing off from startupcontroller" << endl;
1339         i = activeTraffic.erase(i);
1340     }
1341 }
1342
1343 bool FGStartupController::checkTransmissionState(int st, time_t now, time_t startTime, TrafficVectorIterator i, AtcMsgId msgId,
1344                                AtcMsgDir msgDir)
1345 {
1346     int state = i->getState();
1347     if ((state == st) && available) {
1348         if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft(i->getAircraft())) {
1349             
1350             //cerr << "Checking state " << st << " for " << i->getAircraft()->getCallSign() << endl;
1351             static SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
1352             int n = trans_num->getIntValue();
1353             if (n == 0) {
1354                 trans_num->setIntValue(-1);
1355                  // PopupCallback(n);
1356                  //cerr << "Selected transmission message " << n << endl;
1357                  FGATCManager *atc = (FGATCManager*) globals->get_subsystem("atc");
1358                  atc->getATCDialog()->removeEntry(1);
1359             } else {
1360                 //cerr << "creading message for " << i->getAircraft()->getCallSign() << endl;
1361                 transmit(&(*i), &(*parent), msgId, msgDir, false);
1362                 return false;
1363             }
1364         }
1365         if (now > startTime) {
1366             //cerr << "Transmitting startup msg" << endl;
1367             transmit(&(*i), &(*parent), msgId, msgDir, true);
1368             i->updateState();
1369             lastTransmission = now;
1370             available = false;
1371             return true;
1372         }
1373     }
1374     return false;
1375 }
1376
1377 void FGStartupController::updateAircraftInformation(int id, double lat, double lon,
1378                                                     double heading, double speed, double alt,
1379                                                     double dt)
1380 {
1381     TrafficVectorIterator i = activeTraffic.begin();
1382     // Search search if the current id has an entry
1383     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1384     TrafficVectorIterator current, closest;
1385     if (activeTraffic.size()) {
1386         //while ((i->getId() != id) && i != activeTraffic.end()) {
1387         while (i != activeTraffic.end()) {
1388             if (i->getId() == id) {
1389                 break;
1390             }
1391             i++;
1392         }
1393     }
1394 //    // update position of the current aircraft
1395
1396     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1397         SG_LOG(SG_GENERAL, SG_ALERT,
1398                "AI error: updating aircraft without traffic record");
1399     } else {
1400         i->setPositionAndHeading(lat, lon, heading, speed, alt);
1401         current = i;
1402     }
1403     setDt(getDt() + dt);
1404
1405     int state = i->getState();
1406
1407     // The user controlled aircraft should have crased here, because it doesn't have a traffic reference. 
1408     // NOTE: if we create a traffic schedule for the user aircraft, we can use this to plan a flight.
1409     time_t startTime = i->getAircraft()->getTrafficRef()->getDepartureTime();
1410     time_t now = time(NULL) + fgGetLong("/sim/time/warp");
1411     //cerr << i->getAircraft()->getTrafficRef()->getCallSign() 
1412     //     << " is scheduled to depart in " << startTime-now << " seconds. Available = " << available
1413     //     << " at parking " << getGateName(i->getAircraft()) << endl;
1414
1415     if ((now - lastTransmission) > 3 + (rand() % 15)) {
1416         available = true;
1417     }
1418
1419     checkTransmissionState(0, now, (startTime + 0  ), i, MSG_ANNOUNCE_ENGINE_START,                     ATC_AIR_TO_GROUND);
1420     checkTransmissionState(1, now, (startTime + 60 ), i, MSG_REQUEST_ENGINE_START,                      ATC_AIR_TO_GROUND);
1421     checkTransmissionState(2, now, (startTime + 80 ), i, MSG_PERMIT_ENGINE_START,                       ATC_GROUND_TO_AIR);
1422     checkTransmissionState(3, now, (startTime + 100), i, MSG_ACKNOWLEDGE_ENGINE_START,                  ATC_AIR_TO_GROUND);
1423     if (checkTransmissionState(4, now, (startTime + 130), i, MSG_ACKNOWLEDGE_SWITCH_GROUND_FREQUENCY,       ATC_AIR_TO_GROUND)) {
1424         i->nextFrequency();
1425     }
1426     checkTransmissionState(5, now, (startTime + 140), i, MSG_INITIATE_CONTACT,                          ATC_AIR_TO_GROUND);
1427     checkTransmissionState(6, now, (startTime + 150), i, MSG_ACKNOWLEDGE_INITIATE_CONTACT,              ATC_GROUND_TO_AIR);
1428     checkTransmissionState(7, now, (startTime + 180), i, MSG_REQUEST_PUSHBACK_CLEARANCE,                ATC_AIR_TO_GROUND);
1429
1430
1431    
1432     if ((state == 8) && available) {
1433         if (now > startTime + 200) {
1434             if (i->pushBackAllowed()) {
1435                 i->allowRepeatedTransmissions();
1436                 transmit(&(*i), &(*parent), MSG_PERMIT_PUSHBACK_CLEARANCE,
1437                          ATC_GROUND_TO_AIR, true);
1438                 i->updateState();
1439             } else {
1440                 transmit(&(*i), &(*parent), MSG_HOLD_PUSHBACK_CLEARANCE,
1441                          ATC_GROUND_TO_AIR, true);
1442                 i->suppressRepeatedTransmissions();
1443             }
1444             lastTransmission = now;
1445             available = false;
1446         }
1447     }
1448     if ((state == 9) && available) {
1449         i->setHoldPosition(false);
1450     }
1451 }
1452
1453 // Note that this function is copied from simgear. for maintanance purposes, it's probabtl better to make a general function out of that.
1454 static void WorldCoordinate(osg::Matrix& obj_pos, double lat,
1455                             double lon, double elev, double hdg, double slope)
1456 {
1457     SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
1458     obj_pos = geod.makeZUpFrame();
1459     // hdg is not a compass heading, but a counter-clockwise rotation
1460     // around the Z axis
1461     obj_pos.preMult(osg::Matrix::rotate(hdg * SGD_DEGREES_TO_RADIANS,
1462                                         0.0, 0.0, 1.0));
1463     obj_pos.preMult(osg::Matrix::rotate(slope * SGD_DEGREES_TO_RADIANS,
1464                                         0.0, 1.0, 0.0));
1465 }
1466
1467
1468 void FGStartupController::render(bool visible)
1469 {
1470
1471     SGMaterialLib *matlib = globals->get_matlib();
1472     if (group) {
1473         //int nr = ;
1474         globals->get_scenery()->get_scene_graph()->removeChild(group);
1475         //while (group->getNumChildren()) {
1476         //  cerr << "Number of children: " << group->getNumChildren() << endl;
1477         //simgear::EffectGeode* geode = (simgear::EffectGeode*) group->getChild(0);
1478           //osg::MatrixTransform *obj_trans = (osg::MatrixTransform*) group->getChild(0);
1479            //geode->releaseGLObjects();
1480            //group->removeChild(geode);
1481            //delete geode;
1482         group = 0;
1483     }
1484     if (visible) {
1485         group = new osg::Group;
1486         FGScenery * local_scenery = globals->get_scenery();
1487         double elevation_meters = 0.0;
1488         double elevation_feet = 0.0;
1489
1490
1491         //for ( FGTaxiSegmentVectorIterator i = segments.begin(); i != segments.end(); i++) {
1492         double dx = 0;
1493         for   (TrafficVectorIterator i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
1494             // Handle start point
1495             int pos = i->getCurrentPosition();
1496             //cerr << "rendering for " << i->getAircraft()->getCallSign() << "pos = " << pos << endl;
1497             if (pos > 0) {
1498                 FGTaxiSegment *segment  = parent->getGroundNetwork()->findSegment(pos);
1499                 SGGeod start(SGGeod::fromDeg((i->getLongitude()), (i->getLatitude())));
1500                 SGGeod end  (SGGeod::fromDeg(segment->getEnd()->getLongitude(), segment->getEnd()->getLatitude()));
1501
1502                 double length = SGGeodesy::distanceM(start, end);
1503                 //heading = SGGeodesy::headingDeg(start->getGeod(), end->getGeod());
1504
1505                 double az2, heading; //, distanceM;
1506                 SGGeodesy::inverse(start, end, heading, az2, length);
1507                 double coveredDistance = length * 0.5;
1508                 SGGeod center;
1509                 SGGeodesy::direct(start, heading, coveredDistance, center, az2);
1510                 //cerr << "Active Aircraft : Centerpoint = (" << center.getLatitudeDeg() << ", " << center.getLongitudeDeg() << "). Heading = " << heading << endl;
1511                 ///////////////////////////////////////////////////////////////////////////////
1512                 // Make a helper function out of this
1513                 osg::Matrix obj_pos;
1514                 osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
1515                 obj_trans->setDataVariance(osg::Object::STATIC);
1516                 // Experimental: Calculate slope here, based on length, and the individual elevations
1517                 double elevationStart;
1518                 if (isUserAircraft((i)->getAircraft())) {
1519                     elevationStart = fgGetDouble("/position/ground-elev-m");
1520                 } else {
1521                     elevationStart = ((i)->getAircraft()->_getAltitude()); 
1522                 }
1523                 double elevationEnd   = segment->getEnd()->getElevation();
1524                 if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
1525                     SGGeod center2 = end;
1526                     center2.setElevationM(SG_MAX_ELEVATION_M);
1527                     if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
1528                         elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
1529                             //elevation_meters += 0.5;
1530                     }
1531                     else { 
1532                         elevationEnd = parent->getElevation();
1533                     }
1534                     segment->getEnd()->setElevation(elevationEnd);
1535                 }
1536
1537                 double elevationMean  = (elevationStart + elevationEnd) / 2.0;
1538                 double elevDiff       = elevationEnd - elevationStart;
1539                
1540                double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
1541                 
1542                //cerr << "1. Using mean elevation : " << elevationMean << " and " << slope << endl;
1543
1544                 WorldCoordinate( obj_pos, center.getLatitudeDeg(), center.getLongitudeDeg(), elevationMean + 0.5, -(heading), slope );
1545 ;
1546
1547                 obj_trans->setMatrix( obj_pos );
1548                 //osg::Vec3 center(0, 0, 0)
1549
1550                 float width = length /2.0;
1551                 osg::Vec3 corner(-width, 0, 0.25f);
1552                 osg::Vec3 widthVec(2*width + 1, 0, 0);
1553                 osg::Vec3 heightVec(0, 1, 0);
1554                 osg::Geometry* geometry;
1555                 geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
1556                 simgear::EffectGeode* geode = new simgear::EffectGeode;
1557                 geode->setName("test");
1558                 geode->addDrawable(geometry);
1559                 //osg::Node *custom_obj;
1560                 SGMaterial *mat = matlib->find("UnidirectionalTaper");
1561                 if (mat)
1562                     geode->setEffect(mat->get_effect());
1563                 obj_trans->addChild(geode);
1564                 // wire as much of the scene graph together as we can
1565                 //->addChild( obj_trans );
1566                 group->addChild( obj_trans );
1567                 /////////////////////////////////////////////////////////////////////
1568             } else {
1569                 //cerr << "BIG FAT WARNING: current position is here : " << pos << endl;
1570             }
1571             for(intVecIterator j = (i)->getIntentions().begin(); j != (i)->getIntentions().end(); j++) {
1572                 osg::Matrix obj_pos;
1573                 int k = (*j);
1574                 if (k > 0) {
1575                     //cerr << "rendering for " << i->getAircraft()->getCallSign() << "intention = " << k << endl;
1576                     osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
1577                     obj_trans->setDataVariance(osg::Object::STATIC);
1578                     FGTaxiSegment *segment  = parent->getGroundNetwork()->findSegment(k);
1579
1580                     double elevationStart = segment->getStart()->getElevation();
1581                     double elevationEnd   = segment->getEnd  ()->getElevation();
1582                     if ((elevationStart == 0) || (elevationStart == parent->getElevation())) {
1583                         SGGeod center2 = segment->getStart()->getGeod();
1584                         center2.setElevationM(SG_MAX_ELEVATION_M);
1585                         if (local_scenery->get_elevation_m( center2, elevationStart, NULL )) {
1586                             elevation_feet = elevationStart * SG_METER_TO_FEET + 0.5;
1587                             //elevation_meters += 0.5;
1588                         }
1589                         else { 
1590                             elevationStart = parent->getElevation();
1591                         }
1592                         segment->getStart()->setElevation(elevationStart);
1593                     }
1594                     if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
1595                         SGGeod center2 = segment->getEnd()->getGeod();
1596                         center2.setElevationM(SG_MAX_ELEVATION_M);
1597                         if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
1598                             elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
1599                             //elevation_meters += 0.5;
1600                         }
1601                         else { 
1602                             elevationEnd = parent->getElevation();
1603                         }
1604                         segment->getEnd()->setElevation(elevationEnd);
1605                     }
1606  
1607                     double elevationMean  = (elevationStart + elevationEnd) / 2.0;
1608                     double elevDiff       = elevationEnd - elevationStart;
1609                     double length         = segment->getLength();
1610                     double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
1611                 
1612                     //cerr << "2. Using mean elevation : " << elevationMean << " and " << slope << endl;
1613
1614
1615                     WorldCoordinate( obj_pos, segment->getLatitude(), segment->getLongitude(), elevationMean + 0.5, -(segment->getHeading()), slope );
1616
1617                     //WorldCoordinate( obj_pos, segment->getLatitude(), segment->getLongitude(), parent->getElevation()+8+dx, -(segment->getHeading()) );
1618
1619                     obj_trans->setMatrix( obj_pos );
1620                     //osg::Vec3 center(0, 0, 0)
1621
1622                     float width = segment->getLength() /2.0;
1623                     osg::Vec3 corner(-width, 0, 0.25f);
1624                     osg::Vec3 widthVec(2*width + 1, 0, 0);
1625                     osg::Vec3 heightVec(0, 1, 0);
1626                     osg::Geometry* geometry;
1627                     geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
1628                     simgear::EffectGeode* geode = new simgear::EffectGeode;
1629                     geode->setName("test");
1630                     geode->addDrawable(geometry);
1631                     //osg::Node *custom_obj;
1632                     SGMaterial *mat = matlib->find("UnidirectionalTaper");
1633                     if (mat)
1634                         geode->setEffect(mat->get_effect());
1635                     obj_trans->addChild(geode);
1636                     // wire as much of the scene graph together as we can
1637                     //->addChild( obj_trans );
1638                     group->addChild( obj_trans );
1639                 } else {
1640                     //cerr << "BIG FAT WARNING: k is here : " << pos << endl;
1641                 }
1642             }
1643             //dx += 0.1;
1644         }
1645         globals->get_scenery()->get_scene_graph()->addChild(group);
1646     }
1647 }
1648
1649 string FGStartupController::getName() {
1650     return string(parent->getId() + "-startup");
1651 }
1652
1653
1654 /***************************************************************************
1655  * class FGApproachController
1656  *
1657  **************************************************************************/
1658 FGApproachController::FGApproachController(FGAirportDynamics *par):
1659 FGATCController()
1660 {
1661     parent = par;
1662 }
1663
1664 // 
1665 void FGApproachController::announcePosition(int id,
1666                                             FGAIFlightPlan * intendedRoute,
1667                                             int currentPosition,
1668                                             double lat, double lon,
1669                                             double heading, double speed,
1670                                             double alt, double radius,
1671                                             int leg, FGAIAircraft * ref)
1672 {
1673     init();
1674     TrafficVectorIterator i = activeTraffic.begin();
1675     // Search whether the current id alread has an entry
1676     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1677     if (activeTraffic.size()) {
1678         //while ((i->getId() != id) && i != activeTraffic.end()) {
1679         while (i != activeTraffic.end()) {
1680             if (i->getId() == id) {
1681                 break;
1682             }
1683             i++;
1684         }
1685     }
1686     // Add a new TrafficRecord if no one exsists for this aircraft.
1687     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1688         FGTrafficRecord rec;
1689         rec.setId(id);
1690
1691         rec.setPositionAndHeading(lat, lon, heading, speed, alt);
1692         rec.setRunway(intendedRoute->getRunway());
1693         rec.setLeg(leg);
1694         //rec.setCallSign(callsign);
1695         rec.setAircraft(ref);
1696         activeTraffic.push_back(rec);
1697     } else {
1698         i->setPositionAndHeading(lat, lon, heading, speed, alt);
1699     }
1700 }
1701
1702 void FGApproachController::updateAircraftInformation(int id, double lat, double lon,
1703                                                      double heading, double speed, double alt,
1704                                                      double dt)
1705 {
1706     TrafficVectorIterator i = activeTraffic.begin();
1707     // Search search if the current id has an entry
1708     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1709     TrafficVectorIterator current, closest;
1710     if (activeTraffic.size()) {
1711         //while ((i->getId() != id) && i != activeTraffic.end()) {
1712         while (i != activeTraffic.end()) {
1713             if (i->getId() == id) {
1714                 break;
1715             }
1716             i++;
1717         }
1718     }
1719 //    // update position of the current aircraft
1720     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1721         SG_LOG(SG_GENERAL, SG_ALERT,
1722                "AI error: updating aircraft without traffic record");
1723     } else {
1724         i->setPositionAndHeading(lat, lon, heading, speed, alt);
1725         current = i;
1726         //cerr << "ApproachController: checking for speed" << endl;
1727         time_t time_diff =
1728             current->getAircraft()->
1729             checkForArrivalTime(string("final001"));
1730         if (time_diff > 15) {
1731             current->setSpeedAdjustment(current->getAircraft()->
1732                                         getPerformance()->vDescent() *
1733                                         1.35);
1734         } else if (time_diff > 5) {
1735             current->setSpeedAdjustment(current->getAircraft()->
1736                                         getPerformance()->vDescent() *
1737                                         1.2);
1738         } else if (time_diff < -15) {
1739             current->setSpeedAdjustment(current->getAircraft()->
1740                                         getPerformance()->vDescent() *
1741                                         0.65);
1742         } else if (time_diff < -5) {
1743             current->setSpeedAdjustment(current->getAircraft()->
1744                                         getPerformance()->vDescent() *
1745                                         0.8);
1746         } else {
1747             current->clearSpeedAdjustment();
1748         }
1749         //current->setSpeedAdjustment(current->getAircraft()->getPerformance()->vDescent() + time_diff);
1750     }
1751     setDt(getDt() + dt);
1752 }
1753
1754 void FGApproachController::signOff(int id)
1755 {
1756     TrafficVectorIterator i = activeTraffic.begin();
1757     // Search search if the current id alread has an entry
1758     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1759     if (activeTraffic.size()) {
1760         //while ((i->getId() != id) && i != activeTraffic.end()) {
1761         while (i != activeTraffic.end()) {
1762             if (i->getId() == id) {
1763                 break;
1764             }
1765             i++;
1766         }
1767     }
1768     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1769         SG_LOG(SG_GENERAL, SG_ALERT,
1770                "AI error: Aircraft without traffic record is signing off from approach");
1771     } else {
1772         i = activeTraffic.erase(i);
1773     }
1774 }
1775
1776
1777
1778
1779 bool FGApproachController::hasInstruction(int id)
1780 {
1781     TrafficVectorIterator i = activeTraffic.begin();
1782     // Search search if the current id has an entry
1783     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1784     if (activeTraffic.size()) {
1785         //while ((i->getId() != id) && i != activeTraffic.end()) {
1786         while (i != activeTraffic.end()) {
1787             if (i->getId() == id) {
1788                 break;
1789             }
1790             i++;
1791         }
1792     }
1793     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1794         SG_LOG(SG_GENERAL, SG_ALERT,
1795                "AI error: checking ATC instruction for aircraft without traffic record");
1796     } else {
1797         return i->hasInstruction();
1798     }
1799     return false;
1800 }
1801
1802
1803 FGATCInstruction FGApproachController::getInstruction(int id)
1804 {
1805     TrafficVectorIterator i = activeTraffic.begin();
1806     // Search search if the current id has an entry
1807     // This might be faster using a map instead of a vector, but let's start by taking a safe route
1808     if (activeTraffic.size()) {
1809         //while ((i->getId() != id) && i != activeTraffic.end()) {
1810         while (i != activeTraffic.end()) {
1811             if (i->getId() == id) {
1812                 break;
1813             }
1814             i++;
1815         }
1816     }
1817     if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1818         SG_LOG(SG_GENERAL, SG_ALERT,
1819                "AI error: requesting ATC instruction for aircraft without traffic record");
1820     } else {
1821         return i->getInstruction();
1822     }
1823     return FGATCInstruction();
1824 }
1825
1826
1827 ActiveRunway *FGApproachController::getRunway(string name)
1828 {
1829     ActiveRunwayVecIterator rwy = activeRunways.begin();
1830     if (activeRunways.size()) {
1831         while (rwy != activeRunways.end()) {
1832             if (rwy->getRunwayName() == name) {
1833                 break;
1834             }
1835             rwy++;
1836         }
1837     }
1838     if (rwy == activeRunways.end()) {
1839         ActiveRunway aRwy(name, 0);
1840         activeRunways.push_back(aRwy);
1841         rwy = activeRunways.end() - 1;
1842     }
1843     return &(*rwy);
1844 }
1845
1846 void FGApproachController::render(bool visible) {
1847     //cerr << "FGApproachController::render function not yet implemented" << endl;
1848 }
1849
1850
1851
1852 string FGApproachController::getName() {
1853     return string(parent->getId() + "-approach");
1854 }