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);
63 /******************************************************************************
65 ***************e*************************************************************/
66 void ScheduleTime::clear()
70 scheduleNames.clear();
74 ScheduleTime::ScheduleTime(const ScheduleTime &other)
77 timeVecConstIterator i;
78 for (i = other.start.begin(); i != other.start.end(); i++)
80 for (i = other.end.begin(); i != other.end.end(); i++)
82 stringVecConstIterator k;
83 for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
84 scheduleNames.push_back(*k);
87 //stringVec scheduleNames;
88 tailWind = other.tailWind;
89 crssWind = other.tailWind;
93 ScheduleTime & ScheduleTime::operator= (const ScheduleTime &other)
97 timeVecConstIterator i;
98 for (i = other.start.begin(); i != other.start.end(); i++)
100 for (i = other.end.begin(); i != other.end.end(); i++)
102 stringVecConstIterator k;
103 for (k = other.scheduleNames.begin(); k != other.scheduleNames.end(); k++)
104 scheduleNames.push_back(*k);
107 //stringVec scheduleNames;
108 tailWind = other.tailWind;
109 crssWind = other.tailWind;
112 string ScheduleTime::getName(time_t dayStart)
114 if ((start.size() != end.size()) || (start.size() != scheduleNames.size()))
116 cerr << "Unable to parse schedule times" << endl;
121 int nrItems = start.size();
122 //cerr << "Nr of items to process: " << nrItems << endl;
125 for (unsigned int i = 0; i < start.size(); i++)
128 if ((dayStart >= start[i]) && (dayStart <= end[i]))
129 return scheduleNames[i];
132 //couldn't find one so return 0;
133 //cerr << "Returning 0 " << endl;
137 /******************************************************************************
139 *****************************************************************************/
141 RunwayList::RunwayList(const RunwayList &other)
144 stringVecConstIterator i;
145 for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
146 preferredRunways.push_back(*i);
148 RunwayList& RunwayList::operator= (const RunwayList &other)
151 preferredRunways.clear();
152 stringVecConstIterator i;
153 for (i = other.preferredRunways.begin(); i != other.preferredRunways.end(); i++)
154 preferredRunways.push_back(*i);
157 void RunwayList::set(string tp, string lst)
159 //weekday = atoi(timeCopy.substr(0,1).c_str());
160 // timeOffsetInDays = weekday - currTimeDate->getGmt()->tm_wday;
161 // timeCopy = timeCopy.substr(2,timeCopy.length());
165 while (rwys.find(",") != string::npos)
167 rwy = rwys.substr(0, rwys.find(",",0));
168 //cerr << "adding runway [" << rwy << "] to the list " << endl;
169 preferredRunways.push_back(rwy);
170 rwys.erase(0, rwys.find(",",0)+1); // erase until after the first whitspace
171 while (rwys[0] == ' ')
172 rwys.erase(0, 1); // Erase any leading whitespaces.
173 //cerr << "Remaining runway list " << rwys;
175 preferredRunways.push_back(rwys);
179 void RunwayList::clear()
182 preferredRunways.clear();
184 /****************************************************************************
186 ***************************************************************************/
188 RunwayGroup::RunwayGroup(const RunwayGroup &other)
191 RunwayListVecConstIterator i;
192 for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
193 rwyList.push_back(*i);
194 choice[0] = other.choice[0];
195 choice[1] = other.choice[1];
196 nrActive = other.nrActive;
198 RunwayGroup& RunwayGroup:: operator= (const RunwayGroup &other)
202 RunwayListVecConstIterator i;
203 for (i = other.rwyList.begin(); i != other.rwyList.end(); i++)
204 rwyList.push_back(*i);
205 choice[0] = other.choice[0];
206 choice[1] = other.choice[1];
207 nrActive = other.nrActive;
211 void RunwayGroup::setActive(string aptId,
219 int activeRwys = rwyList.size(); // get the number of runways active
221 // bool found = true;
230 nrOfPreferences = rwyList[0].getRwyList()->size();
231 for (int i = 0; i < nrOfPreferences; i++)
233 bool validSelection = true;
234 for (int j = 0; j < activeRwys; j++)
236 //cerr << "I J " << i << " " << j << endl;
237 name = rwyList[j].getRwyList(i);
238 //cerr << "Name of Runway: " << name << endl;
239 if (globals->get_runways()->search( aptId,
243 //cerr << "Succes" << endl;
244 hdgDiff = fabs(windHeading - rwy._heading);
245 //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
246 //cerr << "Wind Speed : " << windSpeed << endl;
248 hdgDiff = 360 - hdgDiff;
249 //cerr << "Heading diff: " << hdgDiff << endl;
250 hdgDiff *= ((2*M_PI)/360.0); // convert to radians
251 crossWind = windSpeed * sin(hdgDiff);
252 tailWind = -windSpeed * cos(hdgDiff);
253 //cerr << "Tailwind : " << tailWind << endl;
254 //cerr << "Crosswnd : " << crossWind << endl;
255 if ((tailWind > maxTail) || (crossWind > maxCross))
256 validSelection = false;
258 cerr << "Failed to find runway " << name << " at " << aptId << endl;
265 //cerr << "Valid runay selection : " << i << endl;
266 nrActive = activeRwys;
271 // If this didn't work, due to heavy winds, try again
272 // but select only one landing and one takeoff runway.
275 for (int i = activeRwys-1; i; i--)
277 if (rwyList[i].getType() == string("landing"))
279 if (rwyList[i].getType() == string("takeoff"))
282 //cerr << "Choosing " << choice[0] << " for landing and " << choice[1] << "for takeoff" << endl;
283 nrOfPreferences = rwyList[0].getRwyList()->size();
284 for (int i = 0; i < nrOfPreferences; i++)
286 bool validSelection = true;
287 for (int j = 0; j < 2; j++)
289 //cerr << "I J " << i << " " << j << endl;
290 name = rwyList[choice[j]].getRwyList(i);
291 //cerr << "Name of Runway: " << name << endl;
292 if (globals->get_runways()->search( aptId,
296 //cerr << "Succes" << endl;
297 hdgDiff = fabs(windHeading - rwy._heading);
298 //cerr << "Wind Heading: " << windHeading << "Runway Heading: " <<rwy._heading << endl;
299 //cerr << "Wind Speed : " << windSpeed << endl;
301 hdgDiff = 360 - hdgDiff;
302 //cerr << "Heading diff: " << hdgDiff << endl;
303 hdgDiff *= ((2*M_PI)/360.0); // convert to radians
304 crossWind = windSpeed * sin(hdgDiff);
305 tailWind = -windSpeed * cos(hdgDiff);
306 //cerr << "Tailwind : " << tailWind << endl;
307 //cerr << "Crosswnd : " << crossWind << endl;
308 if ((tailWind > maxTail) || (crossWind > maxCross))
309 validSelection = false;
311 cerr << "Failed to find runway " << name << " at " << aptId << endl;
318 //cerr << "Valid runay selection : " << i << endl;
326 //RunwayListVectorIterator i; // = rwlist.begin();
327 //stringVecIterator j;
328 //for (i = rwyList.begin(); i != rwyList.end(); i++)
330 // cerr << i->getType();
331 // for (j = i->getRwyList()->begin(); j != i->getRwyList()->end(); j++)
341 void RunwayGroup::getActive(int i, string *name, string *type)
347 if (nrActive == (int)rwyList.size())
349 *name = rwyList[i].getRwyList(active);
350 *type = rwyList[i].getType();
354 *name = rwyList[choice[i]].getRwyList(active);
355 *type = rwyList[choice[i]].getType();
358 /*****************************************************************************
359 * FGRunway preference
360 ****************************************************************************/
361 FGRunwayPreference::FGRunwayPreference()
363 //cerr << "Running default Constructor" << endl;
367 FGRunwayPreference::FGRunwayPreference(const FGRunwayPreference &other)
369 initialized = other.initialized;
371 scheduleName = other.scheduleName;
373 comTimes = other.comTimes; // Commercial Traffic;
374 genTimes = other.genTimes; // General Aviation;
375 milTimes = other.milTimes; // Military Traffic;
376 currTimes= other.currTimes; // Needed for parsing;
378 rwyList = other.rwyList;
379 rwyGroup = other.rwyGroup;
380 PreferenceListConstIterator i;
381 for (i = other.preferences.begin(); i != other.preferences.end(); i++)
382 preferences.push_back(*i);
385 FGRunwayPreference & FGRunwayPreference::operator= (const FGRunwayPreference &other)
387 initialized = other.initialized;
389 scheduleName = other.scheduleName;
391 comTimes = other.comTimes; // Commercial Traffic;
392 genTimes = other.genTimes; // General Aviation;
393 milTimes = other.milTimes; // Military Traffic;
394 currTimes= other.currTimes; // Needed for parsing;
396 rwyList = other.rwyList;
397 rwyGroup = other.rwyGroup;
398 PreferenceListConstIterator i;
400 for (i = other.preferences.begin(); i != other.preferences.end(); i++)
401 preferences.push_back(*i);
405 ScheduleTime *FGRunwayPreference::getSchedule(const char *trafficType)
407 if (!(strcmp(trafficType, "com"))) {
410 if (!(strcmp(trafficType, "gen"))) {
413 if (!(strcmp(trafficType, "mil"))) {
419 RunwayGroup *FGRunwayPreference::getGroup(const string groupName)
421 PreferenceListIterator i = preferences.begin();
422 if (preferences.begin() == preferences.end())
424 while (!(i == preferences.end() || i->getName() == groupName))
426 if (i != preferences.end())
432 void FGRunwayPreference::startXML () {
433 // cout << "Start XML" << endl;
436 void FGRunwayPreference::endXML () {
437 cout << "End XML" << endl;
440 void FGRunwayPreference::startElement (const char * name, const XMLAttributes &atts) {
441 //cout << "StartElement " << name << endl;
443 if (!(strcmp(name, "wind"))) {
444 //cerr << "Will be processing Wind" << endl;
445 for (int i = 0; i < atts.size(); i++)
447 //cout << " " << atts.getName(i) << '=' << atts.getValue(i) << endl;
448 //attname = atts.getName(i);
449 if (atts.getName(i) == string("tail")) {
450 //cerr << "Tail Wind = " << atts.getValue(i) << endl;
451 currTimes.setTailWind(atof(atts.getValue(i)));
453 if (atts.getName(i) == string("cross")) {
454 //cerr << "Cross Wind = " << atts.getValue(i) << endl;
455 currTimes.setCrossWind(atof(atts.getValue(i)));
459 if (!(strcmp(name, "time"))) {
460 //cerr << "Will be processing time" << endl;
461 for (int i = 0; i < atts.size(); i++)
463 if (atts.getName(i) == string("start")) {
464 //cerr << "Start Time = " << atts.getValue(i) << endl;
465 currTimes.addStartTime(processTime(atts.getValue(i)));
467 if (atts.getName(i) == string("end")) {
468 //cerr << "End time = " << atts.getValue(i) << endl;
469 currTimes.addEndTime(processTime(atts.getValue(i)));
471 if (atts.getName(i) == string("schedule")) {
472 //cerr << "Schedule Name = " << atts.getValue(i) << endl;
473 currTimes.addScheduleName(atts.getValue(i));
477 if (!(strcmp(name, "takeoff"))) {
480 if (!(strcmp(name, "landing")))
484 if (!(strcmp(name, "schedule"))) {
485 for (int i = 0; i < atts.size(); i++)
487 //cout << " " << atts.getName(i) << '=' << atts.getValue(i) << endl;
488 //attname = atts.getName(i);
489 if (atts.getName(i) == string("name")) {
490 //cerr << "Schedule name = " << atts.getValue(i) << endl;
491 scheduleName = atts.getValue(i);
497 //based on a string containing hour and minute, return nr seconds since day start.
498 time_t FGRunwayPreference::processTime(string tme)
500 string hour = tme.substr(0, tme.find(":",0));
501 string minute = tme.substr(tme.find(":",0)+1, tme.length());
503 //cerr << "hour = " << hour << " Minute = " << minute << endl;
504 return (atoi(hour.c_str()) * 3600 + atoi(minute.c_str()) * 60);
507 void FGRunwayPreference::endElement (const char * name) {
508 //cout << "End element " << name << endl;
509 if (!(strcmp(name, "rwyuse"))) {
512 if (!(strcmp(name, "com"))) { // Commercial Traffic
513 //cerr << "Setting time table for commerical traffic" << endl;
514 comTimes = currTimes;
517 if (!(strcmp(name, "gen"))) { // General Aviation
518 //cerr << "Setting time table for general aviation" << endl;
519 genTimes = currTimes;
522 if (!(strcmp(name, "mil"))) { // Military Traffic
523 //cerr << "Setting time table for military traffic" << endl;
524 genTimes = currTimes;
528 if (!(strcmp(name, "takeoff"))) {
529 //cerr << "Adding takeoff: " << value << endl;
530 rwyList.set(name, value);
531 rwyGroup.add(rwyList);
533 if (!(strcmp(name, "landing"))) {
534 //cerr << "Adding landing: " << value << endl;
535 rwyList.set(name, value);
536 rwyGroup.add(rwyList);
538 if (!(strcmp(name, "schedule"))) {
539 //cerr << "Adding schedule" << scheduleName << endl;
540 rwyGroup.setName(scheduleName);
541 //rwyGroup.addRunways(rwyList);
542 preferences.push_back(rwyGroup);
548 void FGRunwayPreference::data (const char * s, int len) {
549 string token = string(s,len);
550 //cout << "Character data " << string(s,len) << endl;
551 //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
554 // value = string("");
558 void FGRunwayPreference::pi (const char * target, const char * data) {
559 //cout << "Processing instruction " << target << ' ' << data << endl;
562 void FGRunwayPreference::warning (const char * message, int line, int column) {
563 cout << "Warning: " << message << " (" << line << ',' << column << ')'
567 void FGRunwayPreference::error (const char * message, int line, int column) {
568 cout << "Error: " << message << " (" << line << ',' << column << ')'
572 /*****************************************************************************
573 * Helper function for parsing position string
574 ****************************************************************************/
575 double processPosition(string pos)
584 prefix= subs.substr(0,1);
585 if (prefix == string("S") || (prefix == string("W")))
587 subs = subs.substr(1, subs.length());
588 degree = subs.substr(0, subs.find(" ",0));
589 decimal = subs.substr(subs.find(" ",0), subs.length());
592 //cerr << sign << " "<< degree << " " << decimal << endl;
593 value = sign * (atof(degree.c_str()) + atof(decimal.c_str())/60.0);
594 //cerr << value <<endl;
600 /*********************************************************************************
602 ********************************************************************************/
603 FGParking::FGParking(double lat,
618 airlineCodes = codes;
622 /***************************************************************************
624 ***************************************************************************/
625 FGAirport::FGAirport() : _longitude(0), _latitude(0), _elevation(0)
628 for (int i = 0; i < 10; i++)
630 avWindHeading [i] = 0;
635 FGAirport::FGAirport(const FGAirport& other)
638 _longitude = other._longitude;
639 _latitude = other._latitude;
640 _elevation = other._elevation;
642 _has_metar = other._has_metar;
643 for (FGParkingVecConstIterator ip= other.parkings.begin(); ip != other.parkings.end(); ip++)
644 parkings.push_back(*(ip));
645 rwyPrefs = other.rwyPrefs;
646 lastUpdate = other.lastUpdate;
648 stringVecConstIterator il;
649 for (il = other.landing.begin(); il != other.landing.end(); il++)
650 landing.push_back(*il);
651 for (il = other.takeoff.begin(); il != other.takeoff.end(); il++)
652 takeoff.push_back(*il);
653 lastUpdate = other.lastUpdate;
654 for (int i = 0; i < 10; i++)
656 avWindHeading [i] = other.avWindHeading[i];
657 avWindSpeed [i] = other.avWindSpeed [i];
661 FGAirport::FGAirport(string id, double lon, double lat, double elev, string name, bool has_metar)
668 _has_metar = has_metar;
670 for (int i = 0; i < 10; i++)
672 avWindHeading [i] = 0;
678 // Initialization required after XMLRead
679 void FGAirport::init()
681 // This may seem a bit weird to first randomly shuffle the parkings
682 // and then sort them again. However, parkings are sorted here by ascending
683 // radius. Since many parkings have similar radii, with each radius class they will
684 // still be allocated relatively systematically. Randomizing prior to sorting will
685 // prevent any initial orderings to be destroyed, leading (hopefully) to a more
686 // naturalistic gate assignment.
687 random_shuffle(parkings.begin(), parkings.end());
688 sort(parkings.begin(), parkings.end());
689 // add the gate positions to the ground network.
690 groundNetwork.addNodes(&parkings);
691 groundNetwork.init();
694 bool FGAirport::getAvailableParking(double *lat, double *lon, double *heading, int *gateId, double rad, string flType, string acType, string airline)
697 bool available = false;
700 FGParkingVecIterator i;
701 // if (flType == "cargo")
703 // gateType = "RAMP_CARGO";
705 // else if (flType == "ga")
707 // gateType = "RAMP_GA";
709 // else gateType = "GATE";
711 if (parkings.begin() == parkings.end())
713 //cerr << "Could not find parking spot at " << _id << endl;
721 // First try finding a parking with a designated airline code
722 for (i = parkings.begin(); !(i == parkings.end() || found); i++)
724 //cerr << "Gate Id: " << i->getIndex()
725 // << " Type : " << i->getType()
726 // << " Codes : " << i->getCodes()
727 // << " Radius: " << i->getRadius()
728 // << " Name : " << i->getName()
729 // << " Available: " << i->isAvailable() << endl;
731 // Taken by another aircraft
732 if (!(i->isAvailable()))
737 // No airline codes, so skip
738 if (i->getCodes().empty())
743 else // Airline code doesn't match
744 if (i->getCodes().find(airline, 0) == string::npos)
749 // Type doesn't match
750 if (i->getType() != flType)
756 if (i->getRadius() < rad)
764 *lat = i->getLatitude ();
765 *lon = i->getLongitude();
766 *heading = i->getHeading ();
767 *gateId = i->getIndex ();
768 i->setAvailable(false);
772 // then try again for those without codes.
773 for (i = parkings.begin(); !(i == parkings.end() || found); i++)
776 if (!(i->isAvailable()))
781 if (!(i->getCodes().empty()))
783 if ((i->getCodes().find(airline,0) == string::npos))
789 if (i->getType() != flType)
795 if (i->getRadius() < rad)
803 *lat = i->getLatitude ();
804 *lon = i->getLongitude();
805 *heading = i->getHeading ();
806 *gateId = i->getIndex ();
807 i->setAvailable(false);
811 // And finally once more if that didn't work. Now ignore the airline codes, as a last resort
812 for (i = parkings.begin(); !(i == parkings.end() || found); i++)
815 if (!(i->isAvailable()))
820 if (i->getType() != flType)
826 if (i->getRadius() < rad)
834 *lat = i->getLatitude ();
835 *lon = i->getLongitude();
836 *heading = i->getHeading ();
837 *gateId = i->getIndex ();
838 i->setAvailable(false);
845 //cerr << "Traffic overflow at" << _id
846 // << ". flType = " << flType
847 // << ". airline = " << airline
848 // << " Radius = " <<rad
859 void FGAirport::getParking (int id, double *lat, double* lon, double *heading)
869 FGParkingVecIterator i = parkings.begin();
870 for (i = parkings.begin(); i != parkings.end(); i++)
872 if (id == i->getIndex())
874 *lat = i->getLatitude();
875 *lon = i->getLongitude();
876 *heading = i->getLongitude();
882 FGParking *FGAirport::getParking(int i)
884 if (i < (int)parkings.size())
885 return &(parkings[i]);
889 string FGAirport::getParkingName(int i)
891 if (i < (int)parkings.size() && i >= 0)
892 return (parkings[i].getName());
894 return string("overflow");
896 void FGAirport::releaseParking(int id)
901 FGParkingVecIterator i = parkings.begin();
902 for (i = parkings.begin(); i != parkings.end(); i++)
904 if (id == i->getIndex())
906 i -> setAvailable(true);
912 void FGAirport::startXML () {
913 //cout << "Start XML" << endl;
916 void FGAirport::endXML () {
917 //cout << "End XML" << endl;
920 void FGAirport::startElement (const char * name, const XMLAttributes &atts) {
921 // const char *attval;
924 FGTaxiSegment taxiSegment;
926 taxiSegment.setIndex(index);
927 //cout << "Start element " << name << endl;
934 if (name == string("Parking"))
936 for (int i = 0; i < atts.size(); i++)
938 //cout << " " << atts.getName(i) << '=' << atts.getValue(i) << endl;
939 attname = atts.getName(i);
940 if (attname == string("index"))
941 park.setIndex(atoi(atts.getValue(i)));
942 else if (attname == string("type"))
943 park.setType(atts.getValue(i));
944 else if (attname == string("name"))
945 gateName = atts.getValue(i);
946 else if (attname == string("number"))
947 gateNumber = atts.getValue(i);
948 else if (attname == string("lat"))
949 park.setLatitude(atts.getValue(i));
950 else if (attname == string("lon"))
951 park.setLongitude(atts.getValue(i));
952 else if (attname == string("heading"))
953 park.setHeading(atof(atts.getValue(i)));
954 else if (attname == string("radius")) {
955 string radius = atts.getValue(i);
956 if (radius.find("M") != string::npos)
957 radius = radius.substr(0, radius.find("M",0));
958 //cerr << "Radius " << radius <<endl;
959 park.setRadius(atof(radius.c_str()));
961 else if (attname == string("airlineCodes"))
962 park.setCodes(atts.getValue(i));
964 park.setName((gateName+gateNumber));
965 parkings.push_back(park);
967 if (name == string("node"))
969 for (int i = 0; i < atts.size() ; i++)
971 attname = atts.getName(i);
972 if (attname == string("index"))
973 taxiNode.setIndex(atoi(atts.getValue(i)));
974 if (attname == string("lat"))
975 taxiNode.setLatitude(atts.getValue(i));
976 if (attname == string("lon"))
977 taxiNode.setLongitude(atts.getValue(i));
979 groundNetwork.addNode(taxiNode);
981 if (name == string("arc"))
983 taxiSegment.setIndex(++index);
984 for (int i = 0; i < atts.size() ; i++)
986 attname = atts.getName(i);
987 if (attname == string("begin"))
988 taxiSegment.setStartNodeRef(atoi(atts.getValue(i)));
989 if (attname == string("end"))
990 taxiSegment.setEndNodeRef(atoi(atts.getValue(i)));
992 groundNetwork.addSegment(taxiSegment);
994 // sort by radius, in asending order, so that smaller gates are first in the list
997 void FGAirport::endElement (const char * name) {
998 //cout << "End element " << name << endl;
1002 void FGAirport::data (const char * s, int len) {
1003 string token = string(s,len);
1004 //cout << "Character data " << string(s,len) << endl;
1005 //if ((token.find(" ") == string::npos && (token.find('\n')) == string::npos))
1008 //value = string("");
1011 void FGAirport::pi (const char * target, const char * data) {
1012 //cout << "Processing instruction " << target << ' ' << data << endl;
1015 void FGAirport::warning (const char * message, int line, int column) {
1016 cout << "Warning: " << message << " (" << line << ',' << column << ')'
1020 void FGAirport::error (const char * message, int line, int column) {
1021 cout << "Error: " << message << " (" << line << ',' << column << ')'
1025 void FGAirport::setRwyUse(FGRunwayPreference& ref)
1028 //cerr << "Exiting due to not implemented yet" << endl;
1031 void FGAirport::getActiveRunway(string trafficType, int action, string *runway)
1040 if (!(rwyPrefs.available()))
1042 chooseRunwayFallback(runway);
1043 return; // generic fall back goes here
1047 RunwayGroup *currRunwayGroup = 0;
1048 int nrActiveRunways = 0;
1049 time_t dayStart = fgGetLong("/sim/time/utc/day-seconds");
1050 if (((dayStart - lastUpdate) > 600) || trafficType != prevTrafficType)
1054 //lastUpdate = dayStart;
1055 prevTrafficType = trafficType;
1058 stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1059 ->getEnvironment(getLatitude(),
1063 windSpeed = stationweather.get_wind_speed_kt();
1064 windHeading = stationweather.get_wind_from_heading_deg();
1065 double averageWindSpeed = 0;
1066 double averageWindHeading = 0;
1067 double cosHeading = 0;
1068 double sinHeading = 0;
1069 // Initialize at the beginning of the next day or startup
1070 if ((lastUpdate == 0) || (dayStart < lastUpdate))
1072 for (int i = 0; i < 10; i++)
1074 avWindHeading [i] = windHeading;
1075 avWindSpeed [i] = windSpeed;
1080 if (windSpeed != avWindSpeed[9]) // update if new metar data
1082 // shift the running average
1083 for (int i = 0; i < 9 ; i++)
1085 avWindHeading[i] = avWindHeading[i+1];
1086 avWindSpeed [i] = avWindSpeed [i+1];
1089 avWindHeading[9] = windHeading;
1090 avWindSpeed [9] = windSpeed;
1093 for (int i = 0; i < 10; i++)
1095 averageWindSpeed += avWindSpeed [i];
1096 //averageWindHeading += avWindHeading [i];
1097 cosHeading += cos(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1098 sinHeading += sin(avWindHeading[i] * SG_DEGREES_TO_RADIANS);
1100 averageWindSpeed /= 10;
1101 //averageWindHeading /= 10;
1104 averageWindHeading = atan2(sinHeading, cosHeading) *SG_RADIANS_TO_DEGREES;
1105 if (averageWindHeading < 0)
1106 averageWindHeading += 360.0;
1107 //cerr << "Wind Heading " << windHeading << " average " << averageWindHeading << endl;
1108 //cerr << "Wind Speed " << windSpeed << " average " << averageWindSpeed << endl;
1109 lastUpdate = dayStart;
1110 //if (wind_speed == 0) {
1111 // wind_heading = 270; This forces West-facing rwys to be used in no-wind situations
1112 // which is consistent with Flightgear's initial setup.
1115 //string rwy_no = globals->get_runways()->search(apt->getId(), int(wind_heading));
1116 string scheduleName;
1117 //cerr << "finding active Runway for" << _id << endl;
1118 //cerr << "Nr of seconds since day start << " << dayStart << endl;
1119 ScheduleTime *currSched;
1120 //cerr << "A"<< endl;
1121 currSched = rwyPrefs.getSchedule(trafficType.c_str());
1124 //cerr << "B"<< endl;
1125 scheduleName = currSched->getName(dayStart);
1126 maxTail = currSched->getTailWind ();
1127 maxCross = currSched->getCrossWind ();
1128 //cerr << "SChedule anme = " << scheduleName << endl;
1129 if (scheduleName.empty())
1131 //cerr << "C"<< endl;
1132 currRunwayGroup = rwyPrefs.getGroup(scheduleName);
1133 //cerr << "D"<< endl;
1134 if (!(currRunwayGroup))
1136 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1137 //cerr << "Nr of Active Runways = " << nrActiveRunways << endl;
1138 currRunwayGroup->setActive(_id, averageWindSpeed, averageWindHeading, maxTail, maxCross);
1139 nrActiveRunways = currRunwayGroup->getNrActiveRunways();
1140 for (int i = 0; i < nrActiveRunways; i++)
1142 type = "unknown"; // initialize to something other than landing or takeoff
1143 currRunwayGroup->getActive(i, &name, &type);
1144 if (type == "landing")
1146 landing.push_back(name);
1147 //cerr << "Landing " << name << endl;
1149 if (type == "takeoff")
1151 takeoff.push_back(name);
1152 //cerr << "takeoff " << name << endl;
1156 if (action == 1) // takeoff
1158 int nr = takeoff.size();
1161 *runway = takeoff[(rand() % nr)];
1165 chooseRunwayFallback(runway);
1168 if (action == 2) // landing
1170 int nr = landing.size();
1173 *runway = landing[(rand() % nr)];
1177 chooseRunwayFallback(runway);
1181 //*runway = globals->get_runways()->search(_id, int(windHeading));
1182 //cerr << "Seleceted runway: " << *runway << endl;
1186 void FGAirport::chooseRunwayFallback(string *runway)
1189 stationweather = ((FGEnvironmentMgr *) globals->get_subsystem("environment"))
1190 ->getEnvironment(getLatitude(),
1194 double windSpeed = stationweather.get_wind_speed_kt();
1195 double windHeading = stationweather.get_wind_from_heading_deg();
1196 if (windSpeed == 0) {
1197 windHeading = 270; // This forces West-facing rwys to be used in no-wind situations
1198 //which is consistent with Flightgear's initial setup.
1201 *runway = globals->get_runways()->search(_id, int(windHeading));
1202 return; // generic fall back goes here
1207 /**************************************************************************
1209 *************************************************************************/
1210 FGTaxiNode::FGTaxiNode()
1214 /***************************************************************************
1216 **************************************************************************/
1217 FGTaxiSegment::FGTaxiSegment()
1221 void FGTaxiSegment::setStart(FGTaxiNodeVector *nodes)
1223 FGTaxiNodeVectorIterator i = nodes->begin();
1224 while (i != nodes->end())
1226 if (i->getIndex() == startNode)
1228 start = i->getAddress();
1229 i->addSegment(this);
1236 void FGTaxiSegment::setEnd(FGTaxiNodeVector *nodes)
1238 FGTaxiNodeVectorIterator i = nodes->begin();
1239 while (i != nodes->end())
1241 if (i->getIndex() == endNode)
1243 end = i->getAddress();
1250 // There is probably a computationally cheaper way of
1252 void FGTaxiSegment::setTrackDistance()
1255 SGWayPoint first (start->getLongitude(),
1256 start->getLatitude(),
1258 SGWayPoint second (end->getLongitude(),
1261 first.CourseAndDistance(second, &course, &length);
1265 bool FGTaxiRoute::next(int *val)
1267 //for (intVecIterator i = nodes.begin(); i != nodes.end(); i++)
1268 // cerr << "FGTaxiRoute contains : " << *(i) << endl;
1269 //cerr << "Offset from end: " << nodes.end() - currNode << endl;
1270 //if (currNode != nodes.end())
1271 // cerr << "true" << endl;
1273 // cerr << "false" << endl;
1275 if (currNode == nodes.end())
1281 /***************************************************************************
1283 **************************************************************************/
1285 FGGroundNetwork::FGGroundNetwork()
1290 void FGGroundNetwork::addSegment(FGTaxiSegment seg)
1292 segments.push_back(seg);
1295 void FGGroundNetwork::addNode(FGTaxiNode node)
1297 nodes.push_back(node);
1300 void FGGroundNetwork::addNodes(FGParkingVec *parkings)
1303 FGParkingVecIterator i = parkings->begin();
1304 while (i != parkings->end())
1306 n.setIndex(i->getIndex());
1307 n.setLatitude(i->getLatitude());
1308 n.setLongitude(i->getLongitude());
1317 void FGGroundNetwork::init()
1320 FGTaxiSegmentVectorIterator i = segments.begin();
1321 while(i != segments.end()) {
1322 //cerr << "initializing node " << i->getIndex() << endl;
1323 i->setStart(&nodes);
1325 i->setTrackDistance();
1326 //cerr << "Track distance = " << i->getLength() << endl;
1327 //cerr << "Track ends at" << i->getEnd()->getIndex() << endl;
1333 int FGGroundNetwork::findNearestNode(double lat, double lon)
1335 double minDist = HUGE;
1336 double course, dist;
1338 SGWayPoint first (lon,
1342 for (FGTaxiNodeVectorIterator
1343 itr = nodes.begin();
1344 itr != nodes.end(); itr++)
1347 SGWayPoint second (itr->getLongitude(),
1350 first.CourseAndDistance(second, &course, &dist);
1354 index = itr->getIndex();
1355 //cerr << "Minimum distance of " << minDist << " for index " << index << endl;
1361 FGTaxiNode *FGGroundNetwork::findNode(int idx)
1363 for (FGTaxiNodeVectorIterator
1364 itr = nodes.begin();
1365 itr != nodes.end(); itr++)
1367 if (itr->getIndex() == idx)
1368 return itr->getAddress();
1373 FGTaxiRoute FGGroundNetwork::findShortestRoute(int start, int end)
1377 FGTaxiNode *firstNode = findNode(start);
1378 FGTaxiNode *lastNode = findNode(end);
1379 //prevNode = prevPrevNode = -1;
1383 trace(firstNode, end, 0, 0);
1388 cerr << "Failed to find route from waypoint " << start << " to " << end << endl;
1391 sort(routes.begin(), routes.end());
1392 //for (intVecIterator i = route.begin(); i != route.end(); i++)
1394 // rte->push_back(*i);
1397 if (routes.begin() != routes.end())
1398 return *(routes.begin());
1404 void FGGroundNetwork::trace(FGTaxiNode *currNode, int end, int depth, double distance)
1406 traceStack.push_back(currNode->getIndex());
1407 totalDistance += distance;
1408 //cerr << "Starting trace " << depth << " total distance: " << totalDistance<< endl;
1409 //<< currNode->getIndex() << endl;
1411 // If the current route matches the required end point we found a valid route
1412 // So we can add this to the routing table
1413 if (currNode->getIndex() == end)
1415 //cerr << "Found route : " << totalDistance << "" << " " << *(traceStack.end()-1) << endl;
1416 routes.push_back(FGTaxiRoute(traceStack,totalDistance));
1417 traceStack.pop_back();
1419 maxDistance = totalDistance;
1421 if (totalDistance < maxDistance)
1422 maxDistance = totalDistance;
1424 totalDistance -= distance;
1429 // search if the currentNode has been encountered before
1430 // if so, we should step back one level, because it is
1431 // rather rediculous to proceed further from here.
1432 // if the current node has not been encountered before,
1433 // i should point to traceStack.end()-1; and we can continue
1434 // if i is not traceStack.end, the previous node was found,
1435 // and we should return.
1436 // This only works at trace levels of 1 or higher though
1438 intVecIterator i = traceStack.begin();
1439 while ((*i) != currNode->getIndex()) {
1440 //cerr << "Route so far : " << (*i) << endl;
1443 if (i != traceStack.end()-1) {
1444 traceStack.pop_back();
1445 totalDistance -= distance;
1448 // If the total distance from start to the current waypoint
1449 // is longer than that of a route we can also stop this trace
1450 // and go back one level.
1451 if ((totalDistance > maxDistance) && foundRoute)
1453 //cerr << "Stopping rediculously long trace: " << totalDistance << endl;
1454 traceStack.pop_back();
1455 totalDistance -= distance;
1460 //cerr << "2" << endl;
1461 if (currNode->getBeginRoute() != currNode->getEndRoute())
1463 //cerr << "3" << endl;
1464 for (FGTaxiSegmentPointerVectorIterator
1465 i = currNode->getBeginRoute();
1466 i != currNode->getEndRoute();
1469 //cerr << (*i)->getLenght() << endl;
1470 trace((*i)->getEnd(), end, depth+1, (*i)->getLength());
1472 // // cerr << currNode -> getIndex() << " ";
1473 // route.push_back(currNode->getIndex());
1480 cerr << "4" << endl;
1482 traceStack.pop_back();
1483 totalDistance -= distance;
1489 /******************************************************************************
1491 *****************************************************************************/
1493 // Populates a list of subdirectories of $FG_ROOT/Airports/AI so that
1494 // the add() method doesn't have to try opening 2 XML files in each of
1495 // thousands of non-existent directories. FIXME: should probably add
1496 // code to free this list after parsing of apt.dat is finished;
1497 // non-issue at the moment, however, as there are no AI subdirectories
1498 // in the base package.
1499 FGAirportList::FGAirportList()
1503 SGPath aid( globals->get_fg_root() );
1504 aid.append( "/Airports/AI" );
1505 if((d = ulOpenDir(aid.c_str())) == NULL)
1507 while((dent = ulReadDir(d)) != NULL) {
1508 cerr << "Dent: " << dent->d_name; // DEBUG
1509 ai_dirs.insert(dent->d_name);
1515 FGAirportList::~FGAirportList( void ) {
1516 for(unsigned int i = 0; i < airports_array.size(); ++i) {
1517 delete airports_array[i];
1522 // add an entry to the list
1523 void FGAirportList::add( const string id, const double longitude,
1524 const double latitude, const double elevation,
1525 const string name, const bool has_metar )
1527 FGRunwayPreference rwyPrefs;
1528 FGAirport* a = new FGAirport(id, longitude, latitude, elevation, name, has_metar);
1529 SGPath parkpath( globals->get_fg_root() );
1530 parkpath.append( "/Airports/AI/" );
1531 parkpath.append(id);
1532 parkpath.append("parking.xml");
1534 SGPath rwyPrefPath( globals->get_fg_root() );
1535 rwyPrefPath.append( "/Airports/AI/" );
1536 rwyPrefPath.append(id);
1537 rwyPrefPath.append("rwyuse.xml");
1538 if (ai_dirs.find(id.c_str()) != ai_dirs.end()
1539 && parkpath.exists())
1542 readXML(parkpath.str(),*a);
1545 catch (const sg_exception &e) {
1546 //cerr << "unable to read " << parkpath.str() << endl;
1549 if (ai_dirs.find(id.c_str()) != ai_dirs.end()
1550 && rwyPrefPath.exists())
1553 readXML(rwyPrefPath.str(), rwyPrefs);
1554 a->setRwyUse(rwyPrefs);
1556 catch (const sg_exception &e) {
1557 //cerr << "unable to read " << rwyPrefPath.str() << endl;
1562 airports_by_id[a->getId()] = a;
1563 // try and read in an auxilary file
1565 airports_array.push_back( a );
1566 SG_LOG( SG_GENERAL, SG_BULK, "Adding " << id << " pos = " << longitude
1567 << ", " << latitude << " elev = " << elevation );
1571 // search for the specified id
1572 FGAirport* FGAirportList::search( const string& id) {
1573 airport_map_iterator itr = airports_by_id.find(id);
1574 return(itr == airports_by_id.end() ? NULL : itr->second);
1578 // search for first subsequent alphabetically to supplied id
1579 const FGAirport* FGAirportList::findFirstById( const string& id, bool exact ) {
1580 airport_map_iterator itr;
1582 itr = airports_by_id.find(id);
1584 itr = airports_by_id.lower_bound(id);
1586 if(itr == airports_by_id.end()) {
1589 return(itr->second);
1594 // search for the airport nearest the specified position
1595 FGAirport* FGAirportList::search( double lon_deg, double lat_deg,
1598 double min_dist = 360.0;
1600 for ( i = 0; i < airports_array.size(); ++i ) {
1601 // crude manhatten distance based on lat/lon difference
1602 double d = fabs(lon_deg - airports_array[i]->getLongitude())
1603 + fabs(lat_deg - airports_array[i]->getLatitude());
1604 if ( d < min_dist ) {
1605 if ( !with_metar || (with_metar&&airports_array[i]->getMetar()) ) {
1612 return ( closest > -1 ? airports_array[closest] : NULL );
1617 FGAirportList::size () const
1619 return airports_array.size();
1622 const FGAirport *FGAirportList::getAirport( unsigned int index ) const
1624 if(index < airports_array.size()) {
1625 return(airports_array[index]);
1633 * Mark the specified airport record as not having metar
1635 void FGAirportList::no_metar( const string &id ) {
1636 if(airports_by_id.find(id) != airports_by_id.end()) {
1637 airports_by_id[id]->setMetar(false);
1643 * Mark the specified airport record as (yes) having metar
1645 void FGAirportList::has_metar( const string &id ) {
1646 if(airports_by_id.find(id) != airports_by_id.end()) {
1647 airports_by_id[id]->setMetar(true);