123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- /*
- *openPilotLog - A FOSS Pilot Logbook Application
- *Copyright (C) 2020-2021 Felix Turowsky
- *
- *This program is free software: you can redistribute it and/or modify
- *it under the terms of the GNU General Public License as published by
- *the Free Software Foundation, either version 3 of the License, or
- *(at your option) any later version.
- *
- *This program is distributed in the hope that it will be useful,
- *but WITHOUT ANY WARRANTY; without even the implied warranty of
- *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- *GNU General Public License for more details.
- *
- *You should have received a copy of the GNU General Public License
- *along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- #include "acalc.h"
- #include "src/functions/alog.h"
- #include "src/database/adatabase.h"
- #include "src/classes/asettings.h"
- #include "src/opl.h"
- #include "src/functions/atime.h"
- using namespace ACalc;
- /*!
- * \brief ACalc::formatTimeInput verifies user input and formats to hh:mm
- * if the output is not a valid time, an empty string is returned. Accepts
- * input as hh:mm, h:mm, hhmm or hmm.
- * \param userinput from a QLineEdit
- * \return formatted QString "hh:mm" or Empty String
- */
- QT_DEPRECATED
- QString ACalc::formatTimeInput(QString user_input)
- {
- QString output; //
- QTime temp_time; //empty time object is invalid by default
- bool contains_seperator = user_input.contains(':');
- if (user_input.length() == 4 && !contains_seperator) {
- temp_time = QTime::fromString(user_input, QStringLiteral("hhmm"));
- } else if (user_input.length() == 3 && !contains_seperator) {
- if (user_input.toInt() < 240) { //Qtime is invalid if time is between 000 and 240 for this case
- QString tempstring = user_input.prepend(QStringLiteral("0"));
- temp_time = QTime::fromString(tempstring, QStringLiteral("hhmm"));
- } else {
- temp_time = QTime::fromString(user_input, QStringLiteral("Hmm"));
- }
- } else if (user_input.length() == 4 && contains_seperator) {
- temp_time = QTime::fromString(user_input, QStringLiteral("h:mm"));
- } else if (user_input.length() == 5 && contains_seperator) {
- temp_time = QTime::fromString(user_input, QStringLiteral("hh:mm"));
- }
- output = temp_time.toString(QStringLiteral("hh:mm"));
- if (output.isEmpty()) {
- DEB << "Time input is invalid.";
- }
- return output;
- }
- /*!
- * The purpose of the following functions is to provide functionality enabling the Calculation of
- * night flying time. EASA defines night as follows:
- *
- * ‘Night’ means the period between the end of evening civil twilight and the beginning of
- * morning civil twilight or such other period between sunset and sunrise as may be prescribed
- * by the appropriate authority, as defined by the Member State.
- *
- *
- *
- * This is the proccess of Calculating night time in this program:
- *
- * 1) A flight from A to B follows the Great Circle Track along these two points
- * at an average cruising height of 11km. (~FL 360)
- *
- * 2) Any time the Elevation of the Sun at the current position is less
- * than -6 degrees, night conditions are present.
- * 3) The Calculation is performed for every minute of flight time.
- *
- * In general, input and output for most functions is decimal degrees, like coordinates
- * are stowed in the airports table. Calculations are normally done using
- * Radians.
- */
- double ACalc::greatCircleDistance(double lat1, double lon1, double lat2, double lon2)
- {
- // Converting Latitude and Longitude to Radians
- lat1 = degToRad(lat1);
- lon1 = degToRad(lon1);
- lat2 = degToRad(lat2);
- lon2 = degToRad(lon2);
- // Haversine Formula
- double delta_lon = lon2 - lon1;
- double delta_lat = lat2 - lat1;
- double result = pow(sin(delta_lat / 2), 2) +
- cos(lat1) * cos(lat2) * pow(sin(delta_lon / 2), 2);
- result = 2 * asin(sqrt(result));
- return result;
- }
- double ACalc::greatCircleDistanceBetweenAirports(const QString &dept, const QString &dest)
- {
- QString statement = QLatin1String("SELECT lat, long FROM airports WHERE icao = '")
- + dept
- + QLatin1String("' OR icao = '")
- + dest
- + QLatin1String("'");
- auto lat_lon = aDB->customQuery(statement, 2);
- if (lat_lon.length() != 4) {
- DEB << "Invalid input. Aborting.";
- return 0;
- }
- double dept_lat = degToRad(lat_lon[0].toDouble());
- double dept_lon = degToRad(lat_lon[1].toDouble());
- double dest_lat = degToRad(lat_lon[2].toDouble());
- double dest_lon = degToRad(lat_lon[3].toDouble());
- // Haversine Formula
- double delta_lon = dest_lon - dept_lon;
- double delta_lat = dest_lat - dept_lat;
- double result = pow(sin(delta_lat / 2), 2) +
- cos(dept_lat) * cos(dest_lat) * pow(sin(delta_lon / 2), 2);
- result = 2 * asin(sqrt(result));
- return radToNauticalMiles(result);
- }
- QVector<QVector<double>> ACalc::intermediatePointsOnGreatCircle(double lat1, double lon1,
- double lat2, double lon2, int tblk)
- {
- double d = greatCircleDistance(lat1, lon1, lat2, lon2); //Calculate distance (Haversine)
- // where d is the great circle distance in radians, to get KM take * 6371 (radius of earth).
- //DEB << "Distance (nm): " << (d*6371.0088) * 0.5399568; // km->nm = *0.54
- // Converting Latitude and Longitude to Radians
- lat1 = degToRad(lat1);
- lon1 = degToRad(lon1);
- lat2 = degToRad(lat2);
- lon2 = degToRad(lon2);
- //loop for creating one minute steps along the Great Circle
- // 0 is departure point, 1 is end point
- QVector<QVector<double>> coordinates;
- double fraction = 1.0 / tblk;
- for (int i = 0; i <= tblk; i++) {
- // Calculating intermediate point for fraction of distance
- double A = sin((1 - fraction * i) * d) / sin(d);
- double B = sin(fraction * i * d) / sin(d);
- double x = A * cos(lat1) * cos(lon1) + B * cos(lat2) * cos(lon2);
- double y = A * cos(lat1) * sin(lon1) + B * cos(lat2) * sin(lon2);
- double z = A * sin(lat1) + B * sin(lat2);
- double lat = atan2(z, sqrt( pow(x, 2) + pow(y, 2) ));
- double lon = atan2(y, x);
- QVector<double> coordinate = {radToDeg(lat), radToDeg(lon)};
- coordinates.prepend(coordinate);
- }
- return coordinates;
- }
- double ACalc::solarElevation(QDateTime utc_time_point, double lat, double lon)
- {
- double Alt = 11; // Assuming 11 kilometers as an average cruising height for a commercial passenger airplane.
- // convert current DateTime Object to a J2000 value used in the Calculation
- double d = utc_time_point.date().toJulianDay() - 2451544 + utc_time_point.time().hour() / 24.0 +
- utc_time_point.time().minute() / 1440.0;
- // Orbital Elements (in degress)
- double w = 282.9404 + 4.70935e-5 * d; // (longitude of perihelion)
- double e = 0.016709 - 1.151e-9 * d; // (eccentricity)
- double M = fmod(356.0470 + 0.9856002585 * d,
- 360.0); // (mean anomaly, needs to be between 0 and 360 degrees)
- double oblecl = 23.4393 - 3.563e-7 * d; // (Sun's obliquity of the ecliptic)
- double L = w + M; // (Sun's mean longitude)
- // auxiliary angle
- double E = M + (180 / M_PI) * e * sin(M * (M_PI / 180)) * (1 + e * cos(M * (M_PI / 180)));
- // The Sun's rectangular coordinates in the plane of the ecliptic
- double x = cos(E * (M_PI / 180)) - e;
- double y = sin(E * (M_PI / 180)) * sqrt(1 - pow(e, 2));
- // find the distance and true anomaly
- double r = sqrt(pow(x, 2) + pow(y, 2));
- double v = atan2(y, x) * (180 / M_PI);
- // find the longitude of the sun
- double solar_longitude = v + w;
- // compute the ecliptic rectangular coordinates
- double x_eclip = r * cos(solar_longitude * (M_PI / 180));
- double y_eclip = r * sin(solar_longitude * (M_PI / 180));
- double z_eclip = 0.0;
- //rotate these coordinates to equitorial rectangular coordinates
- double x_equat = x_eclip;
- double y_equat = y_eclip * cos(oblecl * (M_PI / 180)) + z_eclip * sin(oblecl * (M_PI / 180));
- double z_equat = y_eclip * sin(23.4406 * (M_PI / 180)) + z_eclip * cos(oblecl * (M_PI / 180));
- // convert equatorial rectangular coordinates to RA and Decl:
- r = sqrt(pow(x_equat, 2) + pow(y_equat, 2) + pow(z_equat,
- 2)) - (Alt / 149598000); //roll up the altitude correction
- double RA = atan2(y_equat, x_equat) * (180 / M_PI);
- double delta = asin(z_equat / r) * (180 / M_PI);
- // GET UT in hours time
- double uth = utc_time_point.time().hour() + utc_time_point.time().minute() / 60.0 +
- utc_time_point.time().second() / 3600.0;
- // Calculate local siderial time
- double gmst0 = fmod(L + 180, 360.0) / 15;
- double sid_time = gmst0 + uth + lon / 15;
- // Replace RA with hour angle HA
- double HA = (sid_time * 15 - RA);
- // convert to rectangular coordinate system
- x = cos(HA * (M_PI / 180)) * cos(delta * (M_PI / 180));
- //y = sin(HA * (M_PI / 180)) * cos(delta * (M_PI / 180)); //value of y not needed
- double z = sin(delta * (M_PI / 180));
- // rotate this along an axis going east - west.
- double zhor = x * sin((90 - lat) * (M_PI / 180)) + z * cos((90 - lat) * (M_PI / 180));
- // Find the Elevation
- double elevation = asin(zhor) * (180 / M_PI);
- return elevation;
- }
- int ACalc::calculateNightTime(const QString &dept, const QString &dest, QDateTime departureTime, int tblk, int night_angle)
- {
- const QString statement = QLatin1String("SELECT lat, long FROM airports WHERE icao = '")
- + dept
- + QLatin1String("' OR icao = '") + dest
- + QLatin1String("'");
- auto lat_lon = aDB->customQuery(statement, 2);
- double dept_lat;
- double dept_lon;
- double dest_lat;
- double dest_lon;
- int night_time = 0;
- if (lat_lon.length() == 4) { // normal flight from A to B
- dept_lat = lat_lon[0].toDouble();
- dept_lon = lat_lon[1].toDouble();
- dest_lat = lat_lon[2].toDouble();
- dest_lon = lat_lon[3].toDouble();
- } else if (lat_lon.length() == 2 ) { // Dept == Dest, i.e. local flight
- for (int i = 0; i < tblk; i++) {
- dept_lat = lat_lon[0].toDouble();
- dept_lon = lat_lon[1].toDouble();
- if (solarElevation(departureTime.addSecs(60 * i), dept_lat, dept_lon) < night_angle)
- night_time++;
- }
- return night_time;
- } else {
- DEB << "Invalid input. Aborting.";
- return 0;
- }
- QVector<QVector<double>> route = intermediatePointsOnGreatCircle(dept_lat, dept_lon,
- dest_lat, dest_lon,
- tblk);
- for (int i = 0; i < tblk ; i++) {
- if (solarElevation(departureTime.addSecs(60 * i), route[i][0],
- route[i][1]) < night_angle) {
- night_time ++;
- }
- }
- return night_time;
- }
- bool ACalc::isNight(const QString &icao, QDateTime event_time, int night_angle)
- {
- const QString statement = QLatin1String("SELECT lat, long FROM airports WHERE icao = '")
- + icao
- + QLatin1String("'");
- auto lat_lon = aDB->customQuery(statement, 2);
- if (lat_lon.length() != 2) {
- DEB << "Invalid input. Aborting.";
- return 0;
- }
- double lat = lat_lon[0].toDouble();
- double lon = lat_lon[1].toDouble();
- if(solarElevation(event_time, lat, lon) < night_angle){
- return true;
- } else {
- return false;
- }
- }
- /*!
- * \brief ACalc::updateAutoTimes When the details of an aircraft are changed,
- * this function recalculates deductable times for this aircraft and updates
- * the database accordingly.
- * \param acft An aircraft object.
- * \return
- */
- void ACalc::updateAutoTimes(int acft_id)
- {
- //find all flights for aircraft
- const QString statement = QStringLiteral("SELECT flight_id FROM flights WHERE acft = ") + QString::number(acft_id);
- auto flight_list = aDB->customQuery(statement, 1);
- if (flight_list.isEmpty()) {
- DEB << "No flights for this tail found.";
- return;
- }
- DEB << "Updating " << flight_list.length() << " flights with this aircraft.";
- auto acft = aDB->getTailEntry(acft_id);
- auto acft_data = acft.getData();
- for (const auto& item : flight_list) {
- auto flight = aDB->getFlightEntry(item.toInt());
- auto flight_data = flight.getData();
- if(acft_data.value(OPL::Db::TAILS_MULTIPILOT).toInt() == 0
- && acft_data.value(OPL::Db::TAILS_MULTIENGINE) == 0) {
- DEB << "SPSE";
- flight_data.insert(OPL::Db::FLIGHTS_TSPSE, flight_data.value(OPL::Db::FLIGHTS_TBLK));
- flight_data.insert(OPL::Db::FLIGHTS_TSPME, QString());
- flight_data.insert(OPL::Db::FLIGHTS_TMP, QString());
- } else if ((acft_data.value(OPL::Db::TAILS_MULTIPILOT) == 0
- && acft.getData().value(OPL::Db::TAILS_MULTIENGINE) == 1)) {
- DEB << "SPME";
- flight_data.insert(OPL::Db::FLIGHTS_TSPME, flight_data.value(OPL::Db::FLIGHTS_TBLK));
- flight_data.insert(OPL::Db::FLIGHTS_TSPSE, QString());
- flight_data.insert(OPL::Db::FLIGHTS_TMP, QString());
- } else if ((acft_data.value(OPL::Db::TAILS_MULTIPILOT) == 1)) {
- DEB << "MPME";
- flight_data.insert(OPL::Db::FLIGHTS_TMP, flight_data.value(OPL::Db::FLIGHTS_TBLK));
- flight_data.insert(OPL::Db::FLIGHTS_TSPSE, QString());
- flight_data.insert(OPL::Db::FLIGHTS_TSPME, QString());
- }
- flight.setData(flight_data);
- aDB->commit(flight);
- }
- }
- /*!
- * \brief ACalc::updateNightTimes updates the night times in the database,
- * used when changing night angle setting for example
- */
- void ACalc::updateNightTimes()
- {
- const int night_angle = ASettings::read(ASettings::FlightLogging::NightAngle).toInt();
- //find all flights for aircraft
- auto statement = QStringLiteral("SELECT ROWID FROM flights");
- auto flight_list = aDB->customQuery(statement, 1);
- if (flight_list.isEmpty()) {
- DEB << "No flights found.";
- return;
- }
- DEB << "Updating " << flight_list.length() << " flights in the database.";
- for (const auto& item : flight_list) {
- auto flt = aDB->getFlightEntry(item.toInt());
- auto data = flt.getData();
- auto dateTime = QDateTime(QDate::fromString(data.value(OPL::Db::FLIGHTS_DOFT).toString(), Qt::ISODate),
- QTime().addSecs(data.value(OPL::Db::FLIGHTS_TOFB).toInt() * 60),
- Qt::UTC);
- data.insert(OPL::Db::FLIGHTS_TNIGHT,
- calculateNightTime(data.value(OPL::Db::FLIGHTS_DEPT).toString(),
- data.value(OPL::Db::FLIGHTS_DEST).toString(),
- dateTime,
- data.value(OPL::Db::FLIGHTS_TBLK).toInt(),
- night_angle));
- flt.setData(data);
- aDB->commit(flt);
- }
- }
|