2 // simple.cxx -- a really simplistic class to manage airport ID,
3 // lat, lon of the center of one of it's runways, and
6 // Written by Curtis Olson, started April 1998.
7 // Updated by Durk Talsma, started December, 2004.
9 // Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt
11 // This program is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU General Public License as
13 // published by the Free Software Foundation; either version 2 of the
14 // License, or (at your option) any later version.
16 // This program is distributed in the hope that it will be useful, but
17 // WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
32 # define _USE_MATH_DEFINES
37 #include <simgear/compiler.h>
42 #include <Environment/environment_mgr.hxx>
43 #include <Environment/environment.hxx>
44 #include <simgear/misc/sg_path.hxx>
45 #include <simgear/props/props.hxx>
46 #include <simgear/structure/subsystem_mgr.hxx>
47 #include <simgear/debug/logstream.hxx>
48 #include <Main/globals.hxx>
49 #include <Main/fg_props.hxx>
50 #include <Airports/runways.hxx>
57 SG_USING_STD(random_shuffle);
60 /******************************************************************************
62 ***************e*************************************************************/
63 void ScheduleTime::clear()
67 scheduleNames.clear();
71 ScheduleTime::ScheduleTime(const ScheduleTime &other)
74 timeVecConstIterator i;
75 for (i = other.start.begin(); i != other.start.end(); i++)
77 for (i = other.end.begin(); i != other.end.end(); i++)
79 stringVecConstIterator k;
80 for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
81 scheduleNames.push_back(*k);
84 //stringVec scheduleNames;
85 tailWind = other.tailWind;
86 crssWind = other.tailWind;
90 ScheduleTime & ScheduleTime::operator= (const ScheduleTime &other)
94 timeVecConstIterator i;
95 for (i = other.start.begin(); i != other.start.end(); i++)
97 for (i = other.end.begin(); i != other.end.end(); i++)
99 stringVecConstIterator k;
100 for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
101 scheduleNames.push_back(*k);
104 //stringVec scheduleNames;
105 tailWind = other.tailWind;
106 crssWind = other.tailWind;
109 string ScheduleTime::getName(time_t dayStart)
111 if ((start.size() != end.size()) || (start.size() != scheduleNames.size()))
113 cerr << "Unable to parse schedule times" << endl;
118 int nrItems = start.size();
119 //cerr << "Nr of items to process: " << nrItems << endl;
122 for (unsigned int i = 0; i < start.size(); i++)
125 if ((dayStart >= start[i]) && (dayStart <= end[i]))
126 return scheduleNames[i];
129 //couldn't find one so return 0;
130 //cerr << "Returning 0 " << endl;
134 /******************************************************************************
136 *****************************************************************************/
138 RunwayList::RunwayList(const RunwayList &other)
141 stringVecConstIterator i;
142 for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
143 preferredRunways.push_back(*i);
145 RunwayList& RunwayList::operator= (const RunwayList &other)
148 preferredRunways.clear();
149 stringVecConstIterator i;
150 for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
151 preferredRunways.push_back(*i);
154 void RunwayList::set(const string &tp, const string &lst)
156 //weekday = atoi(timeCopy.substr(0,1).c_str());
157 // timeOffsetInDays = weekday - currTimeDate->getGmt()->tm_wday;
158 // timeCopy = timeCopy.substr(2,timeCopy.length());
162 while (rwys.find(",") != string::npos)
164 rwy = rwys.substr(0, rwys.find(",",0));
165 //cerr << "adding runway [" << rwy << "] to the list " << endl;
166 preferredRunways.push_back(rwy);
167 rwys.erase(0, rwys.find(",",0)+1); // erase until after the first whitspace
168 while (rwys[0] == ' ')
169 rwys.erase(0, 1); // Erase any leading whitespaces.
170 //cerr << "Remaining runway list " << rwys;
172 preferredRunways.push_back(rwys);
176 void RunwayList::clear()
179 preferredRunways.clear();
181 /****************************************************************************
183 ***************************************************************************/
185 RunwayGroup::RunwayGroup(const RunwayGroup &other)
188 RunwayListVecConstIterator i;
189 for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
190 rwyList.push_back(*i);
191 choice[0] = other.choice[0];
192 choice[1] = other.choice[1];
193 nrActive = other.nrActive;
195 RunwayGroup& RunwayGroup:: operator= (const RunwayGroup &other)
199 RunwayListVecConstIterator i;
200 for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
201 rwyList.push_back(*i);
202 choice[0] = other.choice[0];
203 choice[1] = other.choice[1];
204 nrActive = other.nrActive;
208 void RunwayGroup::setActive(const string &aptId,
216 int activeRwys = rwyList.size(); // get the number of runways active
218 // bool found = true;
227 nrOfPreferences = rwyList[0].getRwyList()->size();
228 for (int i = 0; i < nrOfPreferences; i++)
230 bool validSelection = true;
231 for (int j = 0; j < activeRwys; j++)
233 //cerr << "I J " << i << " " << j << endl;
234 name = rwyList[j].getRwyList(i);
235 //cerr << "Name of Runway: " << name << endl;
236 if (globals->get_runways()->search( aptId,
240 //cerr << "Succes" << endl;
241 hdgDiff = fabs(windHeading - rwy._heading);
242 //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
243 //cerr << "Wind Speed : " << windSpeed << endl;
245 hdgDiff = 360 - hdgDiff;
246 //cerr << "Heading diff: " << hdgDiff << endl;
247 hdgDiff *= ((2*M_PI)/360.0); // convert to radians
248 crossWind = windSpeed * sin(hdgDiff);
249 tailWind = -windSpeed * cos(hdgDiff);
250 //cerr << "Tailwind : " << tailWind << endl;
251 //cerr << "Crosswnd : " << crossWind << endl;
252 if ((tailWind > maxTail) || (crossWind > maxCross))
253 validSelection = false;
255 cerr << "Failed to find runway " << name << " at " << aptId << endl;
262 //cerr << "Valid runay selection : " << i << endl;
263 nrActive = activeRwys;
268 // If this didn't work, due to heavy winds, try again
269 // but select only one landing and one takeoff runway.
272 for (int i = activeRwys-1; i; i--)
274 if (rwyList[i].getType() == string("landing"))
276 if (rwyList[i].getType() == string("takeoff"))
279 //cerr << "Choosing " << choice[0] << " for landing and " << choice[1] << "for takeoff" << endl;
280 nrOfPreferences = rwyList[0].getRwyList()->size();
281 for (int i = 0; i < nrOfPreferences; i++)
283 bool validSelection = true;
284 for (int j = 0; j < 2; j++)
286 //cerr << "I J " << i << " " << j << endl;
287 name = rwyList[choice[j]].getRwyList(i);
288 //cerr << "Name of Runway: " << name << endl;
289 if (globals->get_runways()->search( aptId,
293 //cerr << "Succes" << endl;
294 hdgDiff = fabs(windHeading - rwy._heading);
295 //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
296 //cerr << "Wind Speed : " << windSpeed << endl;
298 hdgDiff = 360 - hdgDiff;
299 //cerr << "Heading diff: " << hdgDiff << endl;
300 hdgDiff *= ((2*M_PI)/360.0); // convert to radians
301 crossWind = windSpeed * sin(hdgDiff);
302 tailWind = -windSpeed * cos(hdgDiff);
303 //cerr << "Tailwind : " << tailWind << endl;
304 //cerr << "Crosswnd : " << crossWind << endl;
305 if ((tailWind > maxTail) || (crossWind > maxCross))
306 validSelection = false;
308 cerr << "Failed to find runway " << name << " at " << aptId << endl;
315 //cerr << "Valid runay selection : " << i << endl;
323 //RunwayListVectorIterator i; // = rwlist.begin();
324 //stringVecIterator j;
325 //for (i = rwyList.begin(); i != rwyList.end(); i++)
327 // cerr << i->getType();
328 // for (j = i->getRwyList()->begin(); j != i->getRwyList()->end(); j++)
338 void RunwayGroup::getActive(int i, string &name, string &type)
344 if (nrActive == (int)rwyList.size())
346 name = rwyList[i].getRwyList(active);
347 type = rwyList[i].getType();
351 name = rwyList[choice[i]].getRwyList(active);
352 type = rwyList[choice[i]].getType();
355 /*****************************************************************************
356 * FGRunway preference
357 ****************************************************************************/
358 FGRunwayPreference::FGRunwayPreference()
360 //cerr << "Running default Constructor" << endl;
364 FGRunwayPreference::FGRunwayPreference(const FGRunwayPreference &other)
366 initialized = other.initialized;
368 scheduleName = other.scheduleName;
370 comTimes = other.comTimes; // Commercial Traffic;
371 genTimes = other.genTimes; // General Aviation;
372 milTimes = other.milTimes; // Military Traffic;
373 currTimes= other.currTimes; // Needed for parsing;
375 rwyList = other.rwyList;
376 rwyGroup = other.rwyGroup;
377 PreferenceListConstIterator i;
378 for (i = other.preferences.begin(); i != other.preferences.end(); i++)
379 preferences.push_back(*i);
382 FGRunwayPreference & FGRunwayPreference::operator= (const FGRunwayPreference &other)
384 initialized = other.initialized;
386 scheduleName = other.scheduleName;
388 comTimes = other.comTimes; // Commercial Traffic;
389 genTimes = other.genTimes; // General Aviation;
390 milTimes = other.milTimes; // Military Traffic;
391 currTimes= other.currTimes; // Needed for parsing;
393 rwyList = other.rwyList;
394 rwyGroup = other.rwyGroup;
395 PreferenceListConstIterator i;
397 for (i = other.preferences.begin(); i != other.preferences.end(); i++)
398 preferences.push_back(*i);
402 ScheduleTime *FGRunwayPreference::getSchedule(const char *trafficType)
404 if (!(strcmp(trafficType, "com"))) {
407 if (!(strcmp(trafficType, "gen"))) {
410 if (!(strcmp(trafficType, "mil"))) {
416 RunwayGroup *FGRunwayPreference::getGroup(const string &groupName)
418 PreferenceListIterator i = preferences.begin();
419 if (preferences.begin() == preferences.end())
421 while (!(i == preferences.end() || i->getName() == groupName))
423 if (i != preferences.end())
429 void FGRunwayPreference::startXML () {
430 // cout << "Start XML" << endl;
433 void FGRunwayPreference::endXML () {
434 cout << "End XML" << endl;
437 void FGRunwayPreference::startElement (const char * name, const XMLAttributes &atts) {
438 //cout << "StartElement " << name << endl;
440 if (!(strcmp(name, "wind"))) {
441 //cerr << "Will be processing Wind" << endl;
442 for (int i = 0; i < atts.size(); i++)
444 //cout << " " << atts.getName(i) << '=' << atts.getValue(i) << endl;
445 //attname = atts.getName(i);
446 if (atts.getName(i) == string("tail")) {
447 //cerr << "Tail Wind = " << atts.getValue(i) << endl;
448 currTimes.setTailWind(atof(atts.getValue(i)));
450 if (atts.getName(i) == string("cross")) {
451 //cerr << "Cross Wind = " << atts.getValue(i) << endl;
452 currTimes.setCrossWind(atof(atts.getValue(i)));
456 if (!(strcmp(name, "time"))) {
457 //cerr << "Will be processing time" << endl;
458 for (int i = 0; i < atts.size(); i++)
460 if (atts.getName(i) == string("start")) {
461 //cerr << "Start Time = " << atts.getValue(i) << endl;
462 currTimes.addStartTime(processTime(atts.getValue(i)));
464 if (atts.getName(i) == string("end")) {
465 //cerr << "End time = " << atts.getValue(i) << endl;
466 currTimes.addEndTime(processTime(atts.getValue(i)));
468 if (atts.getName(i) == string("schedule")) {
469 //cerr << "Schedule Name = " << atts.getValue(i) << endl;
470 currTimes.addScheduleName(atts.getValue(i));
474 if (!(strcmp(name, "takeoff"))) {
477 if (!(strcmp(name, "landing")))
481 if (!(strcmp(name, "schedule"))) {
482 for (int i = 0; i < atts.size(); i++)
484 //cout << " " << atts.getName(i) << '=' << atts.getValue(i) << endl;
485 //attname = atts.getName(i);
486 if (atts.getName(i) == string("name")) {
487 //cerr << "Schedule name = " << atts.getValue(i) << endl;
488 scheduleName = atts.getValue(i);
494 //based on a string containing hour and minute, return nr seconds since day start.
495 time_t FGRunwayPreference::processTime(const string &tme)
497 string hour = tme.substr(0, tme.find(":",0));
498 string minute = tme.substr(tme.find(":",0)+1, tme.length());
500 //cerr << "hour = " << hour << " Minute = " << minute << endl;
501 return (atoi(hour.c_str()) * 3600 + atoi(minute.c_str()) * 60);
504 void FGRunwayPreference::endElement (const char * name) {
505 //cout << "End element " << name << endl;
506 if (!(strcmp(name, "rwyuse"))) {
509 if (!(strcmp(name, "com"))) { // Commercial Traffic
510 //cerr << "Setting time table for commerical traffic" << endl;
511 comTimes = currTimes;
514 if (!(strcmp(name, "gen"))) { // General Aviation
515 //cerr << "Setting time table for general aviation" << endl;
516 genTimes = currTimes;
519 if (!(strcmp(name, "mil"))) { // Military Traffic
520 //cerr << "Setting time table for military traffic" << endl;
521 genTimes = currTimes;
525 if (!(strcmp(name, "takeoff"))) {
526 //cerr << "Adding takeoff: " << value << endl;
527 rwyList.set(name, value);
528 rwyGroup.add(rwyList);
530 if (!(strcmp(name, "landing"))) {
531 //cerr << "Adding landing: " << value << endl;
532 rwyList.set(name, value);
533 rwyGroup.add(rwyList);
535 if (!(strcmp(name, "schedule"))) {
536 //cerr << "Adding schedule" << scheduleName << endl;
537 rwyGroup.setName(scheduleName);
538 //rwyGroup.addRunways(rwyList);
539 preferences.push_back(rwyGroup);
545 void FGRunwayPreference::data (const char * s, int len) {
546 string token = string(s,len);
547 //cout << "Character data " << string(s,len) << endl;
548 //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
551 // value = string("");
555 void FGRunwayPreference::pi (const char * target, const char * data) {
556 //cout << "Processing instruction " << target << ' ' << data << endl;
559 void FGRunwayPreference::warning (const char * message, int line, int column) {
560 cout << "Warning: " << message << " (" << line << ',' << column << ')'
564 void FGRunwayPreference::error (const char * message, int line, int column) {
565 cout << "Error: " << message << " (" << line << ',' << column << ')'
569 /*****************************************************************************
570 * Helper function for parsing position string
571 ****************************************************************************/
572 double processPosition(const string &pos)
581 prefix= subs.substr(0,1);
582 if (prefix == string("S") || (prefix == string("W")))
584 subs = subs.substr(1, subs.length());
585 degree = subs.substr(0, subs.find(" ",0));
586 decimal = subs.substr(subs.find(" ",0), subs.length());
589 //cerr << sign << " "<< degree << " " << decimal << endl;
590 value = sign * (atof(degree.c_str()) + atof(decimal.c_str())/60.0);
591 //cerr << value <<endl;
597 /*********************************************************************************
599 ********************************************************************************/
600 FGParking::FGParking(double lat,
615 airlineCodes = codes;
619 /***************************************************************************
621 ***************************************************************************/
622 FGAirport::FGAirport() : _longitude(0), _latitude(0), _elevation(0)
625 for (int i = 0; i < 10; i++)
627 avWindHeading [i] = 0;
632 FGAirport::FGAirport(const FGAirport& other)
635 _longitude = other._longitude;
636 _latitude = other._latitude;
637 _elevation = other._elevation;
639 _has_metar = other._has_metar;
640 for (FGParkingVecConstIterator ip= other.parkings.begin(); ip != other.parkings.end(); ip++)
641 parkings.push_back(*(ip));
642 rwyPrefs = other.rwyPrefs;
643 lastUpdate = other.lastUpdate;
645 stringVecConstIterator il;
646 for (il = other.landing.begin(); il != other.landing.end(); il++)
647 landing.push_back(*il);
648 for (il = other.takeoff.begin(); il != other.takeoff.end(); il++)
649 takeoff.push_back(*il);
650 lastUpdate = other.lastUpdate;
651 for (int i = 0; i < 10; i++)
653 avWindHeading [i] = other.avWindHeading[i];
654 avWindSpeed [i] = other.avWindSpeed [i];
658 FGAirport::FGAirport(const string &id, double lon, double lat, double elev, const string &name, bool has_metar)
665 _has_metar = has_metar;
667 for (int i = 0; i < 10; i++)
669 avWindHeading [i] = 0;
675 // Initialization required after XMLRead
676 void FGAirport::init()
678 // This may seem a bit weird to first randomly shuffle the parkings
679 // and then sort them again. However, parkings are sorted here by ascending
680 // radius. Since many parkings have similar radii, with each radius class they will
681 // still be allocated relatively systematically. Randomizing prior to sorting will
682 // prevent any initial orderings to be destroyed, leading (hopefully) to a more
683 // naturalistic gate assignment.
684 random_shuffle(parkings.begin(), parkings.end());
685 sort(parkings.begin(), parkings.end());
686 // add the gate positions to the ground network.
687 groundNetwork.addNodes(&parkings);
688 groundNetwork.init();
691 bool FGAirport::getAvailableParking(double *lat, double *lon, double *heading, int *gateId, double rad, const string &flType, const string &acType, const string &airline)
694 bool available = false;
697 FGParkingVecIterator i;
698 // if (flType == "cargo")
700 // gateType = "RAMP_CARGO";
702 // else if (flType == "ga")
704 // gateType = "RAMP_GA";
706 // else gateType = "GATE";
708 if (parkings.begin() == parkings.end())
710 //cerr << "Could not find parking spot at " << _id << endl;
718 // First try finding a parking with a designated airline code
719 for (i = parkings.begin(); !(i == parkings.end() || found); i++)
721 //cerr << "Gate Id: " << i->getIndex()
722 // << " Type : " << i->getType()
723 // << " Codes : " << i->getCodes()
724 // << " Radius: " << i->getRadius()
725 // << " Name : " << i->getName()
726 // << " Available: " << i->isAvailable() << endl;
728 // Taken by another aircraft
729 if (!(i->isAvailable()))
734 // No airline codes, so skip
735 if (i->getCodes().empty())
740 else // Airline code doesn't match
741 if (i->getCodes().find(airline, 0) == string::npos)
746 // Type doesn't match
747 if (i->getType() != flType)
753 if (i->getRadius() < rad)
761 *lat = i->getLatitude ();
762 *lon = i->getLongitude();
763 *heading = i->getHeading ();
764 *gateId = i->getIndex ();
765 i->setAvailable(false);
769 // then try again for those without codes.
770 for (i = parkings.begin(); !(i == parkings.end() || found); i++)
773 if (!(i->isAvailable()))
778 if (!(i->getCodes().empty()))
780 if ((i->getCodes().find(airline,0) == string::npos))
786 if (i->getType() != flType)
792 if (i->getRadius() < rad)
800 *lat = i->getLatitude ();
801 *lon = i->getLongitude();
802 *heading = i->getHeading ();
803 *gateId = i->getIndex ();
804 i->setAvailable(false);
808 // And finally once more if that didn't work. Now ignore the airline codes, as a last resort
809 for (i = parkings.begin(); !(i == parkings.end() || found); i++)
812 if (!(i->isAvailable()))
817 if (i->getType() != flType)
823 if (i->getRadius() < rad)
831 *lat = i->getLatitude ();
832 *lon = i->getLongitude();
833 *heading = i->getHeading ();
834 *gateId = i->getIndex ();
835 i->setAvailable(false);
842 //cerr << "Traffic overflow at" << _id
843 // << ". flType = " << flType
844 // << ". airline = " << airline
845 // << " Radius = " <<rad
856 void FGAirport::getParking (int id, double *lat, double* lon, double *heading)
866 FGParkingVecIterator i = parkings.begin();
867 for (i = parkings.begin(); i != parkings.end(); i++)
869 if (id == i->getIndex())
871 *lat = i->getLatitude();
872 *lon = i->getLongitude();
873 *heading = i->getLongitude();
879 FGParking *FGAirport::getParking(int i)
881 if (i < (int)parkings.size())
882 return &(parkings[i]);
886 string FGAirport::getParkingName(int i)
888 if (i < (int)parkings.size() && i >= 0)
889 return (parkings[i].getName());
891 return string("overflow");
893 void FGAirport::releaseParking(int id)
898 FGParkingVecIterator i = parkings.begin();
899 for (i = parkings.begin(); i != parkings.end(); i++)
901 if (id == i->getIndex())
903 i -> setAvailable(true);
909 void FGAirport::startXML () {
910 //cout << "Start XML" << endl;
913 void FGAirport::endXML () {
914 //cout << "End XML" << endl;
917 void FGAirport::startElement (const char * name, const XMLAttributes &atts) {
918 // const char *attval;
921 FGTaxiSegment taxiSegment;
923 taxiSegment.setIndex(index);
924 //cout << "Start element " << name << endl;
931 if (name == string("Parking"))
933 for (int i = 0; i < atts.size(); i++)
935 //cout << " " << atts.getName(i) << '=' << atts.getValue(i) << endl;
936 attname = atts.getName(i);
937 if (attname == string("index"))
938 park.setIndex(atoi(atts.getValue(i)));
939 else if (attname == string("type"))
940 park.setType(atts.getValue(i));
941 else if (attname == string("name"))
942 gateName = atts.getValue(i);
943 else if (attname == string("number"))
944 gateNumber = atts.getValue(i);
945 else if (attname == string("lat"))
946 park.setLatitude(atts.getValue(i));
947 else if (attname == string("lon"))
948 park.setLongitude(atts.getValue(i));
949 else if (attname == string("heading"))
950 park.setHeading(atof(atts.getValue(i)));
951 else if (attname == string("radius")) {
952 string radius = atts.getValue(i);
953 if (radius.find("M") != string::npos)
954 radius = radius.substr(0, radius.find("M",0));
955 //cerr << "Radius " << radius <<endl;
956 park.setRadius(atof(radius.c_str()));
958 else if (attname == string("airlineCodes"))
959 park.setCodes(atts.getValue(i));
961 park.setName((gateName+gateNumber));
962 parkings.push_back(park);
964 if (name == string("node"))
966 for (int i = 0; i < atts.size() ; i++)
968 attname = atts.getName(i);
969 if (attname == string("index"))
970 taxiNode.setIndex(atoi(atts.getValue(i)));
971 if (attname == string("lat"))
972 taxiNode.setLatitude(atts.getValue(i));
973 if (attname == string("lon"))
974 taxiNode.setLongitude(atts.getValue(i));
976 groundNetwork.addNode(taxiNode);
978 if (name == string("arc"))
980 taxiSegment.setIndex(++index);
981 for (int i = 0; i < atts.size() ; i++)
983 attname = atts.getName(i);
984 if (attname == string("begin"))
985 taxiSegment.setStartNodeRef(atoi(atts.getValue(i)));
986 if (attname == string("end"))
987 taxiSegment.setEndNodeRef(atoi(atts.getValue(i)));
989 groundNetwork.addSegment(taxiSegment);
991 // sort by radius, in asending order, so that smaller gates are first in the list
994 void FGAirport::endElement (const char * name) {
995 //cout << "End element " << name << endl;
999 void FGAirport::data (const char * s, int len) {
1000 string token = string(s,len);
1001 //cout << "Character data " << string(s,len) << endl;
1002 //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
1005 //value = string("");
1008 void FGAirport::pi (const char * target, const char * data) {
1009 //cout << "Processing instruction " << target << ' ' << data << endl;
1012 void FGAirport::warning (const char * message, int line, int column) {
1013 cout << "Warning: " << message << " (" << line << ',' << column << ')'
1017 void FGAirport::error (const char * message, int line, int column) {
1018 cout << "Error: " << message << " (" << line << ',' << column << ')'
1022 void FGAirport::setRwyUse(const FGRunwayPreference& ref)
1025 //cerr << "Exiting due to not implemented yet" << endl;
1028 void FGAirport::getActiveRunway(const string &trafficType, int action, string &runway)
1037 if (!(rwyPrefs.available()))
1039 runway = chooseRunwayFallback();
1040 return; // generic fall back goes here
1044 RunwayGroup *currRunwayGroup = 0;
1045 int nrActiveRunways = 0;
1046 time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
1047 if (((dayStart - lastUpdate) > 600) || trafficType != prevTrafficType)
1051 //lastUpdate = dayStart;
1052 prevTrafficType = trafficType;
1055 stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1056 ->getEnvironment(getLatitude(),
1060 windSpeed = stationweather.get_wind_speed_kt();
1061 windHeading = stationweather.get_wind_from_heading_deg();
1062 double averageWindSpeed = 0;
1063 double averageWindHeading = 0;
1064 double cosHeading = 0;
1065 double sinHeading = 0;
1066 // Initialize at the beginning of the next day or startup
1067 if ((lastUpdate == 0) || (dayStart < lastUpdate))
1069 for (int i = 0; i < 10; i++)
1071 avWindHeading [i] = windHeading;
1072 avWindSpeed [i] = windSpeed;
1077 if (windSpeed != avWindSpeed[9]) // update if new metar data
1079 // shift the running average
1080 for (int i = 0; i < 9 ; i++)
1082 avWindHeading[i] = avWindHeading[i+1];
1083 avWindSpeed [i] = avWindSpeed [i+1];
1086 avWindHeading[9] = windHeading;
1087 avWindSpeed [9] = windSpeed;
1090 for (int i = 0; i < 10; i++)
1092 averageWindSpeed += avWindSpeed [i];
1093 //averageWindHeading += avWindHeading [i];
1094 cosHeading += cos(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1095 sinHeading += sin(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1097 averageWindSpeed /= 10;
1098 //averageWindHeading /= 10;
1101 averageWindHeading = atan2(sinHeading, cosHeading) *SG_RADIANS_TO_DEGREES;
1102 if (averageWindHeading < 0)
1103 averageWindHeading += 360.0;
1104 //cerr << "Wind Heading " << windHeading << " average " << averageWindHeading << endl;
1105 //cerr << "Wind Speed " << windSpeed << " average " << averageWindSpeed << endl;
1106 lastUpdate = dayStart;
1107 //if (wind_speed == 0) {
1108 // wind_heading = 270; This forces West-facing rwys to be used in no-wind situations
1109 // which is consistent with Flightgear's initial setup.
1112 //string rwy_no = globals->get_runways()->search(apt->getId(), int(wind_heading));
1113 string scheduleName;
1114 //cerr << "finding active Runway for" << _id << endl;
1115 //cerr << "Nr of seconds since day start << " << dayStart << endl;
1116 ScheduleTime *currSched;
1117 //cerr << "A"<< endl;
1118 currSched = rwyPrefs.getSchedule(trafficType.c_str());
1121 //cerr << "B"<< endl;
1122 scheduleName = currSched->getName(dayStart);
1123 maxTail = currSched->getTailWind ();
1124 maxCross = currSched->getCrossWind ();
1125 //cerr << "SChedule anme = " << scheduleName << endl;
1126 if (scheduleName.empty())
1128 //cerr << "C"<< endl;
1129 currRunwayGroup = rwyPrefs.getGroup(scheduleName);
1130 //cerr << "D"<< endl;
1131 if (!(currRunwayGroup))
1133 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1134 //cerr << "Nr of Active Runways = " << nrActiveRunways << endl;
1135 currRunwayGroup->setActive(_id, averageWindSpeed, averageWindHeading, maxTail, maxCross);
1136 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1137 for (int i = 0; i < nrActiveRunways; i++)
1139 type = "unknown"; // initialize to something other than landing or takeoff
1140 currRunwayGroup->getActive(i, name, type);
1141 if (type == "landing")
1143 landing.push_back(name);
1144 //cerr << "Landing " << name << endl;
1146 if (type == "takeoff")
1148 takeoff.push_back(name);
1149 //cerr << "takeoff " << name << endl;
1153 if (action == 1) // takeoff
1155 int nr = takeoff.size();
1158 runway = takeoff[(rand() % nr)];
1162 runway = chooseRunwayFallback();
1165 if (action == 2) // landing
1167 int nr = landing.size();
1170 runway = landing[(rand() % nr)];
1174 runway = chooseRunwayFallback();
1178 //runway = globals->get_runways()->search(_id, int(windHeading));
1179 //cerr << "Seleceted runway: " << runway << endl;
1183 string FGAirport::chooseRunwayFallback()
1186 stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1187 ->getEnvironment(getLatitude(),
1191 double windSpeed = stationweather.get_wind_speed_kt();
1192 double windHeading = stationweather.get_wind_from_heading_deg();
1193 if (windSpeed == 0) {
1194 windHeading = 270; // This forces West-facing rwys to be used in no-wind situations
1195 //which is consistent with Flightgear's initial setup.
1198 return globals->get_runways()->search(_id, int(windHeading));
1203 /**************************************************************************
1205 *************************************************************************/
1206 FGTaxiNode::FGTaxiNode()
1210 /***************************************************************************
1212 **************************************************************************/
1213 FGTaxiSegment::FGTaxiSegment()
1217 void FGTaxiSegment::setStart(FGTaxiNodeVector *nodes)
1219 FGTaxiNodeVectorIterator i = nodes->begin();
1220 while (i != nodes->end())
1222 if (i->getIndex() == startNode)
1224 start = i->getAddress();
1225 i->addSegment(this);
1232 void FGTaxiSegment::setEnd(FGTaxiNodeVector *nodes)
1234 FGTaxiNodeVectorIterator i = nodes->begin();
1235 while (i != nodes->end())
1237 if (i->getIndex() == endNode)
1239 end = i->getAddress();
1246 // There is probably a computationally cheaper way of
1248 void FGTaxiSegment::setTrackDistance()
1251 SGWayPoint first (start->getLongitude(),
1252 start->getLatitude(),
1254 SGWayPoint second (end->getLongitude(),
1257 first.CourseAndDistance(second, &course, &length);
1261 bool FGTaxiRoute::next(int *val)
1263 //for (intVecIterator i = nodes.begin(); i != nodes.end(); i++)
1264 // cerr << "FGTaxiRoute contains : " << *(i) << endl;
1265 //cerr << "Offset from end: " << nodes.end() - currNode << endl;
1266 //if (currNode != nodes.end())
1267 // cerr << "true" << endl;
1269 // cerr << "false" << endl;
1271 if (currNode == nodes.end())
1277 /***************************************************************************
1279 **************************************************************************/
1281 FGGroundNetwork::FGGroundNetwork()
1286 void FGGroundNetwork::addSegment(const FGTaxiSegment &seg)
1288 segments.push_back(seg);
1291 void FGGroundNetwork::addNode(const FGTaxiNode &node)
1293 nodes.push_back(node);
1296 void FGGroundNetwork::addNodes(FGParkingVec *parkings)
1299 FGParkingVecIterator i = parkings->begin();
1300 while (i != parkings->end())
1302 n.setIndex(i->getIndex());
1303 n.setLatitude(i->getLatitude());
1304 n.setLongitude(i->getLongitude());
1313 void FGGroundNetwork::init()
1316 FGTaxiSegmentVectorIterator i = segments.begin();
1317 while(i != segments.end()) {
1318 //cerr << "initializing node " << i->getIndex() << endl;
1319 i->setStart(&nodes);
1321 i->setTrackDistance();
1322 //cerr << "Track distance = " << i->getLength() << endl;
1323 //cerr << "Track ends at" << i->getEnd()->getIndex() << endl;
1329 int FGGroundNetwork::findNearestNode(double lat, double lon)
1331 double minDist = HUGE_VAL;
1332 double course, dist;
1334 SGWayPoint first (lon,
1338 for (FGTaxiNodeVectorIterator
1339 itr = nodes.begin();
1340 itr != nodes.end(); itr++)
1343 SGWayPoint second (itr->getLongitude(),
1346 first.CourseAndDistance(second, &course, &dist);
1350 index = itr->getIndex();
1351 //cerr << "Minimum distance of " << minDist << " for index " << index << endl;
1357 FGTaxiNode *FGGroundNetwork::findNode(int idx)
1359 for (FGTaxiNodeVectorIterator
1360 itr = nodes.begin();
1361 itr != nodes.end(); itr++)
1363 if (itr->getIndex() == idx)
1364 return itr->getAddress();
1369 FGTaxiRoute FGGroundNetwork::findShortestRoute(int start, int end)
1373 FGTaxiNode *firstNode = findNode(start);
1374 FGTaxiNode *lastNode = findNode(end);
1375 //prevNode = prevPrevNode = -1;
1379 trace(firstNode, end, 0, 0);
1384 cerr << "Failed to find route from waypoint " << start << " to " << end << endl;
1387 sort(routes.begin(), routes.end());
1388 //for (intVecIterator i = route.begin(); i != route.end(); i++)
1390 // rte->push_back(*i);
1393 if (routes.begin() != routes.end())
1394 return *(routes.begin());
1400 void FGGroundNetwork::trace(FGTaxiNode *currNode, int end, int depth, double distance)
1402 traceStack.push_back(currNode->getIndex());
1403 totalDistance += distance;
1404 //cerr << "Starting trace " << depth << " total distance: " << totalDistance<< endl;
1405 //<< currNode->getIndex() << endl;
1407 // If the current route matches the required end point we found a valid route
1408 // So we can add this to the routing table
1409 if (currNode->getIndex() == end)
1411 //cerr << "Found route : " << totalDistance << "" << " " << *(traceStack.end()-1) << endl;
1412 routes.push_back(FGTaxiRoute(traceStack,totalDistance));
1413 traceStack.pop_back();
1415 maxDistance = totalDistance;
1417 if (totalDistance < maxDistance)
1418 maxDistance = totalDistance;
1420 totalDistance -= distance;
1425 // search if the currentNode has been encountered before
1426 // if so, we should step back one level, because it is
1427 // rather rediculous to proceed further from here.
1428 // if the current node has not been encountered before,
1429 // i should point to traceStack.end()-1; and we can continue
1430 // if i is not traceStack.end, the previous node was found,
1431 // and we should return.
1432 // This only works at trace levels of 1 or higher though
1434 intVecIterator i = traceStack.begin();
1435 while ((*i) != currNode->getIndex()) {
1436 //cerr << "Route so far : " << (*i) << endl;
1439 if (i != traceStack.end()-1) {
1440 traceStack.pop_back();
1441 totalDistance -= distance;
1444 // If the total distance from start to the current waypoint
1445 // is longer than that of a route we can also stop this trace
1446 // and go back one level.
1447 if ((totalDistance > maxDistance) && foundRoute)
1449 //cerr << "Stopping rediculously long trace: " << totalDistance << endl;
1450 traceStack.pop_back();
1451 totalDistance -= distance;
1456 //cerr << "2" << endl;
1457 if (currNode->getBeginRoute() != currNode->getEndRoute())
1459 //cerr << "3" << endl;
1460 for (FGTaxiSegmentPointerVectorIterator
1461 i = currNode->getBeginRoute();
1462 i != currNode->getEndRoute();
1465 //cerr << (*i)->getLenght() << endl;
1466 trace((*i)->getEnd(), end, depth+1, (*i)->getLength());
1468 // // cerr << currNode -> getIndex() << " ";
1469 // route.push_back(currNode->getIndex());
1476 cerr << "4" << endl;
1478 traceStack.pop_back();
1479 totalDistance -= distance;
1485 /******************************************************************************
1487 *****************************************************************************/
1489 // Populates a list of subdirectories of $FG_ROOT/Airports/AI so that
1490 // the add() method doesn't have to try opening 2 XML files in each of
1491 // thousands of non-existent directories. FIXME: should probably add
1492 // code to free this list after parsing of apt.dat is finished;
1493 // non-issue at the moment, however, as there are no AI subdirectories
1494 // in the base package.
1495 FGAirportList::FGAirportList()
1499 SGPath aid( globals->get_fg_root() );
1500 aid.append( "/Airports/AI" );
1501 if((d = ulOpenDir(aid.c_str())) == NULL)
1503 while((dent = ulReadDir(d)) != NULL) {
1504 cerr << "Dent: " << dent->d_name; // DEBUG
1505 ai_dirs.insert(dent->d_name);
1511 FGAirportList::~FGAirportList( void ) {
1512 for(unsigned int i = 0; i < airports_array.size(); ++i) {
1513 delete airports_array[i];
1518 // add an entry to the list
1519 void FGAirportList::add( const string &id, const double longitude,
1520 const double latitude, const double elevation,
1521 const string &name, const bool has_metar )
1523 FGRunwayPreference rwyPrefs;
1524 FGAirport* a = new FGAirport(id, longitude, latitude, elevation, name, has_metar);
1525 SGPath parkpath( globals->get_fg_root() );
1526 parkpath.append( "/Airports/AI/" );
1527 parkpath.append(id);
1528 parkpath.append("parking.xml");
1530 SGPath rwyPrefPath( globals->get_fg_root() );
1531 rwyPrefPath.append( "/Airports/AI/" );
1532 rwyPrefPath.append(id);
1533 rwyPrefPath.append("rwyuse.xml");
1534 if (ai_dirs.find(id.c_str()) != ai_dirs.end()
1535 && parkpath.exists())
1538 readXML(parkpath.str(),*a);
1541 catch (const sg_exception &e) {
1542 //cerr << "unable to read " << parkpath.str() << endl;
1545 if (ai_dirs.find(id.c_str()) != ai_dirs.end()
1546 && rwyPrefPath.exists())
1549 readXML(rwyPrefPath.str(), rwyPrefs);
1550 a->setRwyUse(rwyPrefs);
1552 catch (const sg_exception &e) {
1553 //cerr << "unable to read " << rwyPrefPath.str() << endl;
1558 airports_by_id[a->getId()] = a;
1559 // try and read in an auxilary file
1561 airports_array.push_back( a );
1562 SG_LOG( SG_GENERAL, SG_BULK, "Adding " << id << " pos = " << longitude
1563 << ", " << latitude << " elev = " << elevation );
1567 // search for the specified id
1568 FGAirport* FGAirportList::search( const string& id) {
1569 airport_map_iterator itr = airports_by_id.find(id);
1570 return(itr == airports_by_id.end() ? NULL : itr->second);
1574 // search for first subsequent alphabetically to supplied id
1575 const FGAirport* FGAirportList::findFirstById( const string& id, bool exact ) {
1576 airport_map_iterator itr;
1578 itr = airports_by_id.find(id);
1580 itr = airports_by_id.lower_bound(id);
1582 if(itr == airports_by_id.end()) {
1585 return(itr->second);
1590 // search for the airport nearest the specified position
1591 FGAirport* FGAirportList::search( double lon_deg, double lat_deg,
1594 double min_dist = 360.0;
1596 for ( i = 0; i < airports_array.size(); ++i ) {
1597 // crude manhatten distance based on lat/lon difference
1598 double d = fabs(lon_deg - airports_array[i]->getLongitude())
1599 + fabs(lat_deg - airports_array[i]->getLatitude());
1600 if ( d < min_dist ) {
1601 if ( !with_metar || (with_metar&&airports_array[i]->getMetar()) ) {
1608 return ( closest > -1 ? airports_array[closest] : NULL );
1613 FGAirportList::size () const
1615 return airports_array.size();
1618 const FGAirport *FGAirportList::getAirport( unsigned int index ) const
1620 if(index < airports_array.size()) {
1621 return(airports_array[index]);
1629 * Mark the specified airport record as not having metar
1631 void FGAirportList::no_metar( const string &id ) {
1632 if(airports_by_id.find(id) != airports_by_id.end()) {
1633 airports_by_id[id]->setMetar(false);
1639 * Mark the specified airport record as (yes) having metar
1641 void FGAirportList::has_metar( const string &id ) {
1642 if(airports_by_id.find(id) != airports_by_id.end()) {
1643 airports_by_id[id]->setMetar(true);