calc.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. /*
  2. *openPilot Log - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020 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 "calc.h"
  19. //#include "dbman.cpp"
  20. /*!
  21. * \brief calc::blocktime Calculates Block Time for a given departure and arrival time
  22. * \param tofb QTime Time Off Blocks
  23. * \param tonb QTime Time On Blocks
  24. * \return Block Time in minutes
  25. */
  26. QTime calc::blocktime(QTime tofb, QTime tonb)
  27. {
  28. if(tonb > tofb)// landing same day
  29. {
  30. QTime blocktimeout(0,0); // initialise return value at midnight
  31. int blockseconds = tofb.secsTo(tonb); // returns seconds between 2 time objects
  32. blocktimeout = blocktimeout.addSecs(blockseconds);
  33. return blocktimeout;
  34. } else // landing next day
  35. {
  36. QTime midnight(0,0);
  37. QTime blocktimeout(0,0); // initialise return value at midnight
  38. int blockseconds = tofb.secsTo(midnight); // returns seconds passed until midnight
  39. blocktimeout = blocktimeout.addSecs(blockseconds);
  40. blockseconds = midnight.secsTo(tonb); // returns seconds passed after midnight
  41. blocktimeout = blocktimeout.addSecs(blockseconds);
  42. return blocktimeout;
  43. }
  44. }
  45. /*!
  46. * \brief calc::minutes_to_string Converts database time to String Time
  47. * \param blockminutes int from database
  48. * \return String hh:mm
  49. */
  50. QString calc::minutes_to_string(QString blockminutes)
  51. {
  52. int minutes = blockminutes.toInt();
  53. QString hour = QString::number(minutes/60);
  54. if (hour.size() < 2) {hour.prepend("0");}
  55. QString minute = QString::number(minutes % 60);
  56. if (minute.size() < 2) {minute.prepend("0");}
  57. QString blocktime = hour + ":" + minute;
  58. return blocktime;
  59. };
  60. /*!
  61. * \brief calc::time_to_minutes converts QTime to int minutes
  62. * \param time QTime
  63. * \return int time as number of minutes
  64. */
  65. int calc::time_to_minutes(QTime time)
  66. {
  67. QString timestring = time.toString("hh:mm");
  68. int minutes = (timestring.left(2).toInt()) * 60;
  69. minutes += timestring.right(2).toInt();
  70. return minutes;
  71. }
  72. /*!
  73. * \brief calc::string_to_minutes Converts String Time to String Number of Minutes
  74. * \param timestring "hh:mm"
  75. * \return String number of minutes
  76. */
  77. int calc::string_to_minutes(QString timestring)
  78. {
  79. int minutes = (timestring.left(2).toInt()) * 60;
  80. minutes += timestring.right(2).toInt();
  81. timestring = QString::number(minutes);
  82. return minutes;
  83. }
  84. /*!
  85. * The purpose of the following functions is to provide functionality enabling the calculation of
  86. * night flying time. EASA defines night as follows:
  87. *
  88. * ‘Night’ means the period between the end of evening civil twilight and the beginning of
  89. * morning civil twilight or such other period between sunset and sunrise as may be prescribed
  90. * by the appropriate authority, as defined by the Member State.
  91. *
  92. *
  93. *
  94. * This is the proccess of calculating night time in this program:
  95. *
  96. * 1) A flight from A to B follows the Great Circle Track along these two points
  97. * at an average cruising height of 11km. (~FL 360)
  98. *
  99. * 2) Any time the Elevation of the Sun at the current position is less
  100. * than -6 degrees, night conditions are present.
  101. * 3) The calculation is performed for every minute of flight time.
  102. *
  103. * In general, input and output for most functions is decimal degrees, like coordinates
  104. * are stowed in the airports table. Calculations are normally done using
  105. * Radians.
  106. */
  107. /*!
  108. * \brief radToDeg Converts radians to degrees
  109. * \param rad
  110. * \return degrees
  111. */
  112. double calc::radToDeg(double rad)
  113. {
  114. double deg = rad * (180 / M_PI);
  115. return deg;
  116. }
  117. /*!
  118. * \brief degToRad Converts degrees to radians
  119. * \param deg
  120. * \return radians
  121. */
  122. double calc::degToRad(double deg)
  123. {
  124. double rad = deg * (M_PI / 180);
  125. return rad;
  126. }
  127. /*!
  128. * \brief radToNauticalMiles Convert Radians to nautical miles
  129. * \param rad
  130. * \return nautical miles
  131. */
  132. double calc::radToNauticalMiles(double rad)
  133. {
  134. double nm = rad * 3440.06479482;
  135. return nm;
  136. }
  137. /*!
  138. * \brief greatCircleDistance Calculates Great Circle distance between two coordinates, return in Radians.
  139. * \param lat1 Location Latitude in degrees -90:90 ;S(-) N(+)
  140. * \param lon1 Location Longitude in degrees -180:180 W(-) E(+)
  141. * \param lat2 Location Latitude in degrees -90:90 ;S(-) N(+)
  142. * \param lon2 Location Longitude in degrees -180:180 W(-) E(+)
  143. * \return
  144. */
  145. double calc::greatCircleDistance(double lat1, double lon1, double lat2, double lon2)
  146. {
  147. // Converting Latitude and Longitude to Radians
  148. lat1 = degToRad(lat1);
  149. lon1 = degToRad(lon1);
  150. lat2 = degToRad(lat2);
  151. lon2 = degToRad(lon2);
  152. // Haversine Formula
  153. double deltalon = lon2 - lon1;
  154. double deltalat = lat2 - lat1;
  155. double result = pow(sin(deltalat / 2), 2) +
  156. cos(lat1) * cos(lat2) * pow(sin(deltalon / 2), 2);
  157. result = 2 * asin(sqrt(result));
  158. return result;
  159. }
  160. /*!
  161. * \brief calc::greatCircleDistanceBetweenAirports Calculates Great
  162. * Circle distance between two coordinates, return in nautical miles.
  163. * \param dept ICAO 4-letter Airport Identifier
  164. * \param dest ICAO 4-letter Airport Identifier
  165. * \return Nautical Miles From Departure to Destination
  166. */
  167. double calc::greatCircleDistanceBetweenAirports(QString dept, QString dest)
  168. {
  169. //db::multiSelect("airports", columns, "EDDF", "icao", db::exactMatch);
  170. /*if(dbAirport::retreiveIcaoCoordinates(dept).isEmpty() || dbAirport::retreiveIcaoCoordinates(dest).isEmpty()){
  171. qWarning() << "greatCircleDistance - invalid input. aborting.";
  172. return 0;
  173. }*/
  174. QVector<QString> columns = {"lat", "long"};
  175. QVector<QString> deptCoordinates = db::multiSelect(columns,"airports", "icao", dept, db::exactMatch);
  176. QVector<QString> destCoordinates = db::multiSelect(columns,"airports", "icao", dest, db::exactMatch);
  177. if(deptCoordinates.isEmpty() || destCoordinates.isEmpty()
  178. )
  179. {
  180. qDebug() << "greatCircleDistance - invalid input. aborting.";
  181. return 0;
  182. }
  183. double lat1 = degToRad(deptCoordinates[0].toDouble());
  184. double lon1 = degToRad(deptCoordinates[1].toDouble());
  185. double lat2 = degToRad(destCoordinates[0].toDouble());
  186. double lon2 = degToRad(destCoordinates[1].toDouble());
  187. // Haversine Formula
  188. double deltalon = lon2 - lon1;
  189. double deltalat = lat2 - lat1;
  190. double result = pow(sin(deltalat / 2), 2) +
  191. cos(lat1) * cos(lat2) * pow(sin(deltalon / 2), 2);
  192. result = 2 * asin(sqrt(result));
  193. return radToNauticalMiles(result);
  194. }
  195. /*!
  196. * \brief Calculates a list of points (lat,lon) along the Great Circle between two points.
  197. * The points are spaced equally, one minute of block time apart.
  198. * \param lat1 Location Latitude in degrees -90:90 ;S(-) N(+)
  199. * \param lon1 Location Longitude in degrees -180:180 W(-) E(+)
  200. * \param lat2 Location Latitude in degrees -90:90 ;S(-) N(+)
  201. * \param lon2 Location Longitude in degrees -180:180 W(-) E(+)
  202. * \param tblk Total Blocktime in minutes
  203. * \return coordinates {lat,lon} along the Great Circle Track
  204. */
  205. QVector<QVector<double>> calc::intermediatePointsOnGreatCircle(double lat1, double lon1, double lat2, double lon2, int tblk)
  206. {
  207. double d = greatCircleDistance(lat1, lon1, lat2, lon2); //calculate distance (radians)
  208. // Converting Latitude and Longitude to Radians
  209. lat1 = degToRad(lat1);
  210. lon1 = degToRad(lon1);
  211. lat2 = degToRad(lat2);
  212. lon2 = degToRad(lon2);
  213. //loop for creating one minute steps along the Great Circle
  214. // 0 is departure point, 1 is end point
  215. QVector<QVector<double>> coordinates;
  216. double fraction = 1.0/tblk;
  217. for(int i = 0; i <= tblk; i++) {
  218. // Calculating intermediate point for fraction of distance
  219. double A=sin((1-fraction * i) * d)/sin(d);
  220. double B=sin(fraction * i * d)/sin(d);
  221. double x = A*cos(lat1) * cos(lon1) + B * cos(lat2) * cos(lon2);
  222. double y = A*cos(lat1) * sin(lon1) + B * cos(lat2) * sin(lon2);
  223. double z = A*sin(lat1) + B * sin(lat2);
  224. double lat = atan2(z, sqrt( pow(x, 2) + pow(y, 2) ));
  225. double lon = atan2(y, x);
  226. QVector<double> coordinate = {lat,lon};
  227. coordinates.append(coordinate);
  228. }
  229. return coordinates;
  230. }
  231. /*!
  232. * \brief Calculates solar elevation angle for a given point in time and latitude/longitude coordinates
  233. *
  234. * It is based on the formulas found here: http://stjarnhimlen.se/comp/tutorial.html#5
  235. *
  236. * Credit also goes to Darin C. Koblick for his matlab implementation of various of these
  237. * formulas and to Kevin Godden for porting it to C++.
  238. *
  239. * Darin C. Koblock: https://www.mathworks.com/matlabcentral/profile/authors/1284781
  240. * Kevin Godden: https://www.ridgesolutions.ie/index.php/about-us/
  241. *
  242. * \param utc_time_point - QDateTime (UTC) for which the elevation is calculated
  243. * \param lat - Location Latitude in degrees -90:90 ;S(-) N(+)
  244. * \param lon - Location Longitude in degrees -180:180 W(-) E(+)
  245. * \return elevation - double of solar elevation in degrees.
  246. */
  247. double calc::solarElevation(QDateTime utc_time_point, double lat, double lon)
  248. {
  249. double Alt = 11; // I am taking 11 kilometers as an average cruising height for a commercial passenger airplane.
  250. // convert current DateTime Object to a J2000 value used in the calculation
  251. double d = utc_time_point.date().toJulianDay() - 2451544 + utc_time_point.time().hour()/24.0 + utc_time_point.time().minute()/1440.0;
  252. // Orbital Elements (in degress)
  253. double w = 282.9404 + 4.70935e-5 * d; // (longitude of perihelion)
  254. double e = 0.016709 - 1.151e-9 * d; // (eccentricity)
  255. double M = fmod(356.0470 + 0.9856002585 * d, 360.0); // (mean anomaly, needs to be between 0 and 360 degrees)
  256. double oblecl = 23.4393 - 3.563e-7*d; // (Sun's obliquity of the ecliptic)
  257. double L = w + M; // (Sun's mean longitude)
  258. // auxiliary angle
  259. double E = M + (180 / M_PI)*e*sin(M*(M_PI / 180))*(1 + e*cos(M*(M_PI / 180)));
  260. // The Sun's rectangular coordinates in the plane of the ecliptic
  261. double x = cos(E*(M_PI / 180)) - e;
  262. double y = sin(E*(M_PI / 180))*sqrt(1 - pow(e, 2));
  263. // find the distance and true anomaly
  264. double r = sqrt(pow(x,2) + pow(y,2));
  265. double v = atan2(y, x)*(180 / M_PI);
  266. // find the longitude of the sun
  267. double solarlongitude = v + w;
  268. // compute the ecliptic rectangular coordinates
  269. double xeclip = r*cos(solarlongitude*(M_PI / 180));
  270. double yeclip = r*sin(solarlongitude*(M_PI / 180));
  271. double zeclip = 0.0;
  272. //rotate these coordinates to equitorial rectangular coordinates
  273. double xequat = xeclip;
  274. double yequat = yeclip*cos(oblecl*(M_PI / 180)) + zeclip * sin(oblecl*(M_PI / 180));
  275. double zequat = yeclip*sin(23.4406*(M_PI / 180)) + zeclip * cos(oblecl*(M_PI / 180));
  276. // convert equatorial rectangular coordinates to RA and Decl:
  277. r = sqrt(pow(xequat, 2) + pow(yequat, 2) + pow(zequat, 2)) - (Alt / 149598000); //roll up the altitude correction
  278. double RA = atan2(yequat, xequat)*(180 / M_PI);
  279. double delta = asin(zequat / r)*(180 / M_PI);
  280. // GET UTH time
  281. double UTH = utc_time_point.time().hour() + utc_time_point.time().minute()/60.0 + utc_time_point.time().second()/3600.0;
  282. // Calculate local siderial time
  283. double GMST0 = fmod(L + 180, 360.0) / 15;
  284. double SIDTIME = GMST0 + UTH + lon / 15;
  285. // Replace RA with hour angle HA
  286. double HA = (SIDTIME*15 - RA);
  287. // convert to rectangular coordinate system
  288. x = cos(HA*(M_PI / 180))*cos(delta*(M_PI / 180));
  289. y = sin(HA*(M_PI / 180))*cos(delta*(M_PI / 180));
  290. double z = sin(delta*(M_PI / 180));
  291. // rotate this along an axis going east - west.
  292. double zhor = x*sin((90 - lat)*(M_PI / 180)) + z*cos((90 - lat)*(M_PI / 180));
  293. // Find the Elevation
  294. double elevation = asin(zhor)*(180 / M_PI);
  295. return elevation;
  296. }
  297. /*!
  298. * \brief Calculates which portion of a flight was conducted in night conditions.
  299. * \param dept - ICAO 4-letter code of Departure Airport
  300. * \param dest - ICAO 4-letter Code of Destination Airport
  301. * \param departureTime - QDateTime of Departure (UTC)
  302. * \param tblk - Total block time in minutes
  303. * \return Total number of minutes under night flying conditions
  304. */
  305. int calc::calculateNightTime(QString dept, QString dest, QDateTime departureTime, int tblk)
  306. {
  307. QVector<QString> columns = {"lat", "long"};
  308. QVector<QString> deptCoordinates = db::multiSelect(columns,"airports", "icao", dept, db::exactMatch);
  309. QVector<QString> destCoordinates = db::multiSelect(columns,"airports", "icao", dest, db::exactMatch);
  310. if(deptCoordinates.isEmpty() || destCoordinates.isEmpty()
  311. )
  312. {
  313. qDebug() << "calc::calculateNightTime - invalid input. aborting.";
  314. return 0;
  315. }
  316. double deptLat = degToRad(deptCoordinates[0].toDouble());
  317. double deptLon = degToRad(deptCoordinates[1].toDouble());
  318. double destLat = degToRad(destCoordinates[0].toDouble());
  319. double destLon = degToRad(destCoordinates[1].toDouble());
  320. qDebug() << "calc::calculateNightTime deptLat = " << deptLat;
  321. qDebug() << "calc::calculateNightTime deptLon = " << deptLon;
  322. qDebug() << "calc::calculateNightTime destLat = " << destLat;
  323. qDebug() << "calc::calculateNightTime destLon = " << destLon;
  324. QVector<QVector<double>> route = intermediatePointsOnGreatCircle(deptLat, deptLon, destLat, destLon, tblk);
  325. int nightTime = 0;
  326. for(int i = 0; i < tblk ; i++) {
  327. if(solarElevation(departureTime.addSecs(60*i),radToDeg(route[i][0]),radToDeg(route[i][1])) < -0.6) {
  328. nightTime ++;
  329. }
  330. }
  331. qDebug() << "calc::calculateNightTime result: " << nightTime << " minutes night flying time.";
  332. return nightTime;
  333. }
  334. /*!
  335. * \brief calc::formatTimeInput verifies user input and formats to hh:mm
  336. * if the output is not a valid time, an empty string is returned. Accepts
  337. * input as hh:mm, h:mm, hhmm or hmm.
  338. * \param userinput from a QLineEdit
  339. * \return formatted QString "hh:mm" or Empty String
  340. */
  341. QString calc::formatTimeInput(QString userinput)
  342. {
  343. QString output; //
  344. QTime temptime; //empty time object is invalid by default
  345. bool containsSeperator = userinput.contains(":");
  346. if(userinput.length() == 4 && !containsSeperator)
  347. {
  348. temptime = QTime::fromString(userinput,"hhmm");
  349. }else if(userinput.length() == 3 && !containsSeperator)
  350. {
  351. if(userinput.toInt() < 240) //Qtime is invalid if time is between 000 and 240 for this case
  352. {
  353. QString tempstring = userinput.prepend("0");
  354. temptime = QTime::fromString(tempstring,"hhmm");
  355. }else
  356. {
  357. temptime = QTime::fromString(userinput,"Hmm");
  358. }
  359. }else if(userinput.length() == 4 && containsSeperator)
  360. {
  361. temptime = QTime::fromString(userinput,"h:mm");
  362. }else if(userinput.length() == 5 && containsSeperator)
  363. {
  364. temptime = QTime::fromString(userinput,"hh:mm");
  365. }
  366. output = temptime.toString("hh:mm");
  367. if(output.isEmpty())
  368. {
  369. /*QMessageBox timeformat(this);
  370. timeformat.setText("Please enter a valid time. Any of these formats is valid:\n845 0845 8:45 08:45");
  371. timeformat.exec();*/
  372. qDebug() << "Time input is invalid.";
  373. }
  374. return output;
  375. }