FlightGear next
trafficcontrol.cxx
Go to the documentation of this file.
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#include <config.h>
24
25#include <algorithm>
26#include <cstdio>
27#include <random>
28
29#include <osg/Geode>
30#include <osg/Geometry>
31#include <osg/MatrixTransform>
32#include <osg/Shape>
33
34#include <simgear/scene/material/EffectGeode.hxx>
35#include <simgear/scene/material/matlib.hxx>
36#include <simgear/scene/material/mat.hxx>
37#include <simgear/scene/util/OsgMath.hxx>
38#include <simgear/timing/sg_time.hxx>
39#include <simgear/math/sg_geodesy.hxx>
40
41#include <Scenery/scenery.hxx>
42
43#include "trafficcontrol.hxx"
44#include "atc_mgr.hxx"
48#include <ATC/atc_mgr.hxx>
51#include <Airports/dynamics.hxx>
52#include <Airports/airport.hxx>
53#include <Radio/radio.hxx>
54#include <signal.h>
55
56using std::sort;
57using std::string;
58
59/***************************************************************************
60 * ActiveRunway
61 **************************************************************************/
62
63ActiveRunwayQueue::ActiveRunwayQueue(const std::string& apt, const std::string& r, int cc) :
64 icao(apt),rwy(r)
65{
66 SG_LOG(SG_ATC, SG_DEBUG, "ActiveRunway " << icao << "/" << r << " " << cc );
67 currentlyCleared = cc;
68 distanceToFinal = 6.0 * SG_NM_TO_METER;
69};
70
72 SG_LOG(SG_ATC, SG_DEBUG, "Removed from RunwayQueue " << rwy << " " << id );
73 auto it = std::find_if(runwayQueue.begin(), runwayQueue.end(), [id](const SGSharedPtr<FGTrafficRecord> acft) {
74 return acft->getId() == id;
75 });
76 if (it == runwayQueue.end()) {
77 SG_LOG(SG_ATC, SG_WARN, "Erasing non existant aircraft " << rwy << " " << id );
79 return;
80 }
81 runwayQueue.erase(it);
82 setCleared(0);
83}
84
86{
87 SG_LOG(SG_ATC, SG_WARN, "updateDepartureQueue " << runwayQueue.size());
88 runwayQueue.erase(runwayQueue.begin());
89 SG_LOG(SG_ATC, SG_WARN, "updateDepartureQueue " << runwayQueue.size());
90}
91
98void ActiveRunwayQueue::requestTimeSlot(SGSharedPtr<FGTrafficRecord> trafficRecord)
99{
100 time_t eta = trafficRecord->getPlannedArrivalTime();
101 time_t newEta = 0;
102
103 if (get( trafficRecord->getId())==nullptr) {
104 // Push to end. We will resort
105 runwayQueue.push_back(trafficRecord);
106 }
107
108
109 // if the aircraft is the first arrival, add to the vector and return eta directly
110 if (runwayQueue.empty()) {
111 newEta = eta;
112 SG_LOG(SG_ATC, SG_DEBUG, icao << "/" << getRunwayName() << " Checked eta slots, using " << eta);
113 } else {
114 // First check the already assigned slots to see where we need to fit the flight in
115 SG_LOG(SG_ATC, SG_DEBUG, icao << "/" << getRunwayName() << " Checking eta slots " << eta << " : " << runwayQueue.size() << " Timediff : " << (eta - globals->get_time_params()->get_cur_time()));
116
117 // is this needed - just a debug output?
118 std::vector<SGSharedPtr<FGTrafficRecord>>::iterator i;
119 for (i = runwayQueue.begin();
120 i != runwayQueue.end(); ++i) {
121 SG_LOG(SG_ATC, SG_DEBUG, "Stored time : " << (*i)->getPlannedArrivalTime());
122 }
123
124 // if the flight is before the first scheduled slot + SEPARATION
125 i = runwayQueue.begin();
126 if ((eta + SEPARATION) < (*i)->getPlannedArrivalTime()) {
127 newEta = eta;
128 SG_LOG(SG_ATC, SG_DEBUG, "Added to start. New ETA : " << newEta );
129 trafficRecord->setRunwaySlot(newEta);
130 resort();
132 return;
133 }
134
135 // else, look through the rest of the slots
136 bool found = false;
137 while ((i != runwayQueue.end()) && (!found)) {
138 std::vector<SGSharedPtr<FGTrafficRecord>>::iterator j = i + 1;
139
140 // if the flight is after the last scheduled slot check if SEPARATION is needed
141 if (j == runwayQueue.end()) {
142 if (((*i)->getPlannedArrivalTime() + SEPARATION) < eta) {
143 SG_LOG(SG_ATC, SG_DEBUG, "Storing at end");
144 newEta = eta;
145 } else {
146 newEta = (*i)->getPlannedArrivalTime() + SEPARATION;
147 SG_LOG(SG_ATC, SG_DEBUG, "Storing at end + SEPARATION");
148 }
149 SG_LOG(SG_ATC, SG_DEBUG, "End. New ETA : " << newEta << " Timediff : " << (newEta-eta));
150 trafficRecord->setRunwaySlot(newEta);
151 resort();
153 return;
154 } else {
155 // potential slot found
156 // check the distance between the previous and next slots
157 // distance must be greater than 2* SEPARATION
158 if ((((*j)->getPlannedArrivalTime() - (*i)->getPlannedArrivalTime()) > (SEPARATION * 2))) {
159 // now check whether this slot is usable:
160 // eta should fall between the two points
161 // i.e. eta > i AND eta < j
162 SG_LOG(SG_ATC, SG_DEBUG, "Found potential slot after " << (*i));
163 if (eta > (*i)->getPlannedArrivalTime() && (eta < (*j)->getPlannedArrivalTime())) {
164 found = true;
165 if (eta < ((*i)->getPlannedArrivalTime() + SEPARATION)) {
166 newEta = (*i)->getPlannedArrivalTime() + SEPARATION;
167 SG_LOG(SG_ATC, SG_DEBUG, "Using original" << (*i)->getPlannedArrivalTime() << " + SEPARATION ");
168 } else {
169 newEta = eta;
170 SG_LOG(SG_ATC, SG_DEBUG, "Using original after " << (*i)->getPlannedArrivalTime());
171 }
172 } else if (eta < (*i)->getPlannedArrivalTime()) {
173 found = true;
174 newEta = (*i)->getPlannedArrivalTime() + SEPARATION;
175 SG_LOG(SG_ATC, SG_DEBUG, "Using delayed slot after " << (*i)->getPlannedArrivalTime());
176 }
177 /*
178 if (((*j) - SEPARATION) < eta) {
179 found = true;
180 if (((*i) + SEPARATION) < eta) {
181 newEta = eta;
182 SG_LOG(SG_ATC, SG_BULK, "Using original after " << (*i));
183 } else {
184 newEta = (*i) + SEPARATION;
185 SG_LOG(SG_ATC, SG_BULK, "Using " << (*i) << " + SEPARATION ");
186 }
187 } */
188 }
189 }
190 ++i;
191 }
192 }
193
194 SG_LOG(SG_ATC, SG_DEBUG, "Done. New ETA : " << newEta << " " << rwy << " Size : " << runwayQueue.size() << " " << trafficRecord->getCallsign() );
195 trafficRecord->setRunwaySlot(newEta);
196 resort();
198}
199
206void ActiveRunwayQueue::updateFirst(SGSharedPtr<FGTrafficRecord> trafficRecord, time_t newETA)
207{
208 time_t eta = trafficRecord->getPlannedArrivalTime();
209 time_t now = globals->get_time_params()->get_cur_time();
210
211 newETA = std::max(newETA, now);
212
213 SG_LOG(SG_ATC, SG_DEBUG, "Update First " << eta << " " << newETA << " " << now << " " << rwy << " Leg " << trafficRecord->getLeg() << " Size : " << runwayQueue.size() << " " << trafficRecord->getCallsign() );
214
215 time_t diff = 0;
216
217 for (SGSharedPtr<FGTrafficRecord> queueRecord: runwayQueue) {
218 if (trafficRecord->getId() == queueRecord->getId()) {
219 diff = newETA - eta;
220 diff = std::max((time_t)0, diff);
221 SG_LOG(SG_ATC, SG_DEBUG, queueRecord->getCallsign() << "(" << queueRecord->getId() << ")" << " Diff " << diff );
222 trafficRecord->setPlannedArrivalTime(newETA);
223 queueRecord->setRunwaySlot(queueRecord->getRunwaySlot() + diff);
224 } else {
225 queueRecord->setRunwaySlot(queueRecord->getRunwaySlot() + diff);
226 SG_LOG(SG_ATC, SG_DEBUG, queueRecord->getCallsign() << "(" << queueRecord->getId() << ")" << " Diff " << diff );
227 }
228 }
230}
231
234{
235 time_t now = globals->get_time_params()->get_cur_time();
236
237 SG_LOG(SG_ATC, SG_DEBUG, "Runway Queue for " << icao << "/" << rwy << " Size : " << runwayQueue.size());
238 for (auto acft : runwayQueue) {
239 SG_LOG(SG_ATC, SG_DEBUG, " " << acft->getCallsign() << "(" << acft->getId() << ") Leg : " << acft->getLeg() << " Diff : " << acft->getRunwaySlot() - now << " " << acft->getRunwaySlot() << " " << acft->getPlannedArrivalTime() << " " << acft->getPos().getLatitudeDeg() << " " << acft->getPos().getLongitudeDeg() << " Speed " << acft->getSpeed() << " Elevation " << acft->getPos().getElevationM());
240 }
241
242}
243
245const SGSharedPtr<FGTrafficRecord>ActiveRunwayQueue::get(int id) const
246{
247 auto it = std::find_if(runwayQueue.begin(), runwayQueue.end(), [id](const SGSharedPtr<FGTrafficRecord> acft) {
248 return acft->getId() == id;
249 });
250
251 if (it == runwayQueue.end()) {
252 return nullptr;
253 }
254
255 return *it;
256}
257
259const SGSharedPtr<FGTrafficRecord>ActiveRunwayQueue::getFirstOfStatus(int stat) const
260{
261 auto it = std::find_if(runwayQueue.begin(), runwayQueue.end(), [stat](const SGSharedPtr<FGTrafficRecord> acft) {
262 return acft->getTakeOffStatus() == stat;
263 });
264
265 if (it == runwayQueue.end()) {
266 return {};
267 }
268
269 return *it;
270}
271
272const SGSharedPtr<FGTrafficRecord> ActiveRunwayQueue::getFirstAircraftInDepartureQueue() const
273{
274 // printRunwayQueue();
275 if (runwayQueue.empty()) {
276 return nullptr;
277 }
278
279 return *runwayQueue.begin();
280};
281
282void ActiveRunwayQueue::addToQueue(SGSharedPtr<FGTrafficRecord> ac)
283{
284 assert(ac);
285 assert(!ac->getAircraft());
286 assert(!ac->getAircraft()->getDie());
287 ac->setTakeOffStatus(AITakeOffStatus::QUEUED);
288 runwayQueue.push_back(std::move(ac));
290};
291
292
293/***************************************************************************
294 * FGTrafficRecord
295 **************************************************************************/
296
298 id(0),
299 currentPos(0),
300 leg(0),
301 frequencyId(0),
302 state(0),
303 allowTransmission(true),
304 allowPushback(true),
305 priority(0),
306 timer(0),
307 heading(0), speed(0), altitude(0), radius(0)
308{
309}
310
314
316 FGAIFlightPlan * route)
317{
318 SG_LOG(SG_AI, SG_BULK, "Traffic record position: " << pos);
319 currentPos = pos;
320 if (runway=="" && route) {
321 setRunway(route->getRunway());
322 }
323
324}
325
327{
328 aircraft = ref;
329}
330
332 if (!aircraft) {
333 return true;
334 }
335 return aircraft->getDie();
336 }
337
339 if (aircraft) {
340 aircraft->clearATCController();
341 }
342 }
343
345 {
346 if(aircraft.valid()) {
347 return aircraft.ptr();
348 }
349 return 0;
350 }
351
356{
357 bool result = false;
358 SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| checkPositionAndIntentions CurrentPos : " << currentPos << " Other : " << other.currentPos << " Leg : " << leg << " Other Leg : " << other.leg );
359 if (currentPos == other.currentPos && getId() != other.getId() ) {
360 SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| Check Position and intentions: " << other.getCallsign() << " we are on the same taxiway; Index = " << currentPos);
361 int headingTowards = SGGeodesy::courseDeg( other.getPos(), getPos() );
362 int headingDiff = SGMiscd::normalizePeriodic(-180, 180, headingTowards - getHeading() );
363 SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| " << heading << "\t" << headingTowards << "\t" << headingDiff);
364 // getHeading()
365 result = abs(headingDiff) < 89;
366 }
367 // else if (! other.intentions.empty())
368 // {
369 // SG_LOG(SG_ATC, SG_BULK, "Start check 2");
370 // intVecIterator i = other.intentions.begin();
371 // while (!((i == other.intentions.end()) || ((*i) == currentPos)))
372 // i++;
373 // if (i != other.intentions.end()) {
374 // SG_LOG(SG_ATC, SG_BULK, "Check Position and intentions: current matches other.intentions");
375 // result = true;
376 // }
377 else if (!intentions.empty()) {
378 SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| Itentions Size " << intentions.size());
379 intVecIterator i = intentions.begin();
380 //while (!((i == intentions.end()) || ((*i) == other.currentPos)))
381 while (i != intentions.end()) {
382 if ((*i) == other.currentPos) {
383 break;
384 }
385 ++i;
386 }
387 if (i != intentions.end()) {
388 SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| Check Position and intentions: " << other.getCallsign()<< " matches Index = " << (*i));
389 int headingTowards = SGGeodesy::courseDeg( other.getPos(), getPos() );
390 int distanceM = SGGeodesy::distanceM( other.getPos(), getPos() );
391 int headingDiff = SGMiscd::normalizePeriodic(-180, 180, headingTowards - getHeading() );
392 SG_LOG(SG_ATC, SG_BULK, getCallsign() << "| Heading : " << heading << "\t Heading Other->Current" << headingTowards << "\t Heading Diff :" << headingDiff << "\t Distance : " << distanceM);
393 // difference of heading is small and it's actually near
394 result = abs(headingDiff) < 89 && distanceM < 400;
395// result = true;
396 }
397 }
398 return result;
399}
400
401void FGTrafficRecord::setPositionAndHeading(double lat, double lon,
402 double hdg, double spd,
403 double alt, int leg)
404{
405 this->pos = SGGeod::fromDegFt(lon, lat, alt);
406 if (heading != 0 && spd != 0) {
407 headingDiff = SGMiscd::normalizePeriodic(-180, 180, heading - hdg);
408 } else {
409 headingDiff = 0;
410 }
411 heading = hdg;
412 speed = spd;
413 altitude = alt;
414 if (leg>AILeg::UNKNOWN) {
415 this->leg = leg;
416 }
417}
418
420 FGTrafficRecord & other)
421{
423 || (other.checkPositionAndIntentions(*this)))
424 return -1;
425 intVecIterator i, j;
426 int currentTargetNode = 0, otherTargetNode = 0;
427 if (currentPos > 0)
428 currentTargetNode = net->findSegment(currentPos)->getEnd()->getIndex(); // OKAY,...
429 if (other.currentPos > 0)
430 otherTargetNode = net->findSegment(other.currentPos)->getEnd()->getIndex(); // OKAY,...
431 if ((currentTargetNode == otherTargetNode) && currentTargetNode > 0)
432 return currentTargetNode;
433 if (! intentions.empty()) {
434 for (i = intentions.begin(); i != intentions.end(); ++i) {
435 if ((*i) > 0) {
436 if (currentTargetNode ==
437 net->findSegment(*i)->getEnd()->getIndex()) {
438 SG_LOG(SG_ATC, SG_BULK, "Current crosses at " << currentTargetNode);
439 return currentTargetNode;
440 }
441 }
442 }
443 }
444 if (! other.intentions.empty()) {
445 for (i = other.intentions.begin(); i != other.intentions.end(); ++i) {
446 if ((*i) > 0) {
447 if (otherTargetNode ==
448 net->findSegment(*i)->getEnd()->getIndex()) {
449 SG_LOG(SG_ATC, SG_BULK, "Other crosses at " << currentTargetNode);
450 return otherTargetNode;
451 }
452 }
453 }
454 }
455 if (! intentions.empty() && ! other.intentions.empty()) {
456 for (i = intentions.begin(); i != intentions.end(); ++i) {
457 for (j = other.intentions.begin(); j != other.intentions.end(); ++j) {
458 SG_LOG(SG_ATC, SG_BULK, "finding segment " << *i << " and " << *j);
459 if (((*i) > 0) && ((*j) > 0)) {
460 currentTargetNode =
461 net->findSegment(*i)->getEnd()->getIndex();
462 otherTargetNode =
463 net->findSegment(*j)->getEnd()->getIndex();
464 if (currentTargetNode == otherTargetNode) {
465 SG_LOG(SG_ATC, SG_BULK, "Routes will cross at " << currentTargetNode);
466 return currentTargetNode;
467 }
468 }
469 }
470 }
471 }
472 return -1;
473}
474
476 FGTrafficRecord & other)
477{
478 int node = -1, othernode = -1;
479 if (currentPos > 0)
480 node = net->findSegment(currentPos)->getEnd()->getIndex();
481 if (other.currentPos > 0)
482 othernode =
483 net->findSegment(other.currentPos)->getEnd()->getIndex();
484 if ((node == othernode) && (node != -1))
485 return true;
486 if (! other.intentions.empty()) {
487 for (intVecIterator i = other.intentions.begin();
488 i != other.intentions.end(); ++i) {
489 if (*i > 0) {
490 othernode = net->findSegment(*i)->getEnd()->getIndex();
491 if ((node == othernode) && (node > -1))
492 return true;
493 }
494 }
495 }
496 //if (other.currentPos > 0)
497 // othernode = net->findSegment(other.currentPos)->getEnd()->getIndex();
498 //if (! intentions.empty())
499 // {
500 // for (intVecIterator i = intentions.begin(); i != intentions.end(); i++)
501 // {
502 // if (*i > 0)
503 // {
504 // node = net->findSegment(*i)->getEnd()->getIndex();
505 // if ((node == othernode) && (node > -1))
506 // return true;
507 // }
508 // }
509 // }
510 return false;
511}
512
513
515 FGTrafficRecord & other, int node)
516{
517 // Check if current segment is the reverse segment for the other aircraft
518 SG_LOG(SG_ATC, SG_BULK, "Current segment " << currentPos);
519
520 if ((currentPos > 0) && (other.currentPos > 0)) {
521 FGTaxiSegment *opp = net->findSegment(currentPos)->opposite();
522 if (opp) {
523 if (opp->getIndex() == other.currentPos)
524 return true;
525 }
526
527 for (intVecIterator i = intentions.begin(); i != intentions.end(); ++i) {
528 if ((opp = net->findSegment(other.currentPos)->opposite())) {
529 if ((*i) > 0)
530 if (opp->getIndex() ==
531 net->findSegment(*i)->getIndex()) {
532 if (net->findSegment(*i)->getStart()->getIndex() ==
533 node) {
534 {
535 SG_LOG(SG_ATC, SG_BULK, "Found the node " << node);
536 return true;
537 }
538 }
539 }
540 }
541 if (! other.intentions.empty()) {
542 for (intVecIterator j = other.intentions.begin();
543 j != other.intentions.end(); ++j) {
544 SG_LOG(SG_ATC, SG_BULK, "Current segment 1 " << (*i));
545 if ((*i) > 0) {
546 if ((opp = net->findSegment(*i)->opposite())) {
547 if (opp->getIndex() ==
548 net->findSegment(*j)->getIndex()) {
549 SG_LOG(SG_ATC, SG_BULK, "Nodes " << net->findSegment(*i)->getIndex()
550 << " and " << net->findSegment(*j)->getIndex()
551 << " are opposites ");
552 if (net->findSegment(*i)->getStart()->
553 getIndex() == node) {
554 {
555 SG_LOG(SG_ATC, SG_BULK, "Found the node " << node);
556 return true;
557 }
558 }
559 }
560 }
561 }
562 }
563 }
564 }
565 }
566 return false;
567}
568
569bool FGTrafficRecord::isActive(int margin) const
570{
571 if (aircraft->getDie()) {
572 return false;
573 }
574
575 time_t now = globals->get_time_params()->get_cur_time();
576 time_t deptime = aircraft->getTrafficRef()->getDepartureTime();
577 return ((now + margin) > deptime);
578}
579
580
582{
583 instruction.setChangeSpeed(true);
584 instruction.setSpeed(spd);
585}
586
588{
589 instruction.setChangeHeading(true);
590 instruction.setHeading(heading);
591}
592
594{
595 return allowPushback;
596}
597
598
599
600/***************************************************************************
601 * FGATCInstruction
602 *
603 **************************************************************************/
604
606{
607 holdPattern = false;
608 holdPosition = false;
609 changeSpeed = false;
610 changeHeading = false;
611 changeAltitude = false;
612 resolveCircularWait = false;
613
614 speed = 0;
615 heading = 0;
616 alt = 0;
617 waitsForId = 0;
618}
619
621{
622 return (holdPattern || holdPosition || changeSpeed || changeHeading
623 || changeAltitude || resolveCircularWait);
624}
#define i(x)
const SGSharedPtr< FGTrafficRecord > get(int id) const
Fetch the first aircraft in the departure queue with a certain status.
void setCleared(int number)
ActiveRunwayQueue(const std::string &apt, const std::string &r, int cc)
const time_t SEPARATION
Separation between aircraft in seconds.
void updateFirst(SGSharedPtr< FGTrafficRecord > eta, time_t newETA)
Update the first and move all records backwards.
void requestTimeSlot(SGSharedPtr< FGTrafficRecord > eta)
Fetch next slot for the active runway.
const std::string & getRunwayName() const
const SGSharedPtr< FGTrafficRecord > getFirstAircraftInDepartureQueue() const
void printRunwayQueue() const
Output the contents of the departure queue vector nicely formatted.
const SGSharedPtr< FGTrafficRecord > getFirstOfStatus(int stat) const
Fetch the first aircraft in the departure queue with a certain status.
void removeFromQueue(int id)
void addToQueue(SGSharedPtr< FGTrafficRecord > ac)
bool getDie()
Definition AIBase.hxx:508
const std::string & getRunway() const
bool hasInstruction() const
FGTaxiSegment * findSegment(const FGTaxiNode *from, const FGTaxiNode *to) const
Find the taxiway segment joining two (ground-net) nodes.
FGTaxiNodeRef getEnd() const
FGTaxiSegment * opposite()
int getIndex() const
FGTaxiNodeRef getStart() const
void setHeadingAdjustment(double heading)
bool checkPositionAndIntentions(FGTrafficRecord &other)
Check if another aircraft is ahead of the current one, and on the same taxiway.
void setPositionAndHeading(double lat, double lon, double hdg, double spd, double alt, int leg)
void setSpeedAdjustment(double spd)
bool isActive(int margin) const
void setPositionAndIntentions(int pos, FGAIFlightPlan *route)
bool pushBackAllowed() const
bool isOpposing(FGGroundNetwork *, FGTrafficRecord &other, int node)
int crosses(FGGroundNetwork *, FGTrafficRecord &other)
FGAIAircraft * getAircraft() const
double getHeading() const
const std::string & getCallsign() const
virtual ~FGTrafficRecord()
bool isDead() const
void setAircraft(FGAIAircraft *ref)
void clearATCController() const
bool onRoute(FGGroundNetwork *, FGTrafficRecord &other)
void setRunway(const std::string &rwy)
FGGlobals * globals
Definition globals.cxx:142
std::vector< int >::iterator intVecIterator