/*
*openPilot Log - A FOSS Pilot Logbook Application
*Copyright (C) 2020 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 .
*/
#include "calc.h"
#include "dbman.cpp"
/*!
* \brief calc::blocktime Calculates Block Time for a given departure and arrival time
* \param tofb QTime Time Off Blocks
* \param tonb QTime Time On Blocks
* \return Block Time in minutes
*/
QTime calc::blocktime(QTime tofb, QTime tonb)
{
if(tonb > tofb)// landing same day
{
QTime blocktimeout(0,0); // initialise return value at midnight
int blockseconds = tofb.secsTo(tonb); // returns seconds between 2 time objects
blocktimeout = blocktimeout.addSecs(blockseconds);
return blocktimeout;
} else // landing next day
{
QTime midnight(0,0);
QTime blocktimeout(0,0); // initialise return value at midnight
int blockseconds = tofb.secsTo(midnight); // returns seconds passed until midnight
blocktimeout = blocktimeout.addSecs(blockseconds);
blockseconds = midnight.secsTo(tonb); // returns seconds passed after midnight
blocktimeout = blocktimeout.addSecs(blockseconds);
return blocktimeout;
}
}
/*!
* \brief calc::minutes_to_string Converts database time to String Time
* \param blockminutes int from database
* \return String hh:mm
*/
QString calc::minutes_to_string(QString blockminutes)
{
int minutes = blockminutes.toInt();
QString hour = QString::number(minutes/60);
if (hour.size() < 2) {hour.prepend("0");}
QString minute = QString::number(minutes % 60);
if (minute.size() < 2) {minute.prepend("0");}
QString blocktime = hour + ":" + minute;
return blocktime;
};
/*!
* \brief calc::time_to_minutes converts QTime to int minutes
* \param time QTime
* \return int time as number of minutes
*/
int calc::time_to_minutes(QTime time)
{
QString timestring = time.toString("hh:mm");
int minutes = (timestring.left(2).toInt()) * 60;
minutes += timestring.right(2).toInt();
return minutes;
}
/*!
* \brief calc::string_to_minutes Converts String Time to String Number of Minutes
* \param timestring "hh:mm"
* \return String number of minutes
*/
int calc::string_to_minutes(QString timestring)
{
int minutes = (timestring.left(2).toInt()) * 60;
minutes += timestring.right(2).toInt();
timestring = QString::number(minutes);
return minutes;
}
/*!
* 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.
*/
/*!
* \brief radToDeg Converts radians to degrees
* \param rad
* \return degrees
*/
double calc::radToDeg(double rad)
{
double deg = rad * (180 / M_PI);
return deg;
}
/*!
* \brief degToRad Converts degrees to radians
* \param deg
* \return radians
*/
double calc::degToRad(double deg)
{
double rad = deg * (M_PI / 180);
return rad;
}
/*!
* \brief radToNauticalMiles Convert Radians to nautical miles
* \param rad
* \return nautical miles
*/
double calc::radToNauticalMiles(double rad)
{
double nm = rad * 3440.06479482;
return nm;
}
/*!
* \brief greatCircleDistance Calculates Great Circle distance between two coordinates, return in Radians.
* \param lat1 Location Latitude in degrees -90:90 ;S(-) N(+)
* \param lon1 Location Longitude in degrees -180:180 W(-) E(+)
* \param lat2 Location Latitude in degrees -90:90 ;S(-) N(+)
* \param lon2 Location Longitude in degrees -180:180 W(-) E(+)
* \return
*/
double calc::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 deltalon = lon2 - lon1;
double deltalat = lat2 - lat1;
double result = pow(sin(deltalat / 2), 2) +
cos(lat1) * cos(lat2) * pow(sin(deltalon / 2), 2);
result = 2 * asin(sqrt(result));
return result;
}
/*!
* \brief Calculates a list of points (lat,lon) along the Great Circle between two points.
* The points are spaced equally, one minute of block time apart.
* \param lat1 Location Latitude in degrees -90:90 ;S(-) N(+)
* \param lon1 Location Longitude in degrees -180:180 W(-) E(+)
* \param lat2 Location Latitude in degrees -90:90 ;S(-) N(+)
* \param lon2 Location Longitude in degrees -180:180 W(-) E(+)
* \param tblk Total Blocktime in minutes
* \return coordinates {lat,lon} along the Great Circle Track
*/
QVector> calc::intermediatePointsOnGreatCircle(double lat1, double lon1, double lat2, double lon2, int tblk)
{
double d = greatCircleDistance(lat1, lon1, lat2, lon2); //calculate distance (radians)
// 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> 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 coordinate = {lat,lon};
coordinates.append(coordinate);
}
return coordinates;
}
/*!
* \brief Calculates solar elevation angle for a given point in time and latitude/longitude coordinates
*
* It is based on the formulas found here: http://stjarnhimlen.se/comp/tutorial.html#5
*
* Credit also goes to Darin C. Koblick for his matlab implementation of various of these
* formulas and to Kevin Godden for porting it to C++.
*
* Darin C. Koblock: https://www.mathworks.com/matlabcentral/profile/authors/1284781
* Kevin Godden: https://www.ridgesolutions.ie/index.php/about-us/
*
* \param utc_time_point - QDateTime (UTC) for which the elevation is calculated
* \param lat - Location Latitude in degrees -90:90 ;S(-) N(+)
* \param lon - Location Longitude in degrees -180:180 W(-) E(+)
* \return elevation - double of solar elevation in degrees.
*/
double calc::solarElevation(QDateTime utc_time_point, double lat, double lon)
{
double Alt = 11; // I am taking 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 solarlongitude = v + w;
// compute the ecliptic rectangular coordinates
double xeclip = r*cos(solarlongitude*(M_PI / 180));
double yeclip = r*sin(solarlongitude*(M_PI / 180));
double zeclip = 0.0;
//rotate these coordinates to equitorial rectangular coordinates
double xequat = xeclip;
double yequat = yeclip*cos(oblecl*(M_PI / 180)) + zeclip * sin(oblecl*(M_PI / 180));
double zequat = yeclip*sin(23.4406*(M_PI / 180)) + zeclip * cos(oblecl*(M_PI / 180));
// convert equatorial rectangular coordinates to RA and Decl:
r = sqrt(pow(xequat, 2) + pow(yequat, 2) + pow(zequat, 2)) - (Alt / 149598000); //roll up the altitude correction
double RA = atan2(yequat, xequat)*(180 / M_PI);
double delta = asin(zequat / r)*(180 / M_PI);
// GET UTH 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 SIDTIME = GMST0 + UTH + lon / 15;
// Replace RA with hour angle HA
double HA = (SIDTIME*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));
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;
}
/*!
* \brief Calculates which portion of a flight was conducted in night conditions.
* \param dept - ICAO 4-letter code of Departure Airport
* \param dest - ICAO 4-letter Code of Destination Airport
* \param departureTime - QDateTime of Departure (UTC)
* \param tblk - Total block time in minutes
* \return Total number of minutes under night flying conditions
*/
int calc::calculateNightTime(QString dept, QString dest, QDateTime departureTime, int tblk)
{
double deptLat = db::retreiveIcaoCoordinates(dept)[0];
qDebug() << "calc::calculateNightTime deptLat = " << deptLat;
double deptLon = db::retreiveIcaoCoordinates(dept)[1];
qDebug() << "calc::calculateNightTime deptLon = " << deptLon;
double destLat = db::retreiveIcaoCoordinates(dest)[0];
qDebug() << "calc::calculateNightTime destLat = " << destLat;
double destLon = db::retreiveIcaoCoordinates(dest)[1];
qDebug() << "calc::calculateNightTime destLon = " << destLon;
QVector> route = intermediatePointsOnGreatCircle(deptLat, deptLon, destLat, destLon, tblk);
int nightTime = 0;
for(int i = 0; i < tblk ; i++) {
if(solarElevation(departureTime.addSecs(60*i),radToDeg(route[i][0]),radToDeg(route[i][1])) < -0.6) {
nightTime ++;
}
}
qDebug() << "calc::calculateNightTime result: " << nightTime << " minutes night flying time.";
return nightTime;
}