acalc.cpp 15 KB

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