QGIS API Documentation 4.1.0-Master (376402f9aeb)
Loading...
Searching...
No Matches
qgssunpositioncalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssunpositioncalculator.cpp
3 -----------------------------
4 Date : April 2026
5 Copyright : (C) 2026 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
21#include "qgsexception.h"
22#include "qgspointxy.h"
23
24#include <QString>
25
26using namespace Qt::StringLiterals;
27
28#define FS_TIME_T int64_t
29extern "C"
30{
31#include <freespa.h>
32}
33
35 const QgsPointXY &point, const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &transformContext, const QDateTime &dateTime, double elevationMeters, double pressure, double temperature
36)
37{
39 const QgsCoordinateReferenceSystem wgs84( u"EPSG:4326"_s );
40 QgsPointXY latLonPoint = point;
41 if ( crs != wgs84 )
42 {
43 // may throw QgsCsException, which we want raised to the caller
44 QgsCoordinateTransform ct( crs, wgs84, transformContext );
45 latLonPoint = ct.transform( point );
46 }
47
48 // freespa uses radians for angles
49 const double longitudeRad = latLonPoint.x() * M_PI / 180.0;
50 const double latitudeRad = latLonPoint.y() * M_PI / 180.0;
51
52 const QDateTime utcTime = dateTime.toUTC();
53 struct tm ut_tm;
54 memset( &ut_tm, 0, sizeof( struct tm ) );
55 ut_tm.tm_year = utcTime.date().year() - 1900;
56 ut_tm.tm_mon = utcTime.date().month() - 1;
57 ut_tm.tm_mday = utcTime.date().day();
58 ut_tm.tm_hour = utcTime.time().hour();
59 ut_tm.tm_min = utcTime.time().minute();
60 ut_tm.tm_sec = utcTime.time().second();
61
62 // calculate real solar position
63 const sol_pos realPos = SPA( &ut_tm, nullptr, 0.0, longitudeRad, latitudeRad, elevationMeters );
64 if ( realPos.E != 0 )
65 {
66 throw QgsInvalidArgumentException( u"Invalid freespa calculation: "_s + extractSpaErrorMessage( realPos.E ) );
67 }
68
69 // calculate apparent solar position (applies atmospheric refraction)
70 const sol_pos apparentPos = ApSolposBennet( realPos, nullptr, elevationMeters, pressure, temperature );
71 if ( apparentPos.E != 0 )
72 {
73 throw QgsInvalidArgumentException( u"Invalid freespa calculation: "_s + extractSpaErrorMessage( apparentPos.E ) );
74 }
75
76 // convert back to degrees
77 result.azimuth = apparentPos.a * 180.0 / M_PI;
78 const double zenithDegrees = apparentPos.z * 180.0 / M_PI;
79 result.apparentElevation = 90.0 - zenithDegrees;
80
81 // calculate solar events
82 const solar_day dayEvents = SolarDay( &ut_tm, nullptr, 0.0, longitudeRad, latitudeRad, elevationMeters, nullptr, pressure, temperature, ApSolposBennet );
83
84 auto extractEvent = []( const solar_day &sd, int index ) -> QDateTime {
85 if ( sd.status[index] == _FREESPA_EV_ERR )
86 {
87 throw QgsInvalidArgumentException( u"Error calculating solar event at index %1"_s.arg( index ) );
88 }
89 else if ( sd.status[index] == _FREESPA_EV_OK )
90 {
91 return QDateTime( QDate( sd.ev[index].tm_year + 1900, sd.ev[index].tm_mon + 1, sd.ev[index].tm_mday ), QTime( sd.ev[index].tm_hour, sd.ev[index].tm_min, sd.ev[index].tm_sec ), Qt::UTC );
92 }
93 // event doesn't occur
94 return QDateTime();
95 };
96
97 result.solarMidnightBefore = extractEvent( dayEvents, 0 );
98 result.solarTransit = extractEvent( dayEvents, 1 );
99 result.solarMidnightAfter = extractEvent( dayEvents, 2 );
100
101 result.sunrise = extractEvent( dayEvents, 3 );
102 result.sunset = extractEvent( dayEvents, 4 );
103
104 result.civilDawn = extractEvent( dayEvents, 5 );
105 result.civilDusk = extractEvent( dayEvents, 6 );
106
107 result.nauticalDawn = extractEvent( dayEvents, 7 );
108 result.nauticalDusk = extractEvent( dayEvents, 8 );
109
110 result.astronomicalDawn = extractEvent( dayEvents, 9 );
111 result.astronomicalDusk = extractEvent( dayEvents, 10 );
112
113 return result;
114}
115
116QString QgsSunPositionCalculator::extractSpaErrorMessage( int errorCode )
117{
118 QStringList errors;
119
120 if ( errorCode & _FREESPA_DEU_OOR )
121 errors << u"ΔUT1 out of range"_s;
122 if ( errorCode & _FREESPA_LON_OOR )
123 errors << u"Longitude out of range"_s;
124 if ( errorCode & _FREESPA_LAT_OOR )
125 errors << u"Latitude out of range"_s;
126 if ( errorCode & _FREESPA_ELE_OOR )
127 errors << u"Elevation out of range"_s;
128 if ( errorCode & _FREESPA_PRE_OOR )
129 errors << u"Pressure out of range"_s;
130 if ( errorCode & _FREESPA_TEM_OOR )
131 errors << u"Temperature out of range"_s;
132 if ( errorCode & _FREESPA_DIP_OOR )
133 errors << u"Geometric dip out of range"_s;
134 if ( errorCode & _FREESPA_GMTIMEF )
135 errors << u"Time conversion error"_s;
136
137 if ( errors.isEmpty() )
138 {
139 return u"Unknown calculation error code: %1"_s.arg( errorCode );
140 }
141
142 return errors.join( ", "_L1 );
143}
Represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
Handles coordinate transforms between two coordinate systems.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
Custom exception class when argument are invalid.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
static QgsSunPositionResult calculate(const QgsPointXY &point, const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context, const QDateTime &dateTime, double elevationMeters=0.0, double pressure=1013.25, double temperature=15.0)
Calculates the solar position and events for a given point and time.
Contains the results of a solar position calculation.
QDateTime astronomicalDawn
The datetime of astronomical dawn, when the geometric center of the sun is 18 degrees below the horiz...
QDateTime astronomicalDusk
The datetime of astronomical dusk, when the geometric center of the sun is 18 degrees below the horiz...
QDateTime civilDawn
The datetime of civil dawn, when the geometric center of the sun is 6 degrees below the horizon in th...
QDateTime nauticalDawn
The datetime of nautical dawn, when the geometric center of the sun is 12 degrees below the horizon i...
double azimuth
Azimuth angle in degrees clockwise from North.
QDateTime solarTransit
The datetime of solar transit (solar noon) when the sun reaches its highest elevation (in UTC).
QDateTime solarMidnightAfter
The datetime of the solar midnight following the calculation time (in UTC).
QDateTime sunrise
The datetime of sunrise, defined as the moment the upper edge of the sun's disk becomes visible above...
QDateTime sunset
The datetime of sunset, defined as the moment the upper edge of the sun's disk disappears below the h...
QDateTime nauticalDusk
The datetime of nautical dusk, when the geometric center of the sun is 12 degrees below the horizon i...
QDateTime solarMidnightBefore
The datetime of the solar midnight preceding the calculation time (in UTC).
QDateTime civilDusk
The datetime of civil dusk, when the geometric center of the sun is 6 degrees below the horizon in th...
double apparentElevation
Apparent topocentric elevation angle in degrees (corrected for atmospheric refraction).