acalc.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. #include "acalc.h"
  2. #include "src/testing/adebug.h"
  3. #include "src/experimental/adatabase.h"
  4. using namespace ACalc;
  5. using namespace experimental;
  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, "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("0");
  23. temp_time = QTime::fromString(tempstring, "hhmm");
  24. } else {
  25. temp_time = QTime::fromString(user_input, "Hmm");
  26. }
  27. } else if (user_input.length() == 4 && contains_seperator) {
  28. temp_time = QTime::fromString(user_input, "h:mm");
  29. } else if (user_input.length() == 5 && contains_seperator) {
  30. temp_time = QTime::fromString(user_input, "hh:mm");
  31. }
  32. output = temp_time.toString("hh:mm");
  33. if (output.isEmpty()) {
  34. qDebug() << "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 = '" + dept;
  79. statement.append("' 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;
  182. statement.append("' OR icao = '" + dest + "'");
  183. auto lat_lon = aDB()->customQuery(statement, 2);
  184. if (lat_lon.length() != 4) {
  185. DEB("Invalid input. Aborting.");
  186. return 0;
  187. }
  188. double dept_lat = degToRad(lat_lon[0].toDouble());
  189. double dept_lon = degToRad(lat_lon[1].toDouble());
  190. double dest_lat = degToRad(lat_lon[2].toDouble());
  191. double dest_lon = degToRad(lat_lon[3].toDouble());
  192. QVector<QVector<double>> route = intermediatePointsOnGreatCircle(dept_lat, dept_lon,
  193. dest_lat, dest_lon,
  194. tblk);
  195. int night_time = 0;
  196. for (int i = 0; i < tblk ; i++) {
  197. if (solarElevation(departureTime.addSecs(60 * i), radToDeg(route[i][0]),
  198. radToDeg(route[i][1])) < nightAngle) {
  199. night_time ++;
  200. }
  201. }
  202. return night_time;
  203. }
  204. bool ACalc::isNight(const QString &icao, QDateTime event_time, int night_angle)
  205. {
  206. auto statement = "SELECT lat, long FROM airports WHERE icao = '" + icao + "'";
  207. auto lat_lon = aDB()->customQuery(statement, 2);
  208. if (lat_lon.length() != 2) {
  209. DEB("Invalid input. Aborting.");
  210. return 0;
  211. }
  212. double lat = degToRad(lat_lon[0].toDouble());
  213. double lon = degToRad(lat_lon[1].toDouble());
  214. if(solarElevation(event_time, lat, lon) < night_angle){
  215. return true;
  216. } else {
  217. return false;
  218. }
  219. }
  220. /*!
  221. * \brief ACalc::updateAutoTimes When the details of an aircraft are changed,
  222. * this function recalculates deductable times for this aircraft and updates
  223. * the database accordingly.
  224. * \param acft An aircraft object.
  225. * \return
  226. */
  227. void ACalc::updateAutoTimes(int acft_id)
  228. {
  229. //find all flights for aircraft
  230. auto statement = "SELECT flight_id FROM flights WHERE acft = " + QString::number(acft_id);
  231. auto flight_list = aDB()->customQuery(statement, 1);
  232. if (flight_list.isEmpty()) {
  233. DEB("No flights for this tail found.");
  234. return;
  235. }
  236. DEB("Updating " << flight_list.length() << " flights with this aircraft.");
  237. auto acft = aDB()->getTailEntry(acft_id);
  238. auto acft_data = acft.getData();
  239. for (const auto& item : flight_list) {
  240. auto flight = aDB()->getFlightEntry(item.toInt());
  241. auto flight_data = flight.getData();
  242. if(acft_data.value("multipilot") == "0" && acft_data.value("multiengine") == "0") {
  243. DEB("SPSE");
  244. flight_data.insert("tSPSE", flight_data.value("tblk"));
  245. flight_data.insert("tSPME", "");
  246. flight_data.insert("tMP", "");
  247. } else if ((acft_data.value("multipilot") == "0" && acft.getData().value("multiengine") == "1")) {
  248. DEB("SPME");
  249. flight_data.insert("tSPME", flight_data.value("tblk"));
  250. flight_data.insert("tSPSE", "");
  251. flight_data.insert("tMP", "");
  252. } else if ((acft_data.value("multipilot") == "1")) {
  253. DEB("MPME");
  254. flight_data.insert("tMP", flight_data.value("tblk"));
  255. flight_data.insert("tSPSE", "");
  256. flight_data.insert("tSPME", "");
  257. }
  258. flight.setData(flight_data);
  259. aDB()->commit(flight);
  260. }
  261. }
  262. /*!
  263. * \brief ACalc::updateNightTimes updates the night times in the database,
  264. * used when changing night angle setting for example
  265. */
  266. void ACalc::updateNightTimes()
  267. {
  268. const int night_angle = ASettings::read("flightlogging/nightangle").toInt();
  269. //find all flights for aircraft
  270. auto statement = "SELECT ROWID FROM flights";
  271. auto flight_list = aDB()->customQuery(statement, 1);
  272. if (flight_list.isEmpty()) {
  273. DEB("No flights found.");
  274. return;
  275. }
  276. DEB("Updating " << flight_list.length() << " flights in the database.");
  277. for (const auto& item : flight_list) {
  278. auto flt = aDB()->getFlightEntry(item.toInt());
  279. auto data = flt.getData();
  280. auto dateTime = QDateTime(QDate::fromString(data.value("doft"),Qt::ISODate),
  281. QTime().addSecs(data.value("tofb").toInt() * 60),
  282. Qt::UTC);
  283. data.insert("tNIGHT", QString::number(
  284. calculateNightTime(data.value("dept"),
  285. data.value("dest"),
  286. dateTime,
  287. data.value("tblk").toInt(),
  288. night_angle)));
  289. flt.setData(data);
  290. aDB()->commit(flt);
  291. }
  292. }