36#include <simgear/compiler.h>
37#include <simgear/debug/ErrorReportingCallback.hxx>
38#include <simgear/math/sg_geodesy.hxx>
39#include <simgear/props/props.hxx>
40#include <simgear/sg_inlines.h>
41#include <simgear/structure/subsystem_mgr.hxx>
42#include <simgear/timing/sg_time.hxx>
43#include <simgear/xml/easyxml.hxx>
74 scheduleComplete(false)
83 const string& flightId,
99 flightIdentifier{flightId},
112 scheduleComplete(false)
117 homePort{other.homePort},
118 livery{other.livery},
119 registration{other.registration},
120 airline{other.airline},
121 acType{other.acType},
122 m_class{other.m_class},
123 flightType{other.flightType},
124 flightIdentifier{other.flightIdentifier},
125 currentDestination{other.currentDestination},
126 flights{other.flights},
127 aiAircraft{other.aiAircraft}
130 firstRun = other.firstRun;
131 radius = other.radius;
132 groundOffset = other.groundOffset;
134 distanceToUser = other.distanceToUser;
135 runCount = other.runCount;
137 lastRun = other.lastRun;
138 courseToDest = other.courseToDest;
139 initialized = other.initialized;
141 scheduleComplete = other.scheduleComplete;
149 aiAircraft->setDie(
true);
192 time_t totalTimeEnroute;
193 time_t elapsedTimeEnroute;
194 time_t remainingTimeEnroute;
201 if (!scheduleComplete) {
202 scheduleComplete = scheduleFlights(now);
205 if (!scheduleComplete) {
209 if (flights.empty()) {
221 if (
fgGetBool(
"/sim/traffic-manager/instantaneous-action") ==
true) {
235 if (aiAircraft->getDie()) {
245 SG_LOG(SG_AI, SG_BULK,
"Traffic Manager: " << flight->
getCallSign() <<
" is in the Past");
250 flights.erase(flights.begin());
260 double speed = 450.0;
261 int remainingWaitTime = 0;
266 remainingTimeEnroute = totalTimeEnroute - elapsedTimeEnroute;
267 double x = elapsedTimeEnroute / (double)totalTimeEnroute;
272 double course, az2, distanceM;
273 SGGeodesy::inverse(dep->
geod(), arr->
geod(), course, az2, distanceM);
274 double coveredDistance = distanceM * x;
277 SGGeodesy::direct(dep->
geod(), course, coveredDistance, position, az2);
279 SG_LOG(SG_AI, SG_BULK,
"Traffic Manager: " << flight->
getCallSign() <<
" is in progress " << (x * 100) <<
"%");
280 speed = ((distanceM - coveredDistance) * SG_METER_TO_NM) / 3600.0;
283 remainingTimeEnroute = totalTimeEnroute;
284 elapsedTimeEnroute = 0;
285 position = dep->
geod();
287 if (remainingWaitTime < 600) {
288 SG_LOG(SG_AI, SG_BULK,
"Traffic Manager: " << flight->
getCallSign() <<
" is pending, departure in " << remainingWaitTime <<
" seconds ");
294 elapsedTimeEnroute = 0;
295 position = dep->
geod();
300 distanceToUser = dist(userCart, SGVec3d::fromGeod(position)) * SG_METER_TO_NM;
307 if (remainingWaitTime < 600) {
308 SG_LOG(SG_AI, SG_BULK,
"Traffic manager: " << registration <<
" is scheduled for a flight from " << dep->
getId() <<
" to " << arr->
getId() <<
". Current distance to user: " << distanceToUser);
314 if (!createAIAircraft(flight, speed, deptime, remainingTimeEnroute)) {
329 for (
auto aiPath :
globals->get_data_paths(
"AI")) {
330 aiPath.append(modelPath);
331 if (aiPath.exists()) {
337 for (
auto aircraftPath :
globals->get_aircraft_paths()) {
338 SGPath mp = aircraftPath / modelPath;
347bool FGAISchedule::createAIAircraft(
FGScheduledFlight* flight,
double speedKnots, time_t deptime, time_t remainingTime)
352 string flightPlanName = dep->
getId() +
"-" + arr->
getId() +
".xml";
353 SG_LOG(SG_AI, SG_DEBUG, flight->
getCallSign() <<
"|Traffic manager: Creating AIModel from:" << flightPlanName);
356 aiAircraft->setPerformance(acType, m_class);
357 aiAircraft->setCompany(airline);
358 aiAircraft->setAcType(acType);
359 aiAircraft->setPath(modelPath.c_str());
361 aiAircraft->setLatitude(position.getLatitudeDeg());
362 aiAircraft->setLongitude(position.getLongitudeDeg());
364 aiAircraft->setSpeed(0);
365 aiAircraft->setBank(0);
367 courseToDest = SGGeodesy::courseDeg(position, arr->
geod());
378 position.getLatitudeDeg(),
379 position.getLongitudeDeg(),
380 speedKnots, flightType, acType,
382 if (fp->isValidPlan()) {
384 simgear::ErrorReportContext ec{
"traffic-aircraft-callsign", flight->
getCallSign()};
386 aiAircraft->FGAIBase::setFlightPlan(std::move(fp));
387 globals->get_subsystem<FGAIManager>()->attach(aiAircraft);
388 if (aiAircraft->_getProps()) {
389 SGPropertyNode* nodeForAircraft = aiAircraft->_getProps();
391 nodeForAircraft->getChild(
"departure-airport-id", 0,
true)->setStringValue(dep->
getId());
392 nodeForAircraft->getChild(
"departure-time-sec", 0,
true)->setIntValue(deptime);
395 nodeForAircraft->getChild(
"arrival-airport-id", 0,
true)->setStringValue(arr->
getId());
403 while (!flights.empty()) {
404 flights.front()->release();
405 flights.erase(flights.begin());
413 courseToDest = SGGeodesy::courseDeg((*flights.begin())->getDepartureAirport()->geod(), (*flights.begin())->getArrivalAirport()->geod());
423bool FGAISchedule::scheduleFlights(time_t now)
426 const string& userPort =
fgGetString(
"/sim/presets/airport-id");
427 SG_LOG(SG_AI, SG_BULK,
"Scheduling Flights for : " << modelPath <<
" " << registration <<
" " << homePort);
433 if (currentDestination.empty())
437 if ((!flight) || (!first)) {
449 if (userPort == departurePort) {
462 string depT = asctime(gmtime(&dep));
463 string arrT = asctime(gmtime(&arr));
466 SG_LOG(SG_AI, SG_BULK,
" Flight " << flight->
getCallSign() <<
":"
468 <<
" " << depT <<
":"
471 <<
" " << arrT <<
":");
473 flights.push_back(flight);
476 }
while ((currentDestination != homePort) &&
477 (start.elapsedMSec() < 3.0));
479 if (flight && (currentDestination != homePort)) {
484 SG_LOG(SG_AI, SG_BULK,
" Done ");
490 if (!flights.empty()) {
491 flights.front()->release();
492 flights.erase(flights.begin());
516 flights.push_back(flight);
525 return (*flights.begin())->getDepartureTime();
533 return (*flights.begin())->getDepartureAirport();
541 return (*flights.begin())->getArrivalAirport();
549 return (*flights.begin())->getCruiseAlt();
555 return std::string();
557 return (*flights.begin())->getCallSign();
563 return std::string();
565 return (*flights.begin())->getFlightRules();
570 time_t
min, time_t max)
572 time_t now =
globals->get_time_params()->get_cur_time();
576 fltBegin = tmgr->getFirstFlight(req);
577 fltEnd = tmgr->getLastFlight(req);
580 SG_LOG(SG_AI, SG_BULK,
"Finding available flight for " << req <<
" at " << now);
583 if (fltBegin == fltEnd) {
584 SG_LOG(SG_AI, SG_BULK,
"No Flights Scheduled for " << req);
588 (*i)->adjustTime(now);
593 if (!(*i)->isAvailable()) {
594 SG_LOG(SG_AI, SG_BULK,
"" << (*i)->getCallSign() <<
"is no longer available");
599 if (!((*i)->getRequirement() == req)) {
600 SG_LOG(SG_AI, SG_BULK,
"" << (*i)->getCallSign() <<
" no requirement " << (*i)->getRequirement() <<
" " << req);
603 if (!(((*i)->getArrivalAirport()) && ((*i)->getDepartureAirport()))) {
606 if (!(currentDestination.empty())) {
607 if (currentDestination != (*i)->getDepartureAirport()->getId()) {
608 SG_LOG(SG_AI, SG_BULK, (*i)->getCallSign() <<
" not matching departure.");
615 if (!flights.empty()) {
616 time_t arrival = flights.back()->getArrivalTime();
617 time_t departure = (*i)->getDepartureTime();
618 int groundTime = groundTimeFromRadius();
619 if (departure < (arrival + (groundTime))) {
620 SG_LOG(SG_AI, SG_BULK,
"Not flight candidate : " << (*i)->getCallSign() <<
" Flight Arrival : " << arrival <<
" Planned Departure : " << departure <<
" < " << (arrival + groundTime) <<
" Diff between arrival + groundtime and departure : " << (arrival + groundTime - departure) <<
" Groundtime : " << groundTime);
623 SG_LOG(SG_AI, SG_BULK,
"Next flight candidate : " << (*i)->getCallSign());
627 time_t dep = (*i)->getDepartureTime();
628 if ((dep <
min) || (dep > max))
647int FGAISchedule::groundTimeFromRadius()
651 else if (radius < 15)
653 else if (radius < 20)
655 else if (radius < 25)
657 else if (radius < 30)
668 FGAirport *dep = (*i)->getDepartureAirport(),
669 *arr = (*i)->getArrivalAirport();
670 double dist = SGGeodesy::distanceNm(dep->
geod(), arr->geod());
671 double remainingTimeEnroute = (*i)->getArrivalTime() - (*i)->getDepartureTime();
674 if (remainingTimeEnroute > 0.01)
675 speed = dist / (remainingTimeEnroute / 3600.0);
677 SG_CLAMP_RANGE(speed, 300.0, 500.0);
684 score = ((double)hits / (double)runCount);
686 if (homePort ==
fgGetString(
"/sim/presets/airport-id")) {
703 double currentScore = score * (1.5 - lastRun);
704 double otherScore = other.score * (1.5 - other.lastRun);
705 return currentScore > otherScore;
std::vector< FGScheduledFlight * >::iterator FGScheduledFlightVecIterator
constexpr double TRAFFIC_TO_AI_DIST_TO_START
FGScheduledFlight * findAvailableFlight(const std::string ¤tDestination, const std::string &req, time_t min=0, time_t max=0)
static bool validModelPath(const std::string &model)
bool operator<(const FGAISchedule &other) const
std::string getCallSign()
time_t getDepartureTime()
std::string getFlightRules()
static SGPath resolveModelPath(const std::string &model)
static bool compareSchedules(const FGAISchedule *a, const FGAISchedule *b)
void clearAllFlights()
Warning - will empty the flights vector no matter what.
bool update(time_t now, const SGVec3d &userCart)
Returns true when processing is complete.
void setHeading()
Create an initial heading for user controlled aircraft.
void assign(FGScheduledFlight *ref)
FGAirport * getDepartureAirport()
FGAirport * getArrivalAirport()
const std::string & getId() const
virtual const SGGeod & geod() const
FGAirport * getArrivalAirport()
FGAirport * getDepartureAirport()
time_t getDepartureTime()
static bool compareScheduledFlights(const FGScheduledFlight *a, const FGScheduledFlight *b)
const std::string & getCallSign()
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.