2 // groundnet.cxx - Implimentation of the FlightGear airport ground handling code
4 // Written by Durk Talsma, started June 2005.
6 // Copyright (C) 2004 Durk Talsma.
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
33 #include <osg/Geometry>
34 #include <osg/MatrixTransform>
37 #include <simgear/debug/logstream.hxx>
38 #include <simgear/route/waypoint.hxx>
39 #include <simgear/scene/material/EffectGeode.hxx>
40 #include <simgear/scene/material/matlib.hxx>
41 #include <simgear/scene/material/mat.hxx>
43 #include <Airports/simple.hxx>
44 #include <Airports/dynamics.hxx>
46 #include <AIModel/AIAircraft.hxx>
47 #include <AIModel/AIFlightPlan.hxx>
49 #include <ATC/atc_mgr.hxx>
51 #include <Scenery/scenery.hxx>
53 #include "groundnetwork.hxx"
55 /***************************************************************************
57 **************************************************************************/
59 void FGTaxiSegment::setStart(FGTaxiNodeVector * nodes)
61 FGTaxiNodeVectorIterator i = nodes->begin();
62 while (i != nodes->end()) {
63 //cerr << "Scanning start node index" << (*i)->getIndex() << endl;
64 if ((*i)->getIndex() == startNode) {
65 start = (*i)->getAddress();
66 (*i)->addSegment(this);
71 SG_LOG(SG_GENERAL, SG_ALERT,
72 "Could not find start node " << startNode << endl);
75 void FGTaxiSegment::setEnd(FGTaxiNodeVector * nodes)
77 FGTaxiNodeVectorIterator i = nodes->begin();
78 while (i != nodes->end()) {
79 //cerr << "Scanning end node index" << (*i)->getIndex() << endl;
80 if ((*i)->getIndex() == endNode) {
81 end = (*i)->getAddress();
86 SG_LOG(SG_GENERAL, SG_ALERT,
87 "Could not find end node " << endNode << endl);
92 // There is probably a computationally cheaper way of
94 void FGTaxiSegment::setDimensions(double elevation)
96 length = SGGeodesy::distanceM(start->getGeod(), end->getGeod());
97 //heading = SGGeodesy::headingDeg(start->getGeod(), end->getGeod());
99 double az2; //, distanceM;
100 SGGeodesy::inverse(start->getGeod(), end->getGeod(), heading, az2, length);
101 double coveredDistance = length * 0.5;
102 SGGeodesy::direct(start->getGeod(), heading, coveredDistance, center, az2);
103 //cerr << "Centerpoint = (" << center.getLatitudeDeg() << ", " << center.getLongitudeDeg() << "). Heading = " << heading << endl;
107 //void FGTaxiSegment::setCourseDiff(double crse)
109 // headingDiff = fabs(course - crse);
111 // if (headingDiff > 180)
112 // headingDiff = fabs(headingDiff - 360);
116 /***************************************************************************
118 **************************************************************************/
119 bool FGTaxiRoute::next(int *nde)
121 //for (intVecIterator i = nodes.begin(); i != nodes.end(); i++)
122 // cerr << "FGTaxiRoute contains : " << *(i) << endl;
123 //cerr << "Offset from end: " << nodes.end() - currNode << endl;
124 //if (currNode != nodes.end())
125 // cerr << "true" << endl;
127 // cerr << "false" << endl;
128 //if (nodes.size() != (routes.size()) +1)
129 // cerr << "ALERT: Misconfigured TaxiRoute : " << nodes.size() << " " << routes.size() << endl;
131 if (currNode == nodes.end())
134 if (currNode != nodes.begin()) // make sure route corresponds to the end node
140 bool FGTaxiRoute::next(int *nde, int *rte)
142 //for (intVecIterator i = nodes.begin(); i != nodes.end(); i++)
143 // cerr << "FGTaxiRoute contains : " << *(i) << endl;
144 //cerr << "Offset from end: " << nodes.end() - currNode << endl;
145 //if (currNode != nodes.end())
146 // cerr << "true" << endl;
148 // cerr << "false" << endl;
149 if (nodes.size() != (routes.size()) + 1) {
150 SG_LOG(SG_GENERAL, SG_ALERT,
151 "ALERT: Misconfigured TaxiRoute : " << nodes.
152 size() << " " << routes.size());
155 if (currNode == nodes.end())
158 //*rte = *(currRoute);
159 if (currNode != nodes.begin()) // Make sure route corresponds to the end node
164 // If currNode points to the first node, this means the aircraft is not on the taxi node
165 // yet. Make sure to return a unique identifyer in this situation though, because otherwise
166 // the speed adjust AI code may be unable to resolve whether two aircraft are on the same
167 // taxi route or not. the negative of the preceding route seems a logical choice, as it is
168 // unique for any starting location.
169 // Note that this is probably just a temporary fix until I get Parking / tower control working.
170 *rte = -1 * *(currRoute);
177 void FGTaxiRoute::rewind(int route)
183 if (!(next(&currPoint, &currRoute))) {
184 SG_LOG(SG_GENERAL, SG_ALERT,
185 "Error in rewinding TaxiRoute: current" << currRoute <<
188 } while (currRoute != route);
194 /***************************************************************************
196 **************************************************************************/
197 bool compare_nodes(FGTaxiNode * a, FGTaxiNode * b)
202 bool compare_segments(FGTaxiSegment * a, FGTaxiSegment * b)
207 FGGroundNetwork::FGGroundNetwork()
215 currTraffic = activeTraffic.begin();
220 FGGroundNetwork::~FGGroundNetwork()
222 for (FGTaxiNodeVectorIterator node = nodes.begin();
223 node != nodes.end(); node++) {
227 pushBackNodes.clear();
228 for (FGTaxiSegmentVectorIterator seg = segments.begin();
229 seg != segments.end(); seg++) {
235 void FGGroundNetwork::addSegment(const FGTaxiSegment & seg)
237 segments.push_back(new FGTaxiSegment(seg));
240 void FGGroundNetwork::addNode(const FGTaxiNode & node)
242 nodes.push_back(new FGTaxiNode(node));
245 void FGGroundNetwork::addNodes(FGParkingVec * parkings)
248 FGParkingVecIterator i = parkings->begin();
249 while (i != parkings->end()) {
250 n.setIndex(i->getIndex());
251 n.setLatitude(i->getLatitude());
252 n.setLongitude(i->getLongitude());
253 n.setElevation(parent->getElevation());
254 nodes.push_back(new FGTaxiNode(n));
262 void FGGroundNetwork::init()
266 sort(nodes.begin(), nodes.end(), compare_nodes);
267 //sort(segments.begin(), segments.end(), compare_segments());
268 FGTaxiSegmentVectorIterator i = segments.begin();
269 while (i != segments.end()) {
270 (*i)->setStart(&nodes);
271 (*i)->setEnd(&nodes);
272 (*i)->setDimensions(parent->getElevation());
273 (*i)->setIndex(index);
274 if ((*i)->isPushBack()) {
275 pushBackNodes.push_back((*i)->getEnd());
277 //SG_LOG(SG_GENERAL, SG_BULK, "initializing segment " << (*i)->getIndex() << endl);
278 //SG_LOG(SG_GENERAL, SG_BULK, "Track distance = " << (*i)->getLength() << endl);
279 //SG_LOG(SG_GENERAL, SG_BULK, "Track runs from " << (*i)->getStart()->getIndex() << " to "
280 // << (*i)->getEnd()->getIndex() << endl);
285 i = segments.begin();
286 while (i != segments.end()) {
287 FGTaxiSegmentVectorIterator j = (*i)->getEnd()->getBeginRoute();
288 while (j != (*i)->getEnd()->getEndRoute()) {
289 if ((*j)->getEnd()->getIndex() == (*i)->getStart()->getIndex()) {
290 // int start1 = (*i)->getStart()->getIndex();
291 // int end1 = (*i)->getEnd() ->getIndex();
292 // int start2 = (*j)->getStart()->getIndex();
293 // int end2 = (*j)->getEnd()->getIndex();
294 // int oppIndex = (*j)->getIndex();
295 //cerr << "Opposite of " << (*i)->getIndex() << " (" << start1 << "," << end1 << ") "
296 // << "happens to be " << oppIndex << " (" << start2 << "," << end2 << ") " << endl;
297 (*i)->setOpposite(*j);
304 //FGTaxiNodeVectorIterator j = nodes.begin();
305 //while (j != nodes.end()) {
306 // if ((*j)->getHoldPointType() == 3) {
307 // pushBackNodes.push_back((*j));
311 //cerr << "Done initializing ground network" << endl;
315 int FGGroundNetwork::findNearestNode(const SGGeod & aGeod)
317 double minDist = HUGE_VAL;
320 for (FGTaxiNodeVectorIterator itr = nodes.begin(); itr != nodes.end();
322 double d = SGGeodesy::distanceM(aGeod, (*itr)->getGeod());
325 index = (*itr)->getIndex();
326 //cerr << "Minimum distance of " << minDist << " for index " << index << endl;
333 int FGGroundNetwork::findNearestNode(double lat, double lon)
335 return findNearestNode(SGGeod::fromDeg(lon, lat));
338 FGTaxiNode *FGGroundNetwork::findNode(unsigned idx)
340 for (FGTaxiNodeVectorIterator
342 itr != nodes.end(); itr++)
344 if (itr->getIndex() == idx)
345 return itr->getAddress();
348 if ((idx >= 0) && (idx < nodes.size()))
349 return nodes[idx]->getAddress();
354 FGTaxiSegment *FGGroundNetwork::findSegment(unsigned idx)
356 for (FGTaxiSegmentVectorIterator
357 itr = segments.begin();
358 itr != segments.end(); itr++)
360 if (itr->getIndex() == idx)
361 return itr->getAddress();
364 if ((idx > 0) && (idx <= segments.size()))
365 return segments[idx - 1]->getAddress();
367 //cerr << "Alert: trying to find invalid segment " << idx << endl;
373 FGTaxiRoute FGGroundNetwork::findShortestRoute(int start, int end,
376 //implements Dijkstra's algorithm to find shortest distance route from start to end
377 //taken from http://en.wikipedia.org/wiki/Dijkstra's_algorithm
379 //double INFINITE = 100000000000.0;
380 // initialize scoring values
381 int nParkings = parent->getDynamics()->getNrOfParkings();
382 FGTaxiNodeVector *currNodesSet;
384 currNodesSet = &nodes;
386 currNodesSet = &pushBackNodes;
389 for (FGTaxiNodeVectorIterator
390 itr = currNodesSet->begin(); itr != currNodesSet->end(); itr++) {
391 (*itr)->setPathScore(HUGE_VAL); //infinity by all practical means
392 (*itr)->setPreviousNode(0); //
393 (*itr)->setPreviousSeg(0); //
396 FGTaxiNode *firstNode = findNode(start);
397 firstNode->setPathScore(0);
399 FGTaxiNode *lastNode = findNode(end);
401 FGTaxiNodeVector unvisited(*currNodesSet); // working copy
403 while (!unvisited.empty()) {
404 FGTaxiNode *best = *(unvisited.begin());
405 for (FGTaxiNodeVectorIterator
406 itr = unvisited.begin(); itr != unvisited.end(); itr++) {
407 if ((*itr)->getPathScore() < best->getPathScore())
411 FGTaxiNodeVectorIterator newend =
412 remove(unvisited.begin(), unvisited.end(), best);
413 unvisited.erase(newend, unvisited.end());
415 if (best == lastNode) { // found route or best not connected
418 for (FGTaxiSegmentVectorIterator
419 seg = best->getBeginRoute();
420 seg != best->getEndRoute(); seg++) {
421 if (fullSearch || (*seg)->isPushBack()) {
422 FGTaxiNode *tgt = (*seg)->getEnd();
424 best->getPathScore() + (*seg)->getLength() +
425 (*seg)->getPenalty(nParkings);
426 if (alt < tgt->getPathScore()) { // Relax (u,v)
427 tgt->setPathScore(alt);
428 tgt->setPreviousNode(best);
429 tgt->setPreviousSeg(*seg); //
432 // // cerr << "Skipping TaxiSegment " << (*seg)->getIndex() << endl;
438 if (lastNode->getPathScore() == HUGE_VAL) {
439 // no valid route found
441 SG_LOG(SG_GENERAL, SG_ALERT,
442 "Failed to find route from waypoint " << start << " to "
443 << end << " at " << parent->getId());
447 //exit(1); //TODO exit more gracefully, no need to stall the whole sim with broken GN's
449 // assemble route from backtrace information
450 intVec nodes, routes;
451 FGTaxiNode *bt = lastNode;
452 while (bt->getPreviousNode() != 0) {
453 nodes.push_back(bt->getIndex());
454 routes.push_back(bt->getPreviousSegment()->getIndex());
455 bt = bt->getPreviousNode();
457 nodes.push_back(start);
458 reverse(nodes.begin(), nodes.end());
459 reverse(routes.begin(), routes.end());
461 return FGTaxiRoute(nodes, routes, lastNode->getPathScore(), 0);
465 int FGTaxiSegment::getPenalty(int nGates)
468 if (end->getIndex() < nGates) {
471 if (end->getIsOnRunway()) { // For now. In future versions, need to find out whether runway is active.
477 /* ATC Related Functions */
479 void FGGroundNetwork::announcePosition(int id,
480 FGAIFlightPlan * intendedRoute,
481 int currentPosition, double lat,
482 double lon, double heading,
483 double speed, double alt,
484 double radius, int leg,
485 FGAIAircraft * aircraft)
488 TrafficVectorIterator i = activeTraffic.begin();
489 // Search search if the current id alread has an entry
490 // This might be faster using a map instead of a vector, but let's start by taking a safe route
491 if (activeTraffic.size()) {
492 //while ((i->getId() != id) && i != activeTraffic.end()) {
493 while (i != activeTraffic.end()) {
494 if (i->getId() == id) {
500 // Add a new TrafficRecord if no one exsists for this aircraft.
501 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
505 rec.setPositionAndIntentions(currentPosition, intendedRoute);
506 rec.setPositionAndHeading(lat, lon, heading, speed, alt);
507 rec.setRadius(radius); // only need to do this when creating the record.
508 rec.setAircraft(aircraft);
509 activeTraffic.push_back(rec);
511 i->setPositionAndIntentions(currentPosition, intendedRoute);
512 i->setPositionAndHeading(lat, lon, heading, speed, alt);
517 void FGGroundNetwork::signOff(int id)
519 TrafficVectorIterator i = activeTraffic.begin();
520 // Search search if the current id alread has an entry
521 // This might be faster using a map instead of a vector, but let's start by taking a safe route
522 if (activeTraffic.size()) {
523 //while ((i->getId() != id) && i != activeTraffic.end()) {
524 while (i != activeTraffic.end()) {
525 if (i->getId() == id) {
531 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
532 SG_LOG(SG_GENERAL, SG_ALERT,
533 "AI error: Aircraft without traffic record is signing off");
535 i = activeTraffic.erase(i);
539 * The ground network can deal with the following states:
540 * 0 = Normal; no action required
541 * 1 = "Acknowledge "Hold position
542 * 2 = "Acknowledge "Resume taxi".
543 * 3 = "Issue TaxiClearance"
544 * 4 = =Acknowledge Taxi Clearance"
546 *************************************************************************************************************************/
547 bool FGGroundNetwork::checkTransmissionState(int minState, int maxState, TrafficVectorIterator i, time_t now, AtcMsgId msgId,
550 int state = i->getState();
551 if ((state >= minState) && (state <= maxState) && available) {
552 if ((msgDir == ATC_AIR_TO_GROUND) && isUserAircraft(i->getAircraft())) {
553 //cerr << "Checking state " << state << " for " << i->getAircraft()->getCallSign() << endl;
554 static SGPropertyNode_ptr trans_num = globals->get_props()->getNode("/sim/atc/transmission-num", true);
555 int n = trans_num->getIntValue();
557 trans_num->setIntValue(-1);
559 cerr << "Selected transmission message " << n << endl;
560 FGATCManager *atc = (FGATCManager*) globals->get_subsystem("atc");
561 atc->getATCDialog()->removeEntry(1);
563 //cerr << "creating message for " << i->getAircraft()->getCallSign() << endl;
564 transmit(&(*i), msgId, msgDir, false);
568 transmit(&(*i), msgId, msgDir, true);
570 lastTransmission = now;
577 void FGGroundNetwork::updateAircraftInformation(int id, double lat, double lon,
578 double heading, double speed, double alt,
581 // Check whether aircraft are on hold due to a preceding pushback. If so, make sure to
582 // Transmit air-to-ground "Ready to taxi request:
583 // Transmit ground to air approval / hold
584 // Transmit confirmation ...
585 // Probably use a status mechanism similar to the Engine start procedure in the startup controller.
588 TrafficVectorIterator i = activeTraffic.begin();
589 // Search search if the current id has an entry
590 // This might be faster using a map instead of a vector, but let's start by taking a safe route
591 TrafficVectorIterator current, closest;
592 if (activeTraffic.size()) {
593 //while ((i->getId() != id) && i != activeTraffic.end()) {
594 while (i != activeTraffic.end()) {
595 if (i->getId() == id) {
601 // update position of the current aircraft
602 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
603 SG_LOG(SG_GENERAL, SG_ALERT,
604 "AI error: updating aircraft without traffic record");
606 i->setPositionAndHeading(lat, lon, heading, speed, alt);
612 // Update every three secs, but add some randomness
613 // to prevent all IA objects doing this in synchrony
614 //if (getDt() < (3.0) + (rand() % 10))
618 current->clearResolveCircularWait();
619 current->setWaitsForId(0);
620 checkSpeedAdjustment(id, lat, lon, heading, speed, alt);
621 bool needsTaxiClearance = current->getAircraft()->getTaxiClearanceRequest();
622 if (!needsTaxiClearance) {
623 checkHoldPosition(id, lat, lon, heading, speed, alt);
624 if (checkForCircularWaits(id)) {
625 i->setResolveCircularWait();
628 current->setHoldPosition(true);
629 int state = current->getState();
630 time_t now = time(NULL) + fgGetLong("/sim/time/warp");
631 if ((now - lastTransmission) > 15) {
634 if (checkTransmissionState(0,2, current, now, MSG_REQUEST_TAXI_CLEARANCE, ATC_AIR_TO_GROUND)) {
635 current->setState(3);
637 if (checkTransmissionState(3,3, current, now, MSG_ISSUE_TAXI_CLEARANCE, ATC_GROUND_TO_AIR)) {
638 current->setState(4);
640 if (checkTransmissionState(4,4, current, now, MSG_ACKNOWLEDGE_TAXI_CLEARANCE, ATC_AIR_TO_GROUND)) {
641 current->setState(5);
643 if ((state == 5) && available) {
644 current->setState(0);
645 current->getAircraft()->setTaxiClearanceRequest(false);
646 current->setHoldPosition(false);
655 Scan for a speed adjustment change. Find the nearest aircraft that is in front
656 and adjust speed when we get too close. Only do this when current position and/or
657 intentions of the current aircraft match current taxiroute position of the proximate
658 aircraft. For traffic that is on other routes we need to issue a "HOLD Position"
659 instruction. See below for the hold position instruction.
661 Note that there currently still is one flaw in the logic that needs to be addressed.
662 There can be situations where one aircraft is in front of the current aircraft, on a separate
663 route, but really close after an intersection coming off the current route. This
664 aircraft is still close enough to block the current aircraft. This situation is currently
665 not addressed yet, but should be.
668 void FGGroundNetwork::checkSpeedAdjustment(int id, double lat,
669 double lon, double heading,
670 double speed, double alt)
673 TrafficVectorIterator current, closest;
674 TrafficVectorIterator i = activeTraffic.begin();
675 bool otherReasonToSlowDown = false;
676 bool previousInstruction;
677 if (activeTraffic.size()) {
678 //while ((i->getId() != id) && (i != activeTraffic.end()))
679 while (i != activeTraffic.end()) {
680 if (i->getId() == id) {
688 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
689 SG_LOG(SG_GENERAL, SG_ALERT,
690 "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkSpeedAdjustment");
695 previousInstruction = current->getSpeedAdjustment();
696 double mindist = HUGE_VAL;
697 if (activeTraffic.size()) {
698 double course, dist, bearing, minbearing, az2;
699 SGGeod curr(SGGeod::fromDegM(lon, lat, alt));
700 //TrafficVector iterator closest;
702 for (TrafficVectorIterator i = activeTraffic.begin();
703 i != activeTraffic.end(); i++) {
708 SGGeod other(SGGeod::fromDegM(i->getLongitude(),
711 SGGeodesy::inverse(curr, other, course, az2, dist);
712 bearing = fabs(heading - course);
714 bearing = 360 - bearing;
715 if ((dist < mindist) && (bearing < 60.0)) {
718 minbearing = bearing;
721 //Check traffic at the tower controller
722 if (towerController->hasActiveTraffic()) {
723 for (TrafficVectorIterator i =
724 towerController->getActiveTraffic().begin();
725 i != towerController->getActiveTraffic().end(); i++) {
726 //cerr << "Comparing " << current->getId() << " and " << i->getId() << endl;
727 SGGeod other(SGGeod::fromDegM(i->getLongitude(),
730 SGGeodesy::inverse(curr, other, course, az2, dist);
731 bearing = fabs(heading - course);
733 bearing = 360 - bearing;
734 if ((dist < mindist) && (bearing < 60.0)) {
735 //cerr << "Current aircraft " << current->getAircraft()->getTrafficRef()->getCallSign()
736 // << " is closest to " << i->getAircraft()->getTrafficRef()->getCallSign()
737 // << ", which has status " << i->getAircraft()->isScheduledForTakeoff()
741 minbearing = bearing;
742 otherReasonToSlowDown = true;
746 // Finally, check UserPosition
747 // Note, as of 2011-08-01, this should no longer be necessecary.
749 double userLatitude = fgGetDouble("/position/latitude-deg");
750 double userLongitude = fgGetDouble("/position/longitude-deg");
751 SGGeod user(SGGeod::fromDeg(userLongitude, userLatitude));
752 SGGeodesy::inverse(curr, user, course, az2, dist);
754 bearing = fabs(heading - course);
756 bearing = 360 - bearing;
757 if ((dist < mindist) && (bearing < 60.0)) {
760 minbearing = bearing;
761 otherReasonToSlowDown = true;
764 current->clearSpeedAdjustment();
766 if (current->checkPositionAndIntentions(*closest)
767 || otherReasonToSlowDown) {
768 double maxAllowableDistance =
769 (1.1 * current->getRadius()) +
770 (1.1 * closest->getRadius());
771 if (mindist < 2 * maxAllowableDistance) {
772 if (current->getId() == closest->getWaitsForId())
775 current->setWaitsForId(closest->getId());
776 if (closest->getId() != current->getId()) {
777 current->setSpeedAdjustment(closest->getSpeed() *
780 closest->getAircraft()->getTakeOffStatus() &&
781 (current->getAircraft()->getTrafficRef()->getDepartureAirport() == closest->getAircraft()->getTrafficRef()->getDepartureAirport()) &&
782 (current->getAircraft()->GetFlightPlan()->getRunway() == closest->getAircraft()->GetFlightPlan()->getRunway())
784 current->getAircraft()->scheduleForATCTowerDepartureControl(1);
786 current->setSpeedAdjustment(0); // This can only happen when the user aircraft is the one closest
788 if (mindist < maxAllowableDistance) {
789 //double newSpeed = (maxAllowableDistance-mindist);
790 //current->setSpeedAdjustment(newSpeed);
791 //if (mindist < 0.5* maxAllowableDistance)
793 current->setSpeedAdjustment(0);
802 Check for "Hold position instruction".
803 The hold position should be issued under the following conditions:
804 1) For aircraft entering or crossing a runway with active traffic on it, or landing aircraft near it
805 2) For taxiing aircraft that use one taxiway in opposite directions
806 3) For crossing or merging taxiroutes.
809 void FGGroundNetwork::checkHoldPosition(int id, double lat,
810 double lon, double heading,
811 double speed, double alt)
813 TrafficVectorIterator current;
814 TrafficVectorIterator i = activeTraffic.begin();
815 if (activeTraffic.size()) {
816 //while ((i->getId() != id) && i != activeTraffic.end())
817 while (i != activeTraffic.end()) {
818 if (i->getId() == id) {
826 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
827 SG_LOG(SG_GENERAL, SG_ALERT,
828 "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkHoldPosition");
831 bool origStatus = current->hasHoldPosition();
832 current->setHoldPosition(false);
833 SGGeod curr(SGGeod::fromDegM(lon, lat, alt));
835 for (i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
836 if (i->getId() != current->getId()) {
837 int node = current->crosses(this, *i);
839 FGTaxiNode *taxiNode = findNode(node);
841 // Determine whether it's save to continue or not.
842 // If we have a crossing route, there are two possibilities:
843 // 1) This is an interestion
844 // 2) This is oncoming two-way traffic, using the same taxiway.
845 //cerr << "Hold check 1 : " << id << " has common node " << node << endl;
847 SGGeod other(SGGeod::
848 fromDegM(i->getLongitude(), i->getLatitude(),
852 if (current->isOpposing(this, *i, node)) {
855 //cerr << "Hold check 2 : " << node << " has opposing segment " << endl;
856 // issue a "Hold Position" as soon as we're close to the offending node
857 // For now, I'm doing this as long as the other aircraft doesn't
858 // have a hold instruction as soon as we're within a reasonable
859 // distance from the offending node.
860 // This may be a bit of a conservative estimate though, as it may
861 // be well possible that both aircraft can both continue to taxi
862 // without crashing into each other.
865 if (SGGeodesy::distanceM(other, taxiNode->getGeod()) > 200) // 2.0*i->getRadius())
868 //cerr << "Hold check 3 : " << id <<" Other aircraft approaching node is still far away. (" << dist << " nm). Can safely continue "
872 //cerr << "Hold check 4: " << id << " Would need to wait for other aircraft : distance = " << dist << " meters" << endl;
877 SGGeodesy::distanceM(curr, taxiNode->getGeod());
878 if (!(i->hasHoldPosition())) {
880 if ((dist < 200) && //2.5*current->getRadius()) &&
881 (needsToWait) && (i->onRoute(this, *current)) &&
882 //((i->onRoute(this, *current)) || ((!(i->getSpeedAdjustment())))) &&
883 (!(current->getId() == i->getWaitsForId())))
884 //(!(i->getSpeedAdjustment()))) // &&
885 //(!(current->getSpeedAdjustment())))
888 if (!(isUserAircraft(i->getAircraft()))) { // test code. Don't wait for the user, let the user wait for you.
889 current->setHoldPosition(true);
890 current->setWaitsForId(i->getId());
892 //cerr << "Hold check 5: " << current->getCallSign() <<" Setting Hold Position: distance to node (" << node << ") "
893 // << dist << " meters. Waiting for " << i->getCallSign();
895 //cerr <<" [opposing] " << endl;
897 // cerr << "[non-opposing] " << endl;
898 //if (i->hasSpeefAdjustment())
900 // cerr << " (which in turn waits for ) " << i->
902 //cerr << "Hold check 6: " << id << " No need to hold yet: Distance to node : " << dist << " nm"<< endl;
908 bool currStatus = current->hasHoldPosition();
909 current->setHoldPosition(origStatus);
910 // Either a Hold Position or a resume taxi transmission has been issued
911 time_t now = time(NULL) + fgGetLong("/sim/time/warp");
912 if ((now - lastTransmission) > 2) {
915 if (current->getState() == 0) {
916 if ((origStatus != currStatus) && available) {
917 cerr << "Issueing hold short instrudtion " << currStatus << " " << available << endl;
918 if (currStatus == true) { // No has a hold short instruction
919 transmit(&(*current), MSG_HOLD_POSITION, ATC_GROUND_TO_AIR, true);
920 cerr << "Transmittin hold short instrudtion " << currStatus << " " << available << endl;
921 current->setState(1);
923 transmit(&(*current), MSG_RESUME_TAXI, ATC_GROUND_TO_AIR, true);
924 cerr << "Transmittig resume instrudtion " << currStatus << " " << available << endl;
925 current->setState(2);
927 lastTransmission = now;
929 // Don't act on the changed instruction until the transmission is confirmed
930 // So set back to original status
931 //cerr << "Current state " << current->getState() << endl;
934 //int state = current->getState();
935 if (checkTransmissionState(1,1, current, now, MSG_ACKNOWLEDGE_HOLD_POSITION, ATC_AIR_TO_GROUND)) {
936 current->setState(0);
937 current->setHoldPosition(true);
939 if (checkTransmissionState(2,2, current, now, MSG_ACKNOWLEDGE_RESUME_TAXI, ATC_AIR_TO_GROUND)) {
940 current->setState(0);
941 current->setHoldPosition(false);
946 * Check whether situations occur where the current aircraft is waiting for itself
947 * due to higher order interactions.
948 * A 'circular' wait is a situation where a waits for b, b waits for c, and c waits
949 * for a. Ideally each aircraft only waits for one other aircraft, so by tracing
950 * through this list of waiting aircraft, we can check if we'd eventually end back
951 * at the current aircraft.
953 * Note that we should consider the situation where we are actually checking aircraft
954 * d, which is waiting for aircraft a. d is not part of the loop, but is held back by
955 * the looping aircraft. If we don't check for that, this function will get stuck into
959 bool FGGroundNetwork::checkForCircularWaits(int id)
961 //cerr << "Performing Wait check " << id << endl;
963 TrafficVectorIterator current, other;
964 TrafficVectorIterator i = activeTraffic.begin();
965 int trafficSize = activeTraffic.size();
967 while (i != activeTraffic.end()) {
968 if (i->getId() == id) {
976 if (i == activeTraffic.end() || (trafficSize == 0)) {
977 SG_LOG(SG_GENERAL, SG_ALERT,
978 "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkForCircularWaits");
982 target = current->getWaitsForId();
983 //bool printed = false; // Note that this variable is for debugging purposes only.
987 //cerr << "aircraft waits for user" << endl;
992 while ((target > 0) && (target != id) && counter++ < trafficSize) {
994 TrafficVectorIterator i = activeTraffic.begin();
996 //while ((i->getId() != id) && i != activeTraffic.end())
997 while (i != activeTraffic.end()) {
998 if (i->getId() == target) {
1006 if (i == activeTraffic.end() || (trafficSize == 0)) {
1007 //cerr << "[Waiting for traffic at Runway: DONE] " << endl << endl;;
1008 // The target id is not found on the current network, which means it's at the tower
1009 //SG_LOG(SG_GENERAL, SG_ALERT, "AI error: Trying to access non-existing aircraft in FGGroundNetwork::checkForCircularWaits");
1013 target = other->getWaitsForId();
1015 // actually this trap isn't as impossible as it first seemed:
1016 // the setWaitsForID(id) is set to current when the aircraft
1017 // is waiting for the user controlled aircraft.
1018 //if (current->getId() == other->getId()) {
1019 // cerr << "Caught the impossible trap" << endl;
1020 // cerr << "Current = " << current->getId() << endl;
1021 // cerr << "Other = " << other ->getId() << endl;
1022 // for (TrafficVectorIterator at = activeTraffic.begin();
1023 // at != activeTraffic.end();
1025 // cerr << "currently active aircraft : " << at->getCallSign() << " with Id " << at->getId() << " waits for " << at->getWaitsForId() << endl;
1028 if (current->getId() == other->getId())
1031 //cerr << current->getCallSign() << " (" << current->getId() << ") " << " -> " << other->getCallSign()
1032 // << " (" << other->getId() << "); " << endl;;
1042 // cerr << "[done] " << endl << endl;;
1044 SG_LOG(SG_GENERAL, SG_WARN,
1045 "Detected circular wait condition: Id = " << id <<
1046 "target = " << target);
1053 // Note that this function is probably obsolete...
1054 bool FGGroundNetwork::hasInstruction(int id)
1056 TrafficVectorIterator i = activeTraffic.begin();
1057 // Search search if the current id has an entry
1058 // This might be faster using a map instead of a vector, but let's start by taking a safe route
1059 if (activeTraffic.size()) {
1060 //while ((i->getId() != id) && i != activeTraffic.end()) {
1061 while (i != activeTraffic.end()) {
1062 if (i->getId() == id) {
1068 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1069 SG_LOG(SG_GENERAL, SG_ALERT,
1070 "AI error: checking ATC instruction for aircraft without traffic record");
1072 return i->hasInstruction();
1077 FGATCInstruction FGGroundNetwork::getInstruction(int id)
1079 TrafficVectorIterator i = activeTraffic.begin();
1080 // Search search if the current id has an entry
1081 // This might be faster using a map instead of a vector, but let's start by taking a safe route
1082 if (activeTraffic.size()) {
1083 //while ((i->getId() != id) && i != activeTraffic.end()) {
1084 while (i != activeTraffic.end()) {
1085 if (i->getId() == id) {
1091 if (i == activeTraffic.end() || (activeTraffic.size() == 0)) {
1092 SG_LOG(SG_GENERAL, SG_ALERT,
1093 "AI error: requesting ATC instruction for aircraft without traffic record");
1095 return i->getInstruction();
1097 return FGATCInstruction();
1100 // Note that this function is copied from simgear. for maintanance purposes, it's probabtl better to make a general function out of that.
1101 static void WorldCoordinate(osg::Matrix& obj_pos, double lat,
1102 double lon, double elev, double hdg)
1104 SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
1105 obj_pos = geod.makeZUpFrame();
1106 // hdg is not a compass heading, but a counter-clockwise rotation
1107 // around the Z axis
1108 obj_pos.preMult(osg::Matrix::rotate(hdg * SGD_DEGREES_TO_RADIANS,
1115 void FGGroundNetwork::render(bool visible)
1118 SGMaterialLib *matlib = globals->get_matlib();
1121 globals->get_scenery()->get_scene_graph()->removeChild(group);
1122 //while (group->getNumChildren()) {
1123 // cerr << "Number of children: " << group->getNumChildren() << endl;
1124 simgear::EffectGeode* geode = (simgear::EffectGeode*) group->getChild(0);
1125 //osg::MatrixTransform *obj_trans = (osg::MatrixTransform*) group->getChild(0);
1126 //geode->releaseGLObjects();
1127 //group->removeChild(geode);
1132 group = new osg::Group;
1134 //for ( FGTaxiSegmentVectorIterator i = segments.begin(); i != segments.end(); i++) {
1136 for (TrafficVectorIterator i = activeTraffic.begin(); i != activeTraffic.end(); i++) {
1137 // Handle start point
1138 int pos = i->getCurrentPosition() - 1;
1141 SGGeod start(SGGeod::fromDeg((i->getLongitude()), (i->getLatitude())));
1142 SGGeod end (SGGeod::fromDeg(segments[pos]->getEnd()->getLongitude(), segments[pos]->getEnd()->getLatitude()));
1144 double length = SGGeodesy::distanceM(start, end);
1145 //heading = SGGeodesy::headingDeg(start->getGeod(), end->getGeod());
1147 double az2, heading; //, distanceM;
1148 SGGeodesy::inverse(start, end, heading, az2, length);
1149 double coveredDistance = length * 0.5;
1151 SGGeodesy::direct(start, heading, coveredDistance, center, az2);
1152 //cerr << "Active Aircraft : Centerpoint = (" << center.getLatitudeDeg() << ", " << center.getLongitudeDeg() << "). Heading = " << heading << endl;
1153 ///////////////////////////////////////////////////////////////////////////////
1154 // Make a helper function out of this
1155 osg::Matrix obj_pos;
1156 osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
1157 obj_trans->setDataVariance(osg::Object::STATIC);
1159 WorldCoordinate( obj_pos, center.getLatitudeDeg(), center.getLongitudeDeg(), parent->elevation()+8+dx, -(heading) );
1161 obj_trans->setMatrix( obj_pos );
1162 //osg::Vec3 center(0, 0, 0)
1164 float width = length /2.0;
1165 osg::Vec3 corner(-width, 0, 0.25f);
1166 osg::Vec3 widthVec(2*width + 1, 0, 0);
1167 osg::Vec3 heightVec(0, 1, 0);
1168 osg::Geometry* geometry;
1169 geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
1170 simgear::EffectGeode* geode = new simgear::EffectGeode;
1171 geode->setName("test");
1172 geode->addDrawable(geometry);
1173 //osg::Node *custom_obj;
1174 SGMaterial *mat = matlib->find("UnidirectionalTaper");
1176 geode->setEffect(mat->get_effect());
1177 obj_trans->addChild(geode);
1178 // wire as much of the scene graph together as we can
1179 //->addChild( obj_trans );
1180 group->addChild( obj_trans );
1181 /////////////////////////////////////////////////////////////////////
1183 //cerr << "BIG FAT WARNING: current position is here : " << pos << endl;
1185 for(intVecIterator j = (i)->getIntentions().begin(); j != (i)->getIntentions().end(); j++) {
1186 osg::Matrix obj_pos;
1189 osg::MatrixTransform *obj_trans = new osg::MatrixTransform;
1190 obj_trans->setDataVariance(osg::Object::STATIC);
1192 WorldCoordinate( obj_pos, segments[k]->getLatitude(), segments[k]->getLongitude(), parent->elevation()+8+dx, -(segments[k]->getHeading()) );
1194 obj_trans->setMatrix( obj_pos );
1195 //osg::Vec3 center(0, 0, 0)
1197 float width = segments[k]->getLength() /2.0;
1198 osg::Vec3 corner(-width, 0, 0.25f);
1199 osg::Vec3 widthVec(2*width + 1, 0, 0);
1200 osg::Vec3 heightVec(0, 1, 0);
1201 osg::Geometry* geometry;
1202 geometry = osg::createTexturedQuadGeometry(corner, widthVec, heightVec);
1203 simgear::EffectGeode* geode = new simgear::EffectGeode;
1204 geode->setName("test");
1205 geode->addDrawable(geometry);
1206 //osg::Node *custom_obj;
1207 SGMaterial *mat = matlib->find("UnidirectionalTaper");
1209 geode->setEffect(mat->get_effect());
1210 obj_trans->addChild(geode);
1211 // wire as much of the scene graph together as we can
1212 //->addChild( obj_trans );
1213 group->addChild( obj_trans );
1218 globals->get_scenery()->get_scene_graph()->addChild(group);
1222 string FGGroundNetwork::getName() {
1223 return string(parent->getId() + "-ground");