1 // groundnet.cxx - Implimentation of the FlightGear airport ground handling code
3 // Written by Durk Talsma, started June 2005.
5 // Copyright (C) 2004 Durk Talsma.
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.
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.
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.
31 #include <boost/foreach.hpp>
34 #include <osg/Geometry>
35 #include <osg/MatrixTransform>
38 #include <simgear/debug/logstream.hxx>
39 #include <simgear/scene/material/EffectGeode.hxx>
40 #include <simgear/scene/material/matlib.hxx>
41 #include <simgear/scene/material/mat.hxx>
42 #include <simgear/scene/util/OsgMath.hxx>
43 #include <simgear/structure/exception.hxx>
44 #include <simgear/timing/timestamp.hxx>
46 #include <Airports/airport.hxx>
47 #include <Airports/dynamics.hxx>
48 #include <Airports/runways.hxx>
50 #include <AIModel/AIAircraft.hxx>
51 #include <AIModel/performancedata.hxx>
52 #include <AIModel/AIFlightPlan.hxx>
53 #include <Navaids/NavDataCache.hxx>
55 #include <ATC/atc_mgr.hxx>
57 #include <Scenery/scenery.hxx>
59 #include "groundnetwork.hxx"
62 using flightgear::NavDataCache;
64 /***************************************************************************
66 **************************************************************************/
68 FGTaxiSegment::FGTaxiSegment(PositionedID aStart, PositionedID aEnd) :
77 SGGeod FGTaxiSegment::getCenter() const
79 FGTaxiNode* start(getStart()), *end(getEnd());
80 double heading, length, az2;
81 SGGeodesy::inverse(start->geod(), end->geod(), heading, az2, length);
82 return SGGeodesy::direct(start->geod(), heading, length * 0.5);
85 FGTaxiNodeRef FGTaxiSegment::getEnd() const
87 return FGPositioned::loadById<FGTaxiNode>(endNode);
90 FGTaxiNodeRef FGTaxiSegment::getStart() const
92 return FGPositioned::loadById<FGTaxiNode>(startNode);
95 double FGTaxiSegment::getLength() const
97 return dist(getStart()->cart(), getEnd()->cart());
100 double FGTaxiSegment::getHeading() const
102 return SGGeodesy::courseDeg(getStart()->geod(), getEnd()->geod());
106 void FGTaxiSegment::block(int id, time_t blockTime, time_t now)
108 BlockListIterator i = blockTimes.begin();
109 while (i != blockTimes.end()) {
110 if (i->getId() == id)
114 if (i == blockTimes.end()) {
115 blockTimes.push_back(Block(id, blockTime, now));
116 sort(blockTimes.begin(), blockTimes.end());
118 i->updateTimeStamps(blockTime, now);
122 // The segment has a block if any of the block times listed in the block list is
123 // smaller than the current time.
124 bool FGTaxiSegment::hasBlock(time_t now)
126 for (BlockListIterator i = blockTimes.begin(); i != blockTimes.end(); i++) {
127 if (i->getBlockTime() < now)
133 void FGTaxiSegment::unblock(time_t now)
135 if (blockTimes.size()) {
136 BlockListIterator i = blockTimes.begin();
137 if (i->getTimeStamp() < (now - 30)) {
145 /***************************************************************************
147 **************************************************************************/
148 bool FGTaxiRoute::next(PositionedID *nde, int *rte)
150 if (nodes.size() != (routes.size()) + 1) {
151 SG_LOG(SG_GENERAL, SG_ALERT, "ALERT: Misconfigured TaxiRoute : " << nodes.size() << " " << routes.size());
152 throw sg_range_exception("Misconfigured taxi route");
154 if (currNode == nodes.end())
157 if (currNode != nodes.begin()) {
161 // Handle special case for the first node.
162 *rte = -1 * *(currRoute);
168 /***************************************************************************
170 **************************************************************************/
172 bool compare_trafficrecords(FGTrafficRecord a, FGTrafficRecord b)
174 return (a.getIntentions().size() < b.getIntentions().size());
177 FGGroundNetwork::FGGroundNetwork() :
185 currTraffic = activeTraffic.begin();
188 networkInitialized = false;
192 FGGroundNetwork::~FGGroundNetwork()
194 // JMT 2012-09-8 - disabling the groundnet-caching as part of enabling the
195 // navcache. The problem isn't the NavCache - it's that for the past few years,
196 // we have not being running destructors on FGPositioned classes, and hence,
197 // not running this code.
198 // When I fix FGPositioned lifetimes (unloading-at-runtime support), this
199 // will need to be re-visited so it can run safely during shutdown.
201 saveElevationCache();
203 BOOST_FOREACH(FGTaxiSegment* seg, segments) {
208 void FGGroundNetwork::saveElevationCache()
211 bool saveData = false;
213 if (fgGetBool("/sim/ai/groundnet-cache")) {
214 SGPath cacheData(globals->get_fg_home());
215 cacheData.append("ai");
216 string airport = parent->getId();
218 if ((airport) != "") {
220 ::snprintf(buffer, 128, "%c/%c/%c/",
221 airport[0], airport[1], airport[2]);
222 cacheData.append(buffer);
223 if (!cacheData.exists()) {
224 cacheData.create_dir(0755);
226 cacheData.append(airport + "-groundnet-cache.txt");
227 cachefile.open(cacheData.str().c_str());
231 cachefile << "[GroundNetcachedata:ref:2011:09:04]" << endl;
232 for (IndexTaxiNodeMap::iterator node = nodes.begin();
233 node != nodes.end(); node++) {
235 cachefile << node->second->getIndex () << " "
236 << node->second->getElevationM (parent->getElevation()*SG_FEET_TO_METER) << " "
246 void FGGroundNetwork::init(FGAirport* pr)
248 if (networkInitialized) {
249 FGATCController::init();
250 //cerr << "FGground network already initialized" << endl;
262 // establish pairing of segments
263 BOOST_FOREACH(FGTaxiSegment* segment, segments) {
264 segment->setIndex(index++);
266 if (segment->oppositeDirection) {
267 continue; // already established
270 FGTaxiSegment* opp = findSegment(segment->endNode, segment->startNode);
272 assert(opp->oppositeDirection == NULL);
273 segment->oppositeDirection = opp;
274 opp->oppositeDirection = segment;
278 if (fgGetBool("/sim/ai/groundnet-cache")) {
282 networkInitialized = true;
285 void FGGroundNetwork::loadSegments()
287 flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
288 // iterate over all ground-net nodes in this airport
289 BOOST_FOREACH(PositionedID node, cache->groundNetNodes(parent->guid(), false)) {
290 // find all segments leaving the node
291 BOOST_FOREACH(PositionedID end, cache->groundNetEdgesFrom(node, false)) {
292 segments.push_back(new FGTaxiSegment(node, end));
297 void FGGroundNetwork::parseCache()
299 SGPath cacheData(globals->get_fg_home());
300 cacheData.append("ai");
301 string airport = parent->getId();
303 if (airport.empty()) {
308 ::snprintf(buffer, 128, "%c/%c/%c/",
309 airport[0], airport[1], airport[2]);
310 cacheData.append(buffer);
311 if (!cacheData.exists()) {
312 cacheData.create_dir(0755);
316 cacheData.append(airport + "-groundnet-cache.txt");
317 if (cacheData.exists()) {
318 ifstream data(cacheData.c_str());
321 if (revisionStr != "[GroundNetcachedata:ref:2011:09:04]") {
322 SG_LOG(SG_GENERAL, SG_ALERT,"GroundNetwork Warning: discarding outdated cachefile " <<
323 cacheData.c_str() << " for Airport " << airport);
325 for (IndexTaxiNodeMap::iterator i = nodes.begin();
328 i->second->setElevation(parent->elevation() * SG_FEET_TO_METER);
329 data >> index >> elev;
332 if (index != i->second->getIndex()) {
333 SG_LOG(SG_GENERAL, SG_ALERT, "Index read from ground network cache at airport " << airport << " does not match index in the network itself");
335 i->second->setElevation(elev);
343 int FGGroundNetwork::findNearestNode(const SGGeod & aGeod) const
345 const bool onRunway = false;
346 return NavDataCache::instance()->findGroundNetNode(parent->guid(), aGeod, onRunway);
349 int FGGroundNetwork::findNearestNodeOnRunway(const SGGeod & aGeod, FGRunway* aRunway) const
351 const bool onRunway = true;
352 return NavDataCache::instance()->findGroundNetNode(parent->guid(), aGeod, onRunway, aRunway);
355 FGTaxiNodeRef FGGroundNetwork::findNode(PositionedID idx) const
357 return FGPositioned::loadById<FGTaxiNode>(idx);
360 FGTaxiSegment *FGGroundNetwork::findSegment(unsigned idx) const
362 if ((idx > 0) && (idx <= segments.size()))
363 return segments[idx - 1];
365 //cerr << "Alert: trying to find invalid segment " << idx << endl;
370 FGTaxiSegment* FGGroundNetwork::findSegment(PositionedID from, PositionedID to) const
376 // completely boring linear search of segments. Can be improved if/when
377 // this ever becomes a hot-spot
378 BOOST_FOREACH(FGTaxiSegment* seg, segments) {
379 if (seg->startNode != from) {
383 if ((to == 0) || (seg->endNode == to)) {
388 return NULL; // not found
391 static int edgePenalty(FGTaxiNode* tn)
393 return (tn->type() == FGPositioned::PARKING ? 10000 : 0) +
394 (tn->getIsOnRunway() ? 1000 : 0);
397 class ShortestPathData
405 FGTaxiNodeRef previousNode;
408 FGTaxiRoute FGGroundNetwork::findShortestRoute(PositionedID start, PositionedID end,
411 //implements Dijkstra's algorithm to find shortest distance route from start to end
412 //taken from http://en.wikipedia.org/wiki/Dijkstra's_algorithm
413 FGTaxiNodeVector unvisited;
414 flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
415 std::map<FGTaxiNode*, ShortestPathData> searchData;
417 BOOST_FOREACH(PositionedID n, cache->groundNetNodes(parent->guid(), !fullSearch)) {
418 unvisited.push_back(findNode(n));
421 FGTaxiNode *firstNode = findNode(start);
424 SG_LOG(SG_GENERAL, SG_ALERT,
425 "Error in ground network. Failed to find first waypoint: " << start
426 << " at " << ((parent) ? parent->getId() : "<unknown>"));
427 return FGTaxiRoute();
429 searchData[firstNode].score = 0.0;
431 FGTaxiNode *lastNode = findNode(end);
434 SG_LOG(SG_GENERAL, SG_ALERT,
435 "Error in ground network. Failed to find last waypoint: " << end
436 << " at " << ((parent) ? parent->getId() : "<unknown>"));
437 return FGTaxiRoute();
440 while (!unvisited.empty()) {
441 FGTaxiNode *best = unvisited.front();
442 BOOST_FOREACH(FGTaxiNode* i, unvisited) {
443 if (searchData[i].score < searchData[best].score) {
448 // remove 'best' from the unvisited set
449 FGTaxiNodeVectorIterator newend =
450 remove(unvisited.begin(), unvisited.end(), best);
451 unvisited.erase(newend, unvisited.end());
453 if (best == lastNode) { // found route or best not connected
457 BOOST_FOREACH(PositionedID targetId, cache->groundNetEdgesFrom(best->guid(), !fullSearch)) {
458 FGTaxiNodeRef tgt = FGPositioned::loadById<FGTaxiNode>(targetId);
459 double edgeLength = dist(best->cart(), tgt->cart());
460 double alt = searchData[best].score + edgeLength + edgePenalty(tgt);
461 if (alt < searchData[tgt].score) { // Relax (u,v)
462 searchData[tgt].score = alt;
463 searchData[tgt].previousNode = best;
465 } // of outgoing arcs/segments from current best node iteration
466 } // of unvisited nodes remaining
468 if (searchData[lastNode].score == HUGE_VAL) {
469 // no valid route found
471 SG_LOG(SG_GENERAL, SG_ALERT,
472 "Failed to find route from waypoint " << start << " to "
473 << end << " at " << parent->getId());
476 return FGTaxiRoute();
479 // assemble route from backtrace information
480 PositionedIDVec nodes;
482 FGTaxiNode *bt = lastNode;
484 while (searchData[bt].previousNode != 0) {
485 nodes.push_back(bt->guid());
486 FGTaxiSegment *segment = findSegment(searchData[bt].previousNode->guid(), bt->guid());
487 int idx = segment->getIndex();
488 routes.push_back(idx);
489 bt = searchData[bt].previousNode;
492 nodes.push_back(start);
493 reverse(nodes.begin(), nodes.end());
494 reverse(routes.begin(), routes.end());
495 return FGTaxiRoute(nodes, routes, searchData[lastNode].score, 0);
498 /* ATC Related Functions */
500 void FGGroundNetwork::announcePosition(int id,
501 FGAIFlightPlan * intendedRoute,
502 int currentPosition, double lat,
503 double lon, double heading,
504 double speed, double alt,
505 double radius, int leg,
506 FGAIAircraft * aircraft)
510 TrafficVectorIterator i = activeTraffic.begin();
511 // Search search if the current id alread has an entry
512 // This might be faster using a map instead of a vector, but let's start by taking a safe route
513 if (activeTraffic.size()) {
514 //while ((i->getId() != id) && i != activeTraffic.end()) {
515 while (i != activeTraffic.end()) {
516 if (i->getId() == id) {
522 // Add a new TrafficRecord if no one exsists for this aircraft.
523 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
527 rec.setPositionAndIntentions(currentPosition, intendedRoute);
528 rec.setPositionAndHeading(lat, lon, heading, speed, alt);
529 rec.setRadius(radius); // only need to do this when creating the record.
530 rec.setAircraft(aircraft);
532 activeTraffic.push_front(rec);
534 activeTraffic.push_back(rec);
538 i->setPositionAndIntentions(currentPosition, intendedRoute);
539 i->setPositionAndHeading(lat, lon, heading, speed, alt);
544 void FGGroundNetwork::signOff(int id)
546 TrafficVectorIterator i = activeTraffic.begin();
547 // Search search if the current id alread has an entry
548 // This might be faster using a map instead of a vector, but let's start by taking a safe route
549 if (activeTraffic.size()) {
550 //while ((i->getId() != id) && i != activeTraffic.end()) {
551 while (i != activeTraffic.end()) {
552 if (i->getId() == id) {
558 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
559 SG_LOG(SG_GENERAL, SG_ALERT,
560 "AI error: Aircraft without traffic record is signing off at " << SG_ORIGIN);
562 i = activeTraffic.erase(i);
566 * The ground network can deal with the following states:
567 * 0 = Normal; no action required
568 * 1 = "Acknowledge "Hold position
569 * 2 = "Acknowledge "Resume taxi".
570 * 3 = "Issue TaxiClearance"
571 * 4 = Acknowledge Taxi Clearance"
572 * 5 = Post acknowlegde taxiclearance: Start taxiing
574 * 7 = Acknowledge report runway
575 * 8 = Switch tower frequency
576 * 9 = Acknowledge switch tower frequency
577 *************************************************************************************************************************/
578 bool FGGroundNetwork::checkTransmissionState(int minState, int maxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId,
581 int state = i->getState();
582 if ((state >= minState) && (state <= maxState) && available) {
583 if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft(i->getAircraft())) {
584 //cerr << "Checking state " << state << " for " << i->getAircraft()->getCallSign() << endl;
585 SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
586 int n = trans_num->getIntValue();
588 trans_num->setIntValue(-1);
590 //cerr << "Selected transmission message " << n << endl;
591 //FGATCManager *atc = (FGATCManager*) globals->get_subsystem("atc");
592 FGATCDialogNew::instance()->removeEntry(1);
594 //cerr << "creating message for " << i->getAircraft()->getCallSign() << endl;
595 transmit(&(*i), &(*parent->getDynamics()), msgId, msgDir, false);
599 transmit(&(*i), &(*parent->getDynamics()), msgId, msgDir, true);
601 lastTransmission = now;
608 void FGGroundNetwork::updateAircraftInformation(int id, double lat, double lon,
609 double heading, double speed, double alt,
612 time_t currentTime = time(NULL);
613 if (nextSave < currentTime) {
614 saveElevationCache();
615 nextSave = currentTime + 100 + rand() % 200;
617 // Check whether aircraft are on hold due to a preceding pushback. If so, make sure to
618 // Transmit air-to-ground "Ready to taxi request:
619 // Transmit ground to air approval / hold
620 // Transmit confirmation ...
621 // Probably use a status mechanism similar to the Engine start procedure in the startup controller.
624 TrafficVectorIterator i = activeTraffic.begin();
625 // Search search if the current id has an entry
626 // This might be faster using a map instead of a vector, but let's start by taking a safe route
627 TrafficVectorIterator current, closest;
628 if (activeTraffic.size()) {
629 //while ((i->getId() != id) && i != activeTraffic.end()) {
630 while (i != activeTraffic.end()) {
631 if (i->getId() == id) {
637 // update position of the current aircraft
638 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
639 SG_LOG(SG_GENERAL, SG_ALERT,
640 "AI error: updating aircraft without traffic record at " << SG_ORIGIN);
642 i->setPositionAndHeading(lat, lon, heading, speed, alt);
648 // Update every three secs, but add some randomness
649 // to prevent all IA objects doing this in synchrony
650 //if (getDt() < (3.0) + (rand() % 10))
654 current->clearResolveCircularWait();
655 current->setWaitsForId(0);
656 checkSpeedAdjustment(id, lat, lon, heading, speed, alt);
657 bool needsTaxiClearance = current->getAircraft()->getTaxiClearanceRequest();
658 if (!needsTaxiClearance) {
659 checkHoldPosition(id, lat, lon, heading, speed, alt);
660 //if (checkForCircularWaits(id)) {
661 // i->setResolveCircularWait();
664 current->setHoldPosition(true);
665 int state = current->getState();
666 time_t now = time(NULL) + fgGetLong("/sim/time/warp");
667 if ((now - lastTransmission) > 15) {
670 if (checkTransmissionState(0,2, current, now, MSG_REQUEST_TAXI_CLEARANCE, ATC_AIR_TO_GROUND)) {
671 current->setState(3);
673 if (checkTransmissionState(3,3, current, now, MSG_ISSUE_TAXI_CLEARANCE, ATC_GROUND_TO_AIR)) {
674 current->setState(4);
676 if (checkTransmissionState(4,4, current, now, MSG_ACKNOWLEDGE_TAXI_CLEARANCE, ATC_AIR_TO_GROUND)) {
677 current->setState(5);
679 if ((state == 5) && available) {
680 current->setState(0);
681 current->getAircraft()->setTaxiClearanceRequest(false);
682 current->setHoldPosition(false);
690 Scan for a speed adjustment change. Find the nearest aircraft that is in front
691 and adjust speed when we get too close. Only do this when current position and/or
692 intentions of the current aircraft match current taxiroute position of the proximate
693 aircraft. For traffic that is on other routes we need to issue a "HOLD Position"
694 instruction. See below for the hold position instruction.
696 Note that there currently still is one flaw in the logic that needs to be addressed.
697 There can be situations where one aircraft is in front of the current aircraft, on a separate
698 route, but really close after an intersection coming off the current route. This
699 aircraft is still close enough to block the current aircraft. This situation is currently
700 not addressed yet, but should be.
703 void FGGroundNetwork::checkSpeedAdjustment(int id, double lat,
704 double lon, double heading,
705 double speed, double alt)
708 TrafficVectorIterator current, closest, closestOnNetwork;
709 TrafficVectorIterator i = activeTraffic.begin();
710 bool otherReasonToSlowDown = false;
711 // bool previousInstruction;
712 if (activeTraffic.size()) {
713 //while ((i->getId() != id) && (i != activeTraffic.end()))
714 while (i != activeTraffic.end()) {
715 if (i->getId() == id) {
723 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
724 SG_LOG(SG_GENERAL, SG_ALERT,
725 "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkSpeedAdjustment at " << SG_ORIGIN);
730 // previousInstruction = current->getSpeedAdjustment();
731 double mindist = HUGE_VAL;
732 if (activeTraffic.size()) {
733 double course, dist, bearing, az2; // minbearing,
734 SGGeod curr(SGGeod::fromDegM(lon, lat, alt));
735 //TrafficVector iterator closest;
737 closestOnNetwork = current;
738 for (TrafficVectorIterator i = activeTraffic.begin();
739 i != activeTraffic.end(); i++) {
744 SGGeod other(SGGeod::fromDegM(i->getLongitude(),
747 SGGeodesy::inverse(curr, other, course, az2, dist);
748 bearing = fabs(heading - course);
750 bearing = 360 - bearing;
751 if ((dist < mindist) && (bearing < 60.0)) {
754 closestOnNetwork = i;
755 // minbearing = bearing;
759 //Check traffic at the tower controller
760 if (towerController->hasActiveTraffic()) {
761 for (TrafficVectorIterator i =
762 towerController->getActiveTraffic().begin();
763 i != towerController->getActiveTraffic().end(); i++) {
764 //cerr << "Comparing " << current->getId() << " and " << i->getId() << endl;
765 SGGeod other(SGGeod::fromDegM(i->getLongitude(),
768 SGGeodesy::inverse(curr, other, course, az2, dist);
769 bearing = fabs(heading - course);
771 bearing = 360 - bearing;
772 if ((dist < mindist) && (bearing < 60.0)) {
773 //cerr << "Current aircraft " << current->getAircraft()->getTrafficRef()->getCallSign()
774 // << " is closest to " << i->getAircraft()->getTrafficRef()->getCallSign()
775 // << ", which has status " << i->getAircraft()->isScheduledForTakeoff()
779 // minbearing = bearing;
780 otherReasonToSlowDown = true;
784 // Finally, check UserPosition
785 // Note, as of 2011-08-01, this should no longer be necessecary.
787 double userLatitude = fgGetDouble("/position/latitude-deg");
788 double userLongitude = fgGetDouble("/position/longitude-deg");
789 SGGeod user(SGGeod::fromDeg(userLongitude, userLatitude));
790 SGGeodesy::inverse(curr, user, course, az2, dist);
792 bearing = fabs(heading - course);
794 bearing = 360 - bearing;
795 if ((dist < mindist) && (bearing < 60.0)) {
798 minbearing = bearing;
799 otherReasonToSlowDown = true;
802 current->clearSpeedAdjustment();
803 bool needBraking = false;
804 if (current->checkPositionAndIntentions(*closest)
805 || otherReasonToSlowDown) {
806 double maxAllowableDistance =
807 (1.1 * current->getRadius()) +
808 (1.1 * closest->getRadius());
809 if (mindist < 2 * maxAllowableDistance) {
810 if (current->getId() == closest->getWaitsForId())
813 current->setWaitsForId(closest->getId());
814 if (closest->getId() != current->getId()) {
815 current->setSpeedAdjustment(closest->getSpeed() *
820 // closest->getAircraft()->getTakeOffStatus() &&
821 // (current->getAircraft()->getTrafficRef()->getDepartureAirport() == closest->getAircraft()->getTrafficRef()->getDepartureAirport()) &&
822 // (current->getAircraft()->GetFlightPlan()->getRunway() == closest->getAircraft()->GetFlightPlan()->getRunway())
824 // current->getAircraft()->scheduleForATCTowerDepartureControl(1);
826 current->setSpeedAdjustment(0); // This can only happen when the user aircraft is the one closest
828 if (mindist < maxAllowableDistance) {
829 //double newSpeed = (maxAllowableDistance-mindist);
830 //current->setSpeedAdjustment(newSpeed);
831 //if (mindist < 0.5* maxAllowableDistance)
833 current->setSpeedAdjustment(0);
838 if ((closest->getId() == closestOnNetwork->getId()) && (current->getPriority() < closest->getPriority()) && needBraking) {
839 swap(current, closest);
845 Check for "Hold position instruction".
846 The hold position should be issued under the following conditions:
847 1) For aircraft entering or crossing a runway with active traffic on it, or landing aircraft near it
848 2) For taxiing aircraft that use one taxiway in opposite directions
849 3) For crossing or merging taxiroutes.
852 void FGGroundNetwork::checkHoldPosition(int id, double lat,
853 double lon, double heading,
854 double speed, double alt)
856 TrafficVectorIterator current;
857 TrafficVectorIterator i = activeTraffic.begin();
858 if (activeTraffic.size()) {
859 //while ((i->getId() != id) && i != activeTraffic.end())
860 while (i != activeTraffic.end()) {
861 if (i->getId() == id) {
869 time_t now = time(NULL) + fgGetLong("/sim/time/warp");
870 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
871 SG_LOG(SG_GENERAL, SG_ALERT,
872 "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkHoldPosition at " << SG_ORIGIN);
876 if (current->getAircraft()->getTakeOffStatus() == 1) {
877 current->setHoldPosition(true);
880 if (current->getAircraft()->getTakeOffStatus() == 2) {
881 //cerr << current->getAircraft()->getCallSign() << ". Taxi in position and hold" << endl;
882 current->setHoldPosition(false);
883 current->clearSpeedAdjustment();
886 bool origStatus = current->hasHoldPosition();
887 current->setHoldPosition(false);
888 //SGGeod curr(SGGeod::fromDegM(lon, lat, alt));
889 int currentRoute = i->getCurrentPosition();
891 if (i->getIntentions().size()) {
892 nextRoute = (*(i->getIntentions().begin()));
896 if (currentRoute > 0) {
897 FGTaxiSegment *tx = findSegment(currentRoute);
900 nx = findSegment(nextRoute);
904 //if (tx->hasBlock(now) || nx->hasBlock(now) ) {
905 // current->setHoldPosition(true);
907 SGGeod start(SGGeod::fromDeg((i->getLongitude()), (i->getLatitude())));
908 SGGeod end (nx->getStart()->geod());
910 double distance = SGGeodesy::distanceM(start, end);
911 if (nx->hasBlock(now) && (distance < i->getRadius() * 4)) {
912 current->setHoldPosition(true);
914 intVecIterator ivi = i->getIntentions().begin();
915 while (ivi != i->getIntentions().end()) {
917 distance += segments[(*ivi)-1]->getLength();
918 if ((segments[(*ivi)-1]->hasBlock(now)) && (distance < i->getRadius() * 4)) {
919 current->setHoldPosition(true);
927 bool currStatus = current->hasHoldPosition();
928 current->setHoldPosition(origStatus);
929 // Either a Hold Position or a resume taxi transmission has been issued
930 if ((now - lastTransmission) > 2) {
933 if (current->getState() == 0) {
934 if ((origStatus != currStatus) && available) {
935 //cerr << "Issueing hold short instrudtion " << currStatus << " " << available << endl;
936 if (currStatus == true) { // No has a hold short instruction
937 transmit(&(*current), &(*parent->getDynamics()), MSG_HOLD_POSITION, ATC_GROUND_TO_AIR, true);
938 //cerr << "Transmittin hold short instrudtion " << currStatus << " " << available << endl;
939 current->setState(1);
941 transmit(&(*current), &(*parent->getDynamics()), MSG_RESUME_TAXI, ATC_GROUND_TO_AIR, true);
942 //cerr << "Transmittig resume instrudtion " << currStatus << " " << available << endl;
943 current->setState(2);
945 lastTransmission = now;
947 // Don't act on the changed instruction until the transmission is confirmed
948 // So set back to original status
949 //cerr << "Current state " << current->getState() << endl;
954 // 7 = Acknowledge report runway
955 // 8 = Switch tower frequency
956 //9 = Acknowledge switch tower frequency
958 //int state = current->getState();
959 if (checkTransmissionState(1,1, current, now, MSG_ACKNOWLEDGE_HOLD_POSITION, ATC_AIR_TO_GROUND)) {
960 current->setState(0);
961 current->setHoldPosition(true);
963 if (checkTransmissionState(2,2, current, now, MSG_ACKNOWLEDGE_RESUME_TAXI, ATC_AIR_TO_GROUND)) {
964 current->setState(0);
965 current->setHoldPosition(false);
967 if (current->getAircraft()->getTakeOffStatus() && (current->getState() == 0)) {
968 //cerr << "Scheduling " << current->getAircraft()->getCallSign() << " for hold short" << endl;
969 current->setState(6);
971 if (checkTransmissionState(6,6, current, now, MSG_REPORT_RUNWAY_HOLD_SHORT, ATC_AIR_TO_GROUND)) {
973 if (checkTransmissionState(7,7, current, now, MSG_ACKNOWLEDGE_REPORT_RUNWAY_HOLD_SHORT, ATC_GROUND_TO_AIR)) {
975 if (checkTransmissionState(8,8, current, now, MSG_SWITCH_TOWER_FREQUENCY, ATC_GROUND_TO_AIR)) {
977 if (checkTransmissionState(9,9, current, now, MSG_ACKNOWLEDGE_SWITCH_TOWER_FREQUENCY, ATC_AIR_TO_GROUND)) {
982 //current->setState(0);
986 * Check whether situations occur where the current aircraft is waiting for itself
987 * due to higher order interactions.
988 * A 'circular' wait is a situation where a waits for b, b waits for c, and c waits
989 * for a. Ideally each aircraft only waits for one other aircraft, so by tracing
990 * through this list of waiting aircraft, we can check if we'd eventually end back
991 * at the current aircraft.
993 * Note that we should consider the situation where we are actually checking aircraft
994 * d, which is waiting for aircraft a. d is not part of the loop, but is held back by
995 * the looping aircraft. If we don't check for that, this function will get stuck into
999 bool FGGroundNetwork::checkForCircularWaits(int id)
1001 //cerr << "Performing Wait check " << id << endl;
1003 TrafficVectorIterator current, other;
1004 TrafficVectorIterator i = activeTraffic.begin();
1005 int trafficSize = activeTraffic.size();
1007 while (i != activeTraffic.end()) {
1008 if (i->getId() == id) {
1016 if (i == activeTraffic.end() || (trafficSize == 0)) {
1017 SG_LOG(SG_GENERAL, SG_ALERT,
1018 "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkForCircularWaits at " << SG_ORIGIN);
1022 target = current->getWaitsForId();
1023 //bool printed = false; // Note that this variable is for debugging purposes only.
1027 //cerr << "aircraft waits for user" << endl;
1032 while ((target > 0) && (target != id) && counter++ < trafficSize) {
1034 TrafficVectorIterator i = activeTraffic.begin();
1036 //while ((i->getId() != id) && i != activeTraffic.end())
1037 while (i != activeTraffic.end()) {
1038 if (i->getId() == target) {
1046 if (i == activeTraffic.end() || (trafficSize == 0)) {
1047 //cerr << "[Waiting for traffic at Runway: DONE] " << endl << endl;;
1048 // The target id is not found on the current network, which means it's at the tower
1049 //SG_LOG(SG_GENERAL, SG_ALERT, "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkForCircularWaits");
1053 target = other->getWaitsForId();
1055 // actually this trap isn't as impossible as it first seemed:
1056 // the setWaitsForID(id) is set to current when the aircraft
1057 // is waiting for the user controlled aircraft.
1058 //if (current->getId() == other->getId()) {
1059 // cerr << "Caught the impossible trap" << endl;
1060 // cerr << "Current = " << current->getId() << endl;
1061 // cerr << "Other = " << other ->getId() << endl;
1062 // for (TrafficVectorIterator at = activeTraffic.begin();
1063 // at != activeTraffic.end();
1065 // cerr << "currently active aircraft : " << at->getCallSign() << " with Id " << at->getId() << " waits for " << at->getWaitsForId() << endl;
1068 if (current->getId() == other->getId())
1071 //cerr << current->getCallSign() << " (" << current->getId() << ") " << " -> " << other->getCallSign()
1072 // << " (" << other->getId() << "); " << endl;;
1082 // cerr << "[done] " << endl << endl;;
1084 SG_LOG(SG_GENERAL, SG_WARN,
1085 "Detected circular wait condition: Id = " << id <<
1086 "target = " << target);
1093 // Note that this function is probably obsolete...
1094 bool FGGroundNetwork::hasInstruction(int id)
1096 TrafficVectorIterator i = activeTraffic.begin();
1097 // Search search if the current id has an entry
1098 // This might be faster using a map instead of a vector, but let's start by taking a safe route
1099 if (activeTraffic.size()) {
1100 //while ((i->getId() != id) && i != activeTraffic.end()) {
1101 while (i != activeTraffic.end()) {
1102 if (i->getId() == id) {
1108 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1109 SG_LOG(SG_GENERAL, SG_ALERT,
1110 "AI error: checking ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
1112 return i->hasInstruction();
1117 FGATCInstruction FGGroundNetwork::getInstruction(int id)
1119 TrafficVectorIterator i = activeTraffic.begin();
1120 // Search search if the current id has an entry
1121 // This might be faster using a map instead of a vector, but let's start by taking a safe route
1122 if (activeTraffic.size()) {
1123 //while ((i->getId() != id) && i != activeTraffic.end()) {
1124 while (i != activeTraffic.end()) {
1125 if (i->getId() == id) {
1131 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1132 SG_LOG(SG_GENERAL, SG_ALERT,
1133 "AI error: requesting ATC instruction for aircraft without traffic record at " << SG_ORIGIN);
1135 return i->getInstruction();
1137 return FGATCInstruction();
1140 // Note that this function is copied from simgear. for maintanance purposes, it's probabtl better to make a general function out of that.
1141 static void WorldCoordinate(osg::Matrix& obj_pos, double lat,
1142 double lon, double elev, double hdg, double slope)
1144 SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
1145 obj_pos = makeZUpFrame(geod);
1146 // hdg is not a compass heading, but a counter-clockwise rotation
1147 // around the Z axis
1148 obj_pos.preMult(osg::Matrix::rotate(hdg * SGD_DEGREES_TO_RADIANS,
1150 obj_pos.preMult(osg::Matrix::rotate(slope * SGD_DEGREES_TO_RADIANS,
1157 void FGGroundNetwork::render(bool visible)
1159 SGMaterialLib *matlib = globals->get_matlib();
1162 globals->get_scenery()->get_scene_graph()->removeChild(group);
1163 //while (group->getNumChildren()) {
1164 // cerr << "Number of children: " << group->getNumChildren() << endl;
1165 //simgear::EffectGeode* geode = (simgear::EffectGeode*) group->getChild(0);
1166 //osg::MatrixTransform *obj_trans = (osg::MatrixTransform*) group->getChild(0);
1167 //geode->releaseGLObjects();
1168 //group->removeChild(geode);
1173 group = new osg::Group;
1174 FGScenery * local_scenery = globals->get_scenery();
1175 // double elevation_meters = 0.0;
1176 // double elevation_feet = 0.0;
1177 time_t now = time(NULL) + fgGetLong("/sim/time/warp");
1178 //for ( FGTaxiSegmentVectorIterator i = segments.begin(); i != segments.end(); i++) {
1180 for (TrafficVectorIterator i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
1181 // Handle start point i.e. the segment that is connected to the aircraft itself on the starting end
1182 // and to the the first "real" taxi segment on the other end.
1183 int pos = i->getCurrentPosition() - 1;
1186 SGGeod start(SGGeod::fromDeg((i->getLongitude()), (i->getLatitude())));
1187 SGGeod end (segments[pos]->getEnd()->geod());
1189 double length = SGGeodesy::distanceM(start, end);
1190 //heading = SGGeodesy::headingDeg(start->geod(), end->geod());
1192 double az2, heading; //, distanceM;
1193 SGGeodesy::inverse(start, end, heading, az2, length);
1194 double coveredDistance = length * 0.5;
1196 SGGeodesy::direct(start, heading, coveredDistance, center, az2);
1197 //std::cerr << "Active Aircraft : Centerpoint = (" << center.getLatitudeDeg() << ", " << center.getLongitudeDeg() << "). Heading = " << heading << std::endl;
1198 ///////////////////////////////////////////////////////////////////////////////
1199 // Make a helper function out of this
1200 osg::Matrix obj_pos;
1201 osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
1202 obj_trans->setDataVariance(osg::Object::STATIC);
1203 // Experimental: Calculate slope here, based on length, and the individual elevations
1204 double elevationStart;
1205 if (isUserAircraft((i)->getAircraft())) {
1206 elevationStart = fgGetDouble("/position/ground-elev-m");
1208 elevationStart = ((i)->getAircraft()->_getAltitude());
1210 double elevationEnd = segments[pos]->getEnd()->getElevationM();
1211 //cerr << "Using elevation " << elevationEnd << endl;
1213 if ((elevationEnd == 0) || (elevationEnd = parent->getElevation())) {
1214 SGGeod center2 = end;
1215 center2.setElevationM(SG_MAX_ELEVATION_M);
1216 if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
1217 // elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
1218 //elevation_meters += 0.5;
1221 elevationEnd = parent->getElevation();
1223 segments[pos]->getEnd()->setElevation(elevationEnd);
1225 double elevationMean = (elevationStart + elevationEnd) / 2.0;
1226 double elevDiff = elevationEnd - elevationStart;
1228 double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
1230 //cerr << "1. Using mean elevation : " << elevationMean << " and " << slope << endl;
1232 WorldCoordinate( obj_pos, center.getLatitudeDeg(), center.getLongitudeDeg(), elevationMean+ 0.5, -(heading), slope );
1234 obj_trans->setMatrix( obj_pos );
1235 //osg::Vec3 center(0, 0, 0)
1237 float width = length /2.0;
1238 osg::Vec3 corner(-width, 0, 0.25f);
1239 osg::Vec3 widthVec(2*width + 1, 0, 0);
1240 osg::Vec3 heightVec(0, 1, 0);
1241 osg::Geometry* geometry;
1242 geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
1243 simgear::EffectGeode* geode = new simgear::EffectGeode;
1244 geode->setName("test");
1245 geode->addDrawable(geometry);
1246 //osg::Node *custom_obj;
1248 if (segments[pos]->hasBlock(now)) {
1249 mat = matlib->find("UnidirectionalTaperRed", center);
1251 mat = matlib->find("UnidirectionalTaperGreen", center);
1254 geode->setEffect(mat->get_effect());
1255 obj_trans->addChild(geode);
1256 // wire as much of the scene graph together as we can
1257 //->addChild( obj_trans );
1258 group->addChild( obj_trans );
1259 /////////////////////////////////////////////////////////////////////
1261 //std::cerr << "BIG FAT WARNING: current position is here : " << pos << std::endl;
1263 // Next: Draw the other taxi segments.
1264 for (intVecIterator j = (i)->getIntentions().begin(); j != (i)->getIntentions().end(); j++) {
1265 osg::Matrix obj_pos;
1268 osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
1269 obj_trans->setDataVariance(osg::Object::STATIC);
1271 // Experimental: Calculate slope here, based on length, and the individual elevations
1272 double elevationStart = segments[k]->getStart()->getElevationM();
1273 double elevationEnd = segments[k]->getEnd ()->getElevationM();
1274 if ((elevationStart == 0) || (elevationStart == parent->getElevation())) {
1275 SGGeod center2 = segments[k]->getStart()->geod();
1276 center2.setElevationM(SG_MAX_ELEVATION_M);
1277 if (local_scenery->get_elevation_m( center2, elevationStart, NULL )) {
1278 // elevation_feet = elevationStart * SG_METER_TO_FEET + 0.5;
1279 //elevation_meters += 0.5;
1282 elevationStart = parent->getElevation();
1284 segments[k]->getStart()->setElevation(elevationStart);
1286 if ((elevationEnd == 0) || (elevationEnd == parent->getElevation())) {
1287 SGGeod center2 = segments[k]->getEnd()->geod();
1288 center2.setElevationM(SG_MAX_ELEVATION_M);
1289 if (local_scenery->get_elevation_m( center2, elevationEnd, NULL )) {
1290 // elevation_feet = elevationEnd * SG_METER_TO_FEET + 0.5;
1291 //elevation_meters += 0.5;
1294 elevationEnd = parent->getElevation();
1296 segments[k]->getEnd()->setElevation(elevationEnd);
1299 double elevationMean = (elevationStart + elevationEnd) / 2.0;
1300 double elevDiff = elevationEnd - elevationStart;
1301 double length = segments[k]->getLength();
1302 double slope = atan2(elevDiff, length) * SGD_RADIANS_TO_DEGREES;
1304 // cerr << "2. Using mean elevation : " << elevationMean << " and " << slope << endl;
1306 SGGeod segCenter = segments[k]->getCenter();
1307 WorldCoordinate( obj_pos, segCenter.getLatitudeDeg(), segCenter.getLongitudeDeg(), elevationMean+ 0.5, -(segments[k]->getHeading()), slope );
1309 obj_trans->setMatrix( obj_pos );
1310 //osg::Vec3 center(0, 0, 0)
1312 float width = segments[k]->getLength() /2.0;
1313 osg::Vec3 corner(-width, 0, 0.25f);
1314 osg::Vec3 widthVec(2*width + 1, 0, 0);
1315 osg::Vec3 heightVec(0, 1, 0);
1316 osg::Geometry* geometry;
1317 geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
1318 simgear::EffectGeode* geode = new simgear::EffectGeode;
1319 geode->setName("test");
1320 geode->addDrawable(geometry);
1321 //osg::Node *custom_obj;
1323 if (segments[k]->hasBlock(now)) {
1324 mat = matlib->find("UnidirectionalTaperRed", segCenter);
1326 mat = matlib->find("UnidirectionalTaperGreen", segCenter);
1329 geode->setEffect(mat->get_effect());
1330 obj_trans->addChild(geode);
1331 // wire as much of the scene graph together as we can
1332 //->addChild( obj_trans );
1333 group->addChild( obj_trans );
1338 globals->get_scenery()->get_scene_graph()->addChild(group);
1342 string FGGroundNetwork::getName() {
1343 return string(parent->getId() + "-ground");
1346 void FGGroundNetwork::update(double dt)
1348 time_t now = time(NULL) + fgGetLong("/sim/time/warp");
1349 for (FGTaxiSegmentVectorIterator tsi = segments.begin(); tsi != segments.end(); tsi++) {
1350 (*tsi)->unblock(now);
1353 //sort(activeTraffic.begin(), activeTraffic.end(), compare_trafficrecords);
1354 // Handle traffic that is under ground control first; this way we'll prevent clutter at the gate areas.
1355 // Don't allow an aircraft to pushback when a taxiing aircraft is currently using part of the intended route.
1356 for (TrafficVectorIterator i = parent->getDynamics()->getStartupController()->getActiveTraffic().begin();
1357 i != parent->getDynamics()->getStartupController()->getActiveTraffic().end(); i++) {
1359 i->setPriority(priority++);
1360 // in meters per second;
1361 double vTaxi = (i->getAircraft()->getPerformance()->vTaxi() * SG_NM_TO_METER) / 3600;
1362 if (i->isActive(0)) {
1364 // Check for all active aircraft whether it's current pos segment is
1365 // an opposite of one of the departing aircraft's intentions
1366 for (TrafficVectorIterator j = activeTraffic.begin(); j != activeTraffic.end(); j++) {
1367 int pos = j->getCurrentPosition();
1369 FGTaxiSegment *seg = segments[pos-1]->opposite();
1371 int posReverse = seg->getIndex();
1372 for (intVecIterator k = i->getIntentions().begin(); k != i->getIntentions().end(); k++) {
1373 if ((*k) == posReverse) {
1375 segments[posReverse-1]->block(i->getId(), now, now);
1381 // if the current aircraft is still allowed to pushback, we can start reserving a route for if by blocking all the entry taxiways.
1382 if (i->pushBackAllowed()) {
1384 int pos = i->getCurrentPosition();
1386 FGTaxiSegment *seg = segments[pos-1];
1387 FGTaxiNode *node = seg->getEnd();
1388 length = seg->getLength();
1389 for (FGTaxiSegmentVectorIterator tsi = segments.begin(); tsi != segments.end(); tsi++) {
1390 if (((*tsi)->getEnd() == node) && ((*tsi) != seg)) {
1391 (*tsi)->block(i->getId(), now, now);
1395 for (intVecIterator j = i->getIntentions().begin(); j != i->getIntentions().end(); j++) {
1398 FGTaxiSegment *seg = segments[pos-1];
1399 FGTaxiNode *node = seg->getEnd();
1400 length += seg->getLength();
1401 time_t blockTime = now + (length / vTaxi);
1402 for (FGTaxiSegmentVectorIterator tsi = segments.begin(); tsi != segments.end(); tsi++) {
1403 if (((*tsi)->getEnd() == node) && ((*tsi) != seg)) {
1404 (*tsi)->block(i->getId(), blockTime-30, now);
1412 for (TrafficVectorIterator i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
1414 double vTaxi = (i->getAircraft()->getPerformance()->vTaxi() * SG_NM_TO_METER) / 3600;
1415 i->setPriority(priority++);
1416 int pos = i->getCurrentPosition();
1418 length = segments[pos-1]->getLength();
1419 if (segments[pos-1]->hasBlock(now)) {
1420 //SG_LOG(SG_GENERAL, SG_ALERT, "Taxiway incursion for AI aircraft" << i->getAircraft()->getCallSign());
1425 for (ivi = i->getIntentions().begin(); ivi != i->getIntentions().end(); ivi++) {
1426 int segIndex = (*ivi);
1428 if (segments[segIndex-1]->hasBlock(now))
1432 //after this, ivi points just behind the last valid unblocked taxi segment.
1433 for (intVecIterator j = i->getIntentions().begin(); j != ivi; j++) {
1436 FGTaxiSegment *seg = segments[pos-1];
1437 FGTaxiNode *node = seg->getEnd();
1438 length += seg->getLength();
1439 for (FGTaxiSegmentVectorIterator tsi = segments.begin(); tsi != segments.end(); tsi++) {
1440 if (((*tsi)->getEnd() == node) && ((*tsi) != seg)) {
1441 time_t blockTime = now + (length / vTaxi);
1442 (*tsi)->block(i->getId(), blockTime - 30, now);