acalc.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #include "acalc.h"
  2. #include "src/testing/adebug.h"
  3. #include "src/database/adatabase.h"
  4. #include "src/classes/asettings.h"
  5. using namespace ACalc;
  6. /*!
  7. * \brief ACalc::formatTimeInput verifies user input and formats to hh:mm
  8. * if the output is not a valid time, an empty string is returned. Accepts
  9. * input as hh:mm, h:mm, hhmm or hmm.
  10. * \param userinput from a QLineEdit
  11. * \return formatted QString "hh:mm" or Empty String
  12. */
  13. QString ACalc::formatTimeInput(QString user_input)
  14. {
  15. QString output; //
  16. QTime temp_time; //empty time object is invalid by default
  17. bool contains_seperator = user_input.contains(':');
  18. if (user_input.length() == 4 && !contains_seperator) {
  19. temp_time = QTime::fromString(user_input, QStringLiteral("hhmm"));
  20. } else if (user_input.length() == 3 && !contains_seperator) {
  21. if (user_input.toInt() < 240) { //Qtime is invalid if time is between 000 and 240 for this case
  22. QString tempstring = user_input.prepend(QStringLiteral("0"));
  23. temp_time = QTime::fromString(tempstring, QStringLiteral("hhmm"));
  24. } else {
  25. temp_time = QTime::fromString(user_input, QStringLiteral("Hmm"));
  26. }
  27. } else if (user_input.length() == 4 && contains_seperator) {
  28. temp_time = QTime::fromString(user_input, QStringLiteral("h:mm"));
  29. } else if (user_input.length() == 5 && contains_seperator) {
  30. temp_time = QTime::fromString(user_input, QStringLiteral("hh:mm"));
  31. }
  32. output = temp_time.toString(QStringLiteral("hh:mm"));
  33. if (output.isEmpty()) {
  34. DEB << "Time input is invalid.";
  35. }
  36. return output;
  37. }
  38. /*!
  39. * The purpose of the following functions is to provide functionality enabling the Calculation of
  40. * night flying time. EASA defines night as follows:
  41. *
  42. * ‘Night’ means the period between the end of evening civil twilight and the beginning of
  43. * morning civil twilight or such other period between sunset and sunrise as may be prescribed
  44. * by the appropriate authority, as defined by the Member State.
  45. *
  46. *
  47. *
  48. * This is the proccess of Calculating night time in this program:
  49. *
  50. * 1) A flight from A to B follows the Great Circle Track along these two points
  51. * at an average cruising height of 11km. (~FL 360)
  52. *
  53. * 2) Any time the Elevation of the Sun at the current position is less
  54. * than -6 degrees, night conditions are present.
  55. * 3) The Calculation is performed for every minute of flight time.
  56. *
  57. * In general, input and output for most functions is decimal degrees, like coordinates
  58. * are stowed in the airports table. Calculations are normally done using
  59. * Radians.
  60. */
  61. double ACalc::greatCircleDistance(double lat1, double lon1, double lat2, double lon2)
  62. {
  63. // Converting Latitude and Longitude to Radians
  64. lat1 = degToRad(lat1);
  65. lon1 = degToRad(lon1);
  66. lat2 = degToRad(lat2);
  67. lon2 = degToRad(lon2);
  68. // Haversine Formula
  69. double delta_lon = lon2 - lon1;
  70. double delta_lat = lat2 - lat1;
  71. double result = pow(sin(delta_lat / 2), 2) +
  72. cos(lat1) * cos(lat2) * pow(sin(delta_lon / 2), 2);
  73. result = 2 * asin(sqrt(result));
  74. return result;
  75. }
  76. double ACalc::greatCircleDistanceBetweenAirports(const QString &dept, const QString &dest)
  77. {
  78. auto statement = "SELECT lat, long FROM airports WHERE icao = '"
  79. + dept + "' OR icao = '" + dest + "'";
  80. auto lat_lon = aDB()->customQuery(statement, 2);
  81. if (lat_lon.length() != 4) {
  82. DEB << "Invalid input. Aborting.";
  83. return 0;
  84. }
  85. double dept_lat = degToRad(lat_lon[0].toDouble());
  86. double dept_lon = degToRad(lat_lon[1].toDouble());
  87. double dest_lat = degToRad(lat_lon[2].toDouble());
  88. double dest_lon = degToRad(lat_lon[3].toDouble());
  89. // Haversine Formula
  90. double delta_lon = dest_lon - dept_lon;
  91. double delta_lat = dest_lat - dept_lat;
  92. double result = pow(sin(delta_lat / 2), 2) +
  93. cos(dept_lat) * cos(dest_lat) * pow(sin(delta_lon / 2), 2);
  94. result = 2 * asin(sqrt(result));
  95. return radToNauticalMiles(result);
  96. }
  97. QVector<QVector<double>> ACalc::intermediatePointsOnGreatCircle(double lat1, double lon1,
  98. double lat2, double lon2, int tblk)
  99. {
  100. double d = greatCircleDistance(lat1, lon1, lat2, lon2); //Calculate distance (radians)
  101. // Converting Latitude and Longitude to Radians
  102. lat1 = degToRad(lat1);
  103. lon1 = degToRad(lon1);
  104. lat2 = degToRad(lat2);
  105. lon2 = degToRad(lon2);
  106. //loop for creating one minute steps along the Great Circle
  107. // 0 is departure point, 1 is end point
  108. QVector<QVector<double>> coordinates;
  109. double fraction = 1.0 / tblk;
  110. for (int i = 0; i <= tblk; i++) {
  111. // Calculating intermediate point for fraction of distance
  112. double A = sin((1 - fraction * i) * d) / sin(d);
  113. double B = sin(fraction * i * d) / sin(d);
  114. double x = A * cos(lat1) * cos(lon1) + B * cos(lat2) * cos(lon2);
  115. double y = A * cos(lat1) * sin(lon1) + B * cos(lat2) * sin(lon2);
  116. double z = A * sin(lat1) + B * sin(lat2);
  117. double lat = atan2(z, sqrt( pow(x, 2) + pow(y, 2) ));
  118. double lon = atan2(y, x);
  119. QVector<double> coordinate = {lat, lon};
  120. coordinates.append(coordinate);
  121. }
  122. return coordinates;
  123. }
  124. double ACalc::solarElevation(QDateTime utc_time_point, double lat, double lon)
  125. {
  126. double Alt =
  127. 11; // I am taking 11 kilometers as an average cruising height for a commercial passenger airplane.
  128. // convert current DateTime Object to a J2000 value used in the Calculation
  129. double d = utc_time_point.date().toJulianDay() - 2451544 + utc_time_point.time().hour() / 24.0 +
  130. utc_time_point.time().minute() / 1440.0;
  131. // Orbital Elements (in degress)
  132. double w = 282.9404 + 4.70935e-5 * d; // (longitude of perihelion)
  133. double e = 0.016709 - 1.151e-9 * d; // (eccentricity)
  134. double M = fmod(356.0470 + 0.9856002585 * d,
  135. 360.0); // (mean anomaly, needs to be between 0 and 360 degrees)
  136. double oblecl = 23.4393 - 3.563e-7 * d; // (Sun's obliquity of the ecliptic)
  137. double L = w + M; // (Sun's mean longitude)
  138. // auxiliary angle
  139. double E = M + (180 / M_PI) * e * sin(M * (M_PI / 180)) * (1 + e * cos(M * (M_PI / 180)));
  140. // The Sun's rectangular coordinates in the plane of the ecliptic
  141. double x = cos(E * (M_PI / 180)) - e;
  142. double y = sin(E * (M_PI / 180)) * sqrt(1 - pow(e, 2));
  143. // find the distance and true anomaly
  144. double r = sqrt(pow(x, 2) + pow(y, 2));
  145. double v = atan2(y, x) * (180 / M_PI);
  146. // find the longitude of the sun
  147. double solar_longitude = v + w;
  148. // compute the ecliptic rectangular coordinates
  149. double x_eclip = r * cos(solar_longitude * (M_PI / 180));
  150. double y_eclip = r * sin(solar_longitude * (M_PI / 180));
  151. double z_eclip = 0.0;
  152. //rotate these coordinates to equitorial rectangular coordinates
  153. double x_equat = x_eclip;
  154. double y_equat = y_eclip * cos(oblecl * (M_PI / 180)) + z_eclip * sin(oblecl * (M_PI / 180));
  155. double z_equat = y_eclip * sin(23.4406 * (M_PI / 180)) + z_eclip * cos(oblecl * (M_PI / 180));
  156. // convert equatorial rectangular coordinates to RA and Decl:
  157. r = sqrt(pow(x_equat, 2) + pow(y_equat, 2) + pow(z_equat,
  158. 2)) - (Alt / 149598000); //roll up the altitude correction
  159. double RA = atan2(y_equat, x_equat) * (180 / M_PI);
  160. double delta = asin(z_equat / r) * (180 / M_PI);
  161. // GET UT in hours time
  162. double uth = utc_time_point.time().hour() + utc_time_point.time().minute() / 60.0 +
  163. utc_time_point.time().second() / 3600.0;
  164. // Calculate local siderial time
  165. double gmst0 = fmod(L + 180, 360.0) / 15;
  166. double sid_time = gmst0 + uth + lon / 15;
  167. // Replace RA with hour angle HA
  168. double HA = (sid_time * 15 - RA);
  169. // convert to rectangular coordinate system
  170. x = cos(HA * (M_PI / 180)) * cos(delta * (M_PI / 180));
  171. y = sin(HA * (M_PI / 180)) * cos(delta * (M_PI / 180));
  172. double z = sin(delta * (M_PI / 180));
  173. // rotate this along an axis going east - west.
  174. double zhor = x * sin((90 - lat) * (M_PI / 180)) + z * cos((90 - lat) * (M_PI / 180));
  175. // Find the Elevation
  176. double elevation = asin(zhor) * (180 / M_PI);
  177. return elevation;
  178. }
  179. int ACalc::calculateNightTime(const QString &dept, const QString &dest, QDateTime departureTime, int tblk, int nightAngle)
  180. {
  181. auto statement = "SELECT lat, long FROM airports WHERE icao = '" + dept + "' OR icao = '" + dest + "'";
  182. auto lat_lon = aDB()->customQuery(statement, 2);
  183. if (lat_lon.length() != 4) {
  184. DEB << "Invalid input. Aborting.";
  185. return 0;
  186. }
  187. double dept_lat = degToRad(lat_lon[0].toDouble());
  188. double dept_lon = degToRad(lat_lon[1].toDouble());
  189. double dest_lat = degToRad(lat_lon[2].toDouble());
  190. double dest_lon = degToRad(lat_lon[3].toDouble());
  191. QVector<QVector<double>> route = intermediatePointsOnGreatCircle(dept_lat, dept_lon,
  192. dest_lat, dest_lon,
  193. tblk);
  194. int night_time = 0;
  195. for (int i = 0; i < tblk ; i++) {
  196. if (solarElevation(departureTime.addSecs(60 * i), radToDeg(route[i][0]),
  197. radToDeg(route[i][1])) < nightAngle) {
  198. night_time ++;
  199. }
  200. }
  201. return night_time;
  202. }
  203. bool ACalc::isNight(const QString &icao, QDateTime event_time, int night_angle)
  204. {
  205. auto statement = "SELECT lat, long FROM airports WHERE icao = '" + icao + "'";
  206. auto lat_lon = aDB()->customQuery(statement, 2);
  207. if (lat_lon.length() != 2) {
  208. DEB << "Invalid input. Aborting.";
  209. return 0;
  210. }
  211. double lat = degToRad(lat_lon[0].toDouble());
  212. double lon = degToRad(lat_lon[1].toDouble());
  213. if(solarElevation(event_time, lat, lon) < night_angle){
  214. return true;
  215. } else {
  216. return false;
  217. }
  218. }
  219. /*!
  220. * \brief ACalc::updateAutoTimes When the details of an aircraft are changed,
  221. * this function recalculates deductable times for this aircraft and updates
  222. * the database accordingly.
  223. * \param acft An aircraft object.
  224. * \return
  225. */
  226. void ACalc::updateAutoTimes(int acft_id)
  227. {
  228. //find all flights for aircraft
  229. const QString statement = QStringLiteral("SELECT flight_id FROM flights WHERE acft = ") + QString::number(acft_id);
  230. auto flight_list = aDB()->customQuery(statement, 1);
  231. if (flight_list.isEmpty()) {
  232. DEB << "No flights for this tail found.";
  233. return;
  234. }
  235. DEB << "Updating " << flight_list.length() << " flights with this aircraft.";
  236. auto acft = aDB()->getTailEntry(acft_id);
  237. auto acft_data = acft.getData();
  238. for (const auto& item : flight_list) {
  239. auto flight = aDB()->getFlightEntry(item.toInt());
  240. auto flight_data = flight.getData();
  241. if(acft_data.value(DB_TAILS_MULTIPILOT).toInt() == 0
  242. && acft_data.value(DB_TAILS_MULTIENGINE) == 0) {
  243. DEB << "SPSE";
  244. flight_data.insert(DB_FLIGHTS_TSPSE, flight_data.value(DB_FLIGHTS_TBLK));
  245. flight_data.insert(DB_FLIGHTS_TSPME, DB_NULL);
  246. flight_data.insert(DB_FLIGHTS_TMP, DB_NULL);
  247. } else if ((acft_data.value(DB_TAILS_MULTIPILOT) == 0
  248. && acft.getData().value(DB_TAILS_MULTIENGINE) == 1)) {
  249. DEB << "SPME";
  250. flight_data.insert(DB_FLIGHTS_TSPME, flight_data.value(DB_FLIGHTS_TBLK));
  251. flight_data.insert(DB_FLIGHTS_TSPSE, DB_NULL);
  252. flight_data.insert(DB_FLIGHTS_TMP, DB_NULL);
  253. } else if ((acft_data.value(DB_TAILS_MULTIPILOT) == 1)) {
  254. DEB << "MPME";
  255. flight_data.insert(DB_FLIGHTS_TMP, flight_data.value(DB_FLIGHTS_TBLK));
  256. flight_data.insert(DB_FLIGHTS_TSPSE, DB_NULL);
  257. flight_data.insert(DB_FLIGHTS_TSPME, DB_NULL);
  258. }
  259. flight.setData(flight_data);
  260. aDB()->commit(flight);
  261. }
  262. }
  263. /*!
  264. * \brief ACalc::updateNightTimes updates the night times in the database,
  265. * used when changing night angle setting for example
  266. */
  267. void ACalc::updateNightTimes()
  268. {
  269. const int night_angle = ASettings::read(ASettings::FlightLogging::NightAngle).toInt();
  270. //find all flights for aircraft
  271. auto statement = "SELECT ROWID FROM flights";
  272. auto flight_list = aDB()->customQuery(statement, 1);
  273. if (flight_list.isEmpty()) {
  274. DEB << "No flights found.";
  275. return;
  276. }
  277. DEB << "Updating " << flight_list.length() << " flights in the database.";
  278. for (const auto& item : flight_list) {
  279. auto flt = aDB()->getFlightEntry(item.toInt());
  280. auto data = flt.getData();
  281. auto dateTime = QDateTime(QDate::fromString(data.value(DB_FLIGHTS_DOFT).toString(), Qt::ISODate),
  282. QTime().addSecs(data.value(DB_FLIGHTS_TOFB).toInt() * 60),
  283. Qt::UTC);
  284. data.insert(DB_FLIGHTS_TNIGHT,
  285. calculateNightTime(data.value(DB_FLIGHTS_DEPT).toString(),
  286. data.value(DB_FLIGHTS_DEST).toString(),
  287. dateTime,
  288. data.value(DB_FLIGHTS_TBLK).toInt(),
  289. night_angle));
  290. flt.setData(data);
  291. aDB()->commit(flt);
  292. }
  293. }