• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 9545309360

16 Jun 2024 08:38PM UTC coverage: 12.262% (-0.01%) from 12.274%
9545309360

push

github

web-flow
Translate stellarium-remotecontrol.pot in gl

100% translated source file: 'stellarium-remotecontrol.pot'
on 'gl'.

14429 of 117670 relevant lines covered (12.26%)

18515.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/core/SolarEclipseComputer.cpp
1
/*
2
 * Stellarium
3
 * Copyright (C) 2022 Worachate Boonplod
4
 * Copyright (C) 2022 Georg Zotti
5
 * Copyright (C) 2024 Ruslan Kabatsayev
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19
*/
20

21
#include "SolarEclipseComputer.hpp"
22
#include "StelLocaleMgr.hpp"
23
#include "StelModuleMgr.hpp"
24
#include "SolarSystem.hpp"
25
#include "StelUtils.hpp"
26
#include "StelCore.hpp"
27
#include "sidereal_time.h"
28

29
#include <utility>
30
#include <QPainter>
31

32
namespace
33
{
34

35
double sqr(const double x) { return x*x; }
×
36

37
static const QColor hybridEclipseColor ("#800080");
38
static const QColor totalEclipseColor  ("#ff0000");
39
static const QColor annularEclipseColor("#0000ff");
40
static const QColor eclipseLimitsColor ("#00ff00");
41

42
QString toKMLColorString(const QColor& c)
×
43
{
44
        return QString("%1%2%3%4").arg(c.alpha(),2,16,QChar('0'))
×
45
                                  .arg(c.blue (),2,16,QChar('0'))
×
46
                                  .arg(c.green(),2,16,QChar('0'))
×
47
                                  .arg(c.red  (),2,16,QChar('0'));
×
48
}
49

50
void drawContinuousEquirectGeoLine(QPainter& painter, QPointF pointA, QPointF pointB)
×
51
{
52
        using namespace std;
53
        const auto lineLengthDeg = hypot(pointB.x() - pointA.x(), pointB.y() - pointA.y());
×
54
        // For short enough lines go the simple way
55
        if(lineLengthDeg < 2)
×
56
        {
57
                painter.drawLine(pointA, pointB);
×
58
                return;
×
59
        }
60

61
        // Order them west to east
62
        if(pointA.x() > pointB.x())
×
63
                swap(pointA, pointB);
×
64

65
        Vec3d dirA;
×
66
        StelUtils::spheToRect(M_PI/180 * pointA.x(), M_PI/180 * pointA.y(), dirA);
×
67

68
        Vec3d dirB;
×
69
        StelUtils::spheToRect(M_PI/180 * pointB.x(), M_PI/180 * pointB.y(), dirB);
×
70

71
        const auto cosAngleBetweenDirs = dot(dirA, dirB);
×
72
        const auto angleMax = acos(cosAngleBetweenDirs);
×
73

74
        // Create an orthonormal pair of vectors that will define a plane in which we'll
75
        // rotate from one of the vectors towards the second one, thus creating the
76
        // shortest line on a unit sphere between the previous and the current directions.
77
        const auto firstDir = dirA;
×
78
        const auto secondDir = normalize(dirB - cosAngleBetweenDirs*firstDir);
×
79

80
        auto prevPoint = firstDir;
×
81
        // Keep the step no greater than 2°
82
        const double numPoints = max(3., ceil(lineLengthDeg / 2.));
×
83
        for(double n = 1; n < numPoints; ++n)
×
84
        {
85
                const auto alpha = n / (numPoints-1) * angleMax;
×
86
                const auto currPoint = cos(alpha)*firstDir + sin(alpha)*secondDir;
×
87

88
                double lon1, lat1;
89
                StelUtils::rectToSphe(&lon1, &lat1, prevPoint);
×
90

91
                double lon2, lat2;
92
                StelUtils::rectToSphe(&lon2, &lat2, currPoint);
×
93

94
                // If current point happens to have wrapped around 180°, bring it back to
95
                // the eastern side (the check relies on the ordering of the endpoints).
96
                if(pointA.x() > 0 && lon2 < 0)
×
97
                        lon2 += 2*M_PI;
×
98

99
                painter.drawLine(180/M_PI * QPointF(lon1,lat1), 180/M_PI * QPointF(lon2,lat2));
×
100
                prevPoint = currPoint;
×
101
        }
102
}
103

104
void drawGeoLinesForEquirectMap(QPainter& painter, const std::vector<QPointF>& points)
×
105
{
106
        if(points.empty()) return;
×
107

108
        using namespace std;
109

110
        Vec3d prevDir;
×
111
        StelUtils::spheToRect(M_PI/180 * points[0].x(), M_PI/180 * points[0].y(), prevDir);
×
112
        for(unsigned n = 1; n < points.size(); ++n)
×
113
        {
114
                const auto currLon = M_PI/180 * points[n].x();
×
115
                const auto currLat = M_PI/180 * points[n].y();
×
116
                Vec3d currDir;
×
117
                StelUtils::spheToRect(currLon, currLat, currDir);
×
118

119
                const auto cosAngleBetweenDirs = dot(prevDir, currDir);
×
120
                // Create an orthonormal pair of vectors that will define a plane in which we'll
121
                // rotate from one of the vectors towards the second one, thus creating the
122
                // shortest line on a unit sphere between the previous and the current directions.
123
                const auto firstDir = prevDir;
×
124
                const auto secondDir = normalize(currDir - cosAngleBetweenDirs*firstDir);
×
125
                // The parametric equation for the connecting line is:
126
                //  P(alpha) = cos(alpha)*firstDir+sin(alpha)*secondDir.
127
                // Here we assume alpha>0 (otherwise we'll go the longer route over the sphere).
128
                //
129
                // Now we need to find out if this line crosses the 180° meridian. This happens
130
                // if there exists an alpha such that P(alpha).y==0 && P(alpha).x<0.
131
                // These are the solutions of this equation for alpha.
132
                const auto alpha1 = atan2(firstDir[1], -secondDir[1]);
×
133
                const auto alpha2 = atan2(-firstDir[1], secondDir[1]);
×
134
                const bool firstSolutionBad  = alpha1 < 0 || cos(alpha1) < cosAngleBetweenDirs;
×
135
                const bool secondSolutionBad = alpha2 < 0 || cos(alpha2) < cosAngleBetweenDirs;
×
136
                // If the line doesn't cross 180°, we are not splitting it
137
                if(firstSolutionBad && secondSolutionBad)
×
138
                {
139
                        drawContinuousEquirectGeoLine(painter, points[n-1], points[n]);
×
140
                        prevDir = currDir;
×
141
                        continue;
×
142
                }
143

144
                const auto alpha = firstSolutionBad ? alpha2 : alpha1;
×
145
                const auto P = cos(alpha)*firstDir + sin(alpha)*secondDir;
×
146
                // Ignore the crossing of 0°
147
                if(P[0] > 0)
×
148
                {
149
                        drawContinuousEquirectGeoLine(painter, points[n-1], points[n]);
×
150
                        prevDir = currDir;
×
151
                        continue;
×
152
                }
153

154
                // So, we've found the crossing. Let's split our line by the crossing point.
155
                double crossLon, crossLat;
156
                StelUtils::rectToSphe(&crossLon, &crossLat, P);
×
157
                crossLon *= 180/M_PI;
×
158
                crossLat *= 180/M_PI;
×
159
                const bool sameSign = (crossLon < 0 && points[n-1].x() < 0) || (crossLon >= 0 && points[n-1].x() >= 0);
×
160
                drawContinuousEquirectGeoLine(painter, points[n-1], QPointF(sameSign ? crossLon : -crossLon, crossLat));
×
161
                drawContinuousEquirectGeoLine(painter, points[n], QPointF(sameSign ? -crossLon : crossLon, crossLat));
×
162

163
                prevDir = currDir;
×
164
        }
165
}
166

167
double zetaFromQ(const double cosQ, const double sinQ, const double tf, const EclipseBesselParameters& bp)
×
168
{
169
        // Reference: Explanatory Supplement to the Astronomical Ephemeris
170
        // and the American Ephemeris and Nautical Almanac, 3rd Edition (2013)
171

172
        const double x = bp.elems.x, y = bp.elems.y, d = bp.elems.d,
×
173
                     mudot = bp.mudot, bdot = bp.bdot, cdot = bp.cdot, ddot = bp.ddot;
×
174
        const double cosd = cos(d);
×
175
        const double adot = -bp.ldot-mudot*x*tf*cosd + y*ddot*tf;
×
176

177
        // From equation (11.81), restoring the missing dots over a,b,c in the book (cf. eq. (11.78)).
178
        return (-adot + bdot * cosQ - cdot * sinQ) / ((1 + sqr(tf)) * (ddot * cosQ - mudot * cosd * sinQ));
×
179
}
180

181
struct ShadowLimitPoints
182
{
183
        EclipseBesselParameters bp;
184
        double JD;
185
        struct Point
186
        {
187
                double Q;
188
                double zeta;
189
        };
190
        QVarLengthArray<Point, 8> values;
191

192
        ShadowLimitPoints(const EclipseBesselParameters& bp, const double JD)
×
193
                : bp(bp)
×
194
                , JD(JD)
×
195
        {
196
        }
×
197
};
198

199
ShadowLimitPoints getShadowLimitQs(StelCore*const core, const double JD, const double e2, const bool penumbra)
×
200
{
201
        /*
202
         * Reference: Explanatory Supplement to the Astronomical Ephemeris and the American Ephemeris and Nautical Almanac,
203
         * 3rd Edition (2013).
204
         *
205
         * This function computes Q values for shadow limit points. The equation being solved here is obtained from the
206
         * main identity (11.56):
207
         *
208
         *                                            xi^2+eta1^2+zeta1^2=1,
209
         *
210
         * and (11.60):
211
         *
212
         *                                  zeta=rho2*[zeta1*cos(d1-d2)-eta1*sin(d1-d2)].
213
         *
214
         * Solve the latter equation for zeta1 and substitute the result into the former equation.  Then multiply both
215
         * sides of the result by rho1^2*rho2^2*cos(d1-d2)^2, and after a rearrangement you'll get:
216
         *
217
         *   zeta^2*rho1^2 + 2*zeta*eta*sin(d1-d2)*rho1*rho2 +
218
         *                              + eta^2*rho2^2 - cos(d1-d2)^2*rho1^2*rho2^2 + xi^2*cos(d1-d2)^2*rho1^2*rho2^2 = 0.
219
         *
220
         * Now substitute xi and eta with (eq. (11.82))
221
         *
222
         *                                              xi = x - L*sin(Q),
223
         *                                             eta = x - L*cos(Q),
224
         *
225
         * where (eq. (11.65))
226
         *
227
         *                                             L = l - zeta*tan(f).
228
         *
229
         * The result will be a bit more complicated:
230
         *
231
         *  zeta^2*rho1^2 - cos(d1-d2)^2*rho1^2*rho2^2 + 2*zeta*sin(d1-d2)*rho1*rho2*(y - cos(Q)*(l - zeta*tan(f))) +
232
         *    + rho2^2*(y - cos(Q)*(l - zeta*tan(f)))^2 + cos(d1-d2)^2*rho1^2*rho2^2*(x - sin(Q)*(l - zeta*tan(f)))^2 = 0.
233
         *
234
         * Now replace zeta by the expression obtainable from (11.78),
235
         *
236
         *                                              -adot + bdot*cos(Q) - cdot*sin(Q)
237
         *                               zeta = -------------------------------------------------,
238
         *                                      (1 + tan(f)^2)*(ddot*cos(Q) - mudot*cos(d)*sin(Q)
239
         *
240
         * yielding, after multiplying both sides of the result by (1+tan(f))^2, the final equation:
241
         *
242
         * (-adot + bdot*cos(Q) - cdot*sin(Q))^2*(rho1^2 + 2*cos(Q)*sin(d1 - d2)*rho1*rho2*tan(f) +
243
         *  + (cos(Q)^2 + cos(d1 - d2)^2*sin(Q)^2*rho1^2)*rho2^2*tan(f)^2) +
244
         *  + 2*(-adot + bdot*cos(Q) - cdot*sin(Q))*rho2*(sin(d1 - d2)*(y - cos(Q)*l)*rho1 +
245
         *    + cos(Q)*(y - cos(Q)*l)*rho2*tan(f) + cos(d1 - d2)^2*sin(Q)*(x - sin(Q)*l)*rho1^2*rho2*tan(f)) *
246
         *       * (1 + tan(f)^2)*(cos(Q)*ddot - cos(d)*sin(Q)*mudot) +
247
         *    + ((y - cos(Q)*l)^2 + cos(d1 - d2)^2*(-1 + x - sin(Q)*l)*(1 + x - sin(Q)*l)*rho1^2)*rho2^2 *
248
         *       * (1 + tan(f)^2)^2*(cos(Q)*ddot - cos(d)*sin(Q)*mudot)^2 = 0.
249
         *
250
         * As we are using the Newton's method to solve it, we need to separate the different powers of sin(Q)^n*cos(Q)^m
251
         * to be able to find a derivative. This will then yield the final result implemented in the code below.
252
         */
253

254
        core->setJD(JD);
×
255
        core->update(0);
×
256
        const auto bp = calcBesselParameters(penumbra);
×
257
        const double x = bp.elems.x, y = bp.elems.y, d = bp.elems.d, tf1 = bp.elems.tf1,
×
258
                     bdot = bp.bdot, cdot = bp.cdot, ddot = bp.ddot, mudot = bp.mudot,
×
259
                     tf2 = bp.elems.tf2, L1 = bp.elems.L1, L2 = bp.elems.L2;
×
260
        const double tf = penumbra ? tf1 : tf2;
×
261
        const double L = penumbra ? L1 : L2;
×
262
        const double adot = -bp.ldot-mudot*x*tf*std::cos(d) + y*ddot*tf;
×
263

264
        using namespace std;
265
        const double sind = sin(d);
×
266
        const double cosd = cos(d);
×
267
        const double rho1 = sqrt(1-e2*sqr(cosd));
×
268
        const double rho2 = sqrt(1-e2*sqr(sind));
×
269
        const double sdd = e2*sind*cosd/(rho1*rho2);   // sin(d1-d2)
×
270
        const double cdd = sqrt(1-sqr(sdd));           // cos(d1-d2)
×
271

272
        // Some convenience variables to shorten the expressions below
273
        const double tfSp1 = 1 + sqr(tf);
×
274
        const double tfSp12 = sqr(tfSp1);
×
275
        const double x2 = x*x;
×
276
        const double y2 = y*y;
×
277
        const double adot2 = sqr(adot);
×
278
        const double bdot2 = sqr(bdot);
×
279
        const double cdot2 = sqr(cdot);
×
280
        const double rho12 = sqr(rho1);
×
281
        const double rho22 = sqr(rho2);
×
282
        const double cdd2 = sqr(cdd);
×
283

284
        // Coefficients of the LHS of the equation we are going to solve.
285

286
        //  * Constant term
287
        const double cC0S0 = adot2 * rho12;
×
288

289
        //  * coefficient for cos(Q)
290
        const double cC1S0 = 2*adot*rho1*(-(bdot*rho1) + rho2*sdd*(adot*tf - ddot*tfSp1*y));
×
291
        //  * coefficient for cos(Q)^2
292
        const double cC2S0 = bdot2*rho12 + 2*bdot*rho1*rho2*sdd*(-2*adot*tf + ddot*tfSp1*y) +
×
293
                             rho2*(2*adot*ddot*tfSp1*(L*rho1*sdd - rho2*tf*y) + adot2*rho2*sqr(tf) +
×
294
                                   cdd2*rho12*rho2*tfSp12*sqr(ddot)*(-1 + x2) +
×
295
                                   rho2*tfSp12*sqr(ddot)*y2);
×
296
        //  * coefficient for cos(Q)^3
297
        const double cC3S0 = -2*rho2*(-(bdot*tf) + ddot*L*tfSp1)*(bdot*rho1*sdd - adot*rho2*tf +
×
298
                                                                  ddot*rho2*tfSp1*y);
×
299
        //  * coefficient for cos(Q)^4
300
        const double cC4S0 = rho22*sqr(bdot*tf - ddot*L*tfSp1);
×
301

302
        //  * coefficient for sin(Q)
303
        const double cC0S1 = 2*adot*rho1*(cdot*rho1 + cosd*mudot*rho2*sdd*tfSp1*y);
×
304
        //  * coefficient for sin(Q)^2
305
        const double cC0S2 = cdot2*rho12 + 2*cdot*cosd*mudot*rho1*rho2*sdd*tfSp1*y +
×
306
                             rho22*(cdd2*rho12*(adot*tf + cosd*mudot*tfSp1*(-1 + x)) *
×
307
                                    (adot*tf + cosd*mudot*tfSp1*(1 + x)) +
×
308
                                    tfSp12*sqr(cosd)*sqr(mudot)*y2);
×
309
        //  * coefficient for sin(Q)^3
310
        const double cC0S3 = -2*cdd2*rho12*rho22*(-(cdot*tf) + cosd*L*mudot*tfSp1) *
×
311
                             (adot*tf + cosd*mudot*tfSp1*x);
×
312
        //  * coefficient for sin(Q)^4
313
        const double cC0S4 = cdd2*rho12*rho22*sqr(cdot*tf - cosd*L*mudot*tfSp1);
×
314

315
        //  * coefficient for cos(Q)*sin(Q)
316
        const double cC1S1 = -2*bdot*rho1*(cdot*rho1 + cosd*mudot*rho2*sdd*tfSp1*y) -
×
317
                             2*rho2*(ddot*tfSp1*y*(cdot*rho1*sdd + cosd*mudot*rho2*tfSp1*y) +
×
318
                                     adot*(-2*cdot*rho1*sdd*tf + cosd*mudot*tfSp1*(L*rho1*sdd - rho2*tf*y)) +
×
319
                                     cdd2*ddot*rho12*rho2*tfSp1*(adot*tf*x + cosd*mudot*tfSp1*(-1 + x2)));
×
320

321
        //  * coefficient for cos(Q)^2*sin(Q)^2
322
        const double cC2S2 = rho22*(-2*cdot*cosd*L*mudot*tf*tfSp1 + tfSp12*sqr(cosd)*sqr(L)*sqr(mudot) +
×
323
                                    cdot2*sqr(tf) + cdd2*rho12*sqr(bdot*tf - ddot*L*tfSp1));
×
324

325
        //  * coefficient for cos(Q)*sin(Q)^2
326
        const double cC1S2 = 2*rho2*(cdot2*rho1*sdd*tf + adot*cdd2*rho12*rho2*tf*(-(bdot*tf) + ddot*L*tfSp1) +
×
327
                                     cdot*tfSp1*(-(rho1*(cosd*L*mudot*sdd + cdd2*ddot*rho1*rho2*tf*x)) +
×
328
                                                 cosd*mudot*rho2*tf*y) + cosd*mudot*rho2*tfSp1*
×
329
                                     (cdd2*rho12*(-(bdot*tf) + 2*ddot*L*tfSp1)*x - cosd*L*mudot*tfSp1*y));
×
330
        //  * coefficient for cos(Q)^2*sin(Q)
331
        const double cC2S1 = 2*rho2*(tfSp1*(-(adot*cosd*L*mudot*rho2*tf) + bdot*cdd2*ddot*rho12*rho2*tf*x +
×
332
                                            ddot*L*rho2*tfSp1*(-(cdd2*ddot*rho12*x) + 2*cosd*mudot*y) +
×
333
                                            bdot*cosd*mudot*(L*rho1*sdd - rho2*tf*y)) +
×
334
                                     cdot*(tf*(-2*bdot*rho1*sdd + adot*rho2*tf) +
×
335
                                           ddot*tfSp1*(L*rho1*sdd - rho2*tf*y)));
×
336

337
        //  * coefficient for cos(Q)^3*sin(Q)
338
        const double cC3S1 = -2*rho22*(-(bdot*tf) + ddot*L*tfSp1)*(-(cdot*tf) + cosd*L*mudot*tfSp1);
×
339
        //  * coefficient for cos(Q)*sin(Q)^3
340
        const double cC1S3 = -2*cdd2*rho12*rho22*(-(bdot*tf) + ddot*L*tfSp1)*(-(cdot*tf) + cosd*L*mudot*tfSp1);
×
341

342
        const double lhsScale = max({abs(cC0S0),
×
343
                                     abs(cC1S0),
×
344
                                     abs(cC2S0),
×
345
                                     abs(cC3S0),
×
346
                                     abs(cC4S0),
×
347
                                     abs(cC0S1),
×
348
                                     abs(cC0S2),
×
349
                                     abs(cC0S3),
×
350
                                     abs(cC0S4),
×
351
                                     abs(cC1S1),
×
352
                                     abs(cC2S2),
×
353
                                     abs(cC1S2),
×
354
                                     abs(cC2S1),
×
355
                                     abs(cC3S1),
×
356
                                     abs(cC1S3)});
×
357

358
        // LHS(Q) of the equation and its derivative
359
        const auto lhsAndDerivative = [=](const double Q) -> std::pair<double,double>
×
360
        {
361
                const auto sinQ = std::sin(Q);
×
362
                const auto cosQ = std::cos(Q);
×
363
                const auto sinQ_2 = sqr(sinQ);
×
364
                const auto cosQ_2 = sqr(cosQ);
×
365
                const auto sinQ_3 = sinQ_2 * sinQ;
×
366
                const auto cosQ_3 = cosQ_2 * cosQ;
×
367
                const auto sinQ_4 = sqr(sinQ_2);
×
368
                const auto cosQ_4 = sqr(cosQ_2);
×
369
                const auto lhs = cC0S0 +
×
370
                                 cC1S0*cosQ + cC2S0*cosQ_2 + cC3S0*cosQ_3 + cC4S0*cosQ_4 +
×
371
                                 cC0S1*sinQ + cC0S2*sinQ_2 + cC0S3*sinQ_3 + cC0S4*sinQ_4 +
×
372
                                 cC1S1*cosQ*sinQ + cC1S2*cosQ*sinQ_2 + cC1S3*cosQ*sinQ_3 +
×
373
                                 cC2S1*cosQ_2*sinQ + cC2S2*cosQ_2*sinQ_2 +
×
374
                                 cC3S1*cosQ_3*sinQ;
×
375
                const auto lhsPrime =
×
376
                    -cC1S0*sinQ -2*cC2S0*cosQ*sinQ - 3*cC3S0*cosQ_2*sinQ - 4*cC4S0*cosQ_3*sinQ +
×
377
                     cC0S1*cosQ + 2*cC0S2*sinQ*cosQ + 3*cC0S3*sinQ_2*cosQ + 4*cC0S4*sinQ_3*cosQ +
×
378
                     cC1S1*(cosQ_2-sinQ_2) + cC1S2*(2*cosQ_2*sinQ-sinQ_3) + cC1S3*(3*cosQ_2*sinQ_2-sinQ_4) +
×
379
                     cC2S1*(cosQ_3-2*cosQ*sinQ_2) + cC2S2*(2*cosQ_3*sinQ-2*cosQ*sinQ_3) +
×
380
                     cC3S1*(cosQ_4-3*cosQ_2*sinQ_2);
×
381
                return std::make_pair(lhs, lhsPrime);
×
382
        };
×
383

384
        // Find roots using Newton's method. The LHS of the equation is divided
385
        // by sin((Q-rootQ)/2) for all known roots to find subsequent roots.
386
        ShadowLimitPoints points(bp, JD);
×
387
        double Q = 0;
×
388
        bool rootFound = false;
×
389
        do
×
390
        {
391
                rootFound = false;
×
392
                bool finalIteration = false;
×
393
                // Max retry count is chosen with the idea that we'll scan the (periodic) domain [-pi,pi] with 4
394
                // extrema approximately evenly distributed over the domain, aiming to hit each slope, with an extra
395
                // sample just to make sure that we've not missed anything.
396
                // If we don't do these retries, we may lose roots when Newton's method gets stuck at an extremum that
397
                // doesn't reach zero.
398
                const double maxRetries = 9;
×
399
                for(double retryCount = 0; retryCount < maxRetries && !rootFound; ++retryCount)
×
400
                {
401
                        Q = 2*M_PI*retryCount/maxRetries;
×
402

403
                        for(int n = 0; n < 50; ++n)
×
404
                        {
405
                                const auto [lhs, lhsPrime] = lhsAndDerivative(Q);
×
406

407
                                // Cancel the known roots to avoid finding them instead of the remaining ones
408
                                auto newLHSPrime = lhsPrime;
×
409
                                auto newLHS = lhs;
×
410
                                for(const auto root : points.values)
×
411
                                {
412
                                        const auto rootQ = root.Q;
×
413
                                        // We need a 2pi-periodic root-canceling function,
414
                                        // so take a sine of half the difference.
415
                                        const auto sinDiff = sin((Q - rootQ)/2);
×
416
                                        const auto cosDiff = cos((Q - rootQ)/2);
×
417

418
                                        newLHS /= sinDiff;
×
419
                                        newLHSPrime = (newLHSPrime - 0.5*cosDiff*newLHS)/sinDiff;
×
420
                                }
421
                                if(!isfinite(newLHS) || !isfinite(newLHSPrime))
×
422
                                {
423
                                        qWarning().nospace() << "Hit infinite/NaN values: LHS = " << newLHS
×
424
                                                             << ", LHS'(Q) = " << newLHSPrime << " at Q = " << Q;
×
425
                                        break;
×
426
                                }
427

428
                                if(abs(newLHS) < 1e-10*lhsScale)
×
429
                                        finalIteration = true;
×
430

431
                                const auto deltaQ = newLHS / newLHSPrime;
×
432
                                if(newLHSPrime==0 || abs(deltaQ) > 1000)
×
433
                                {
434
                                        // We are shooting too far away, convergence may be too slow.
435
                                        // Let's try perturbing Q and retrying.
436
                                        Q += 0.01;
×
437
                                        finalIteration = false;
×
438
                                        continue;
×
439
                                }
440
                                Q -= deltaQ;
×
441
                                Q = StelUtils::fmodpos(Q, 2*M_PI);
×
442

443
                                if(finalIteration)
×
444
                                {
445
                                        points.values.push_back({Q, zetaFromQ(cos(Q),sin(Q),tf,bp)});
×
446
                                        // Set a new initial value, but try to avoid setting it to 0 if the current
447
                                        // root is close to it (all values here are arbitrary in other respects).
448
                                        Q = abs(Q) > 0.5 ? 0 : -M_PI/2;
×
449

450
                                        rootFound = true;
×
451
                                        break;
×
452
                                }
453
                        }
454
                }
455
        }
456
        while(rootFound);
457

458
        std::sort(points.values.begin(), points.values.end(), [](auto& p1, auto& p2){ return p1.Q < p2.Q; });
×
459

460
        return points;
×
461
}
×
462

463
SolarEclipseComputer::GeoPoint computeTimePoint(const EclipseBesselParameters& bp, const double earthOblateness,
×
464
                                                const double Q, const double zeta, const bool penumbra)
465
{
466
        // Reference: Explanatory Supplement to the Astronomical Ephemeris
467
        // and the American Ephemeris and Nautical Almanac, 3rd Edition (2013)
468

469
        const double f = earthOblateness;
×
470
        const double e2 = f*(2.-f);
×
471
        const double ff = 1./(1.-f);
×
472
        const double x = bp.elems.x, y = bp.elems.y, d = bp.elems.d, tf1 = bp.elems.tf1,
×
473
                     tf2 = bp.elems.tf2, L1 = bp.elems.L1, L2 = bp.elems.L2, mu = bp.elems.mu;
×
474

475
        using namespace std;
476
        const double sind = sin(d);
×
477
        const double cosd = cos(d);
×
478
        const double rho1 = sqrt(1-e2*sqr(cosd));
×
479
        const double rho2 = sqrt(1-e2*sqr(sind));
×
480
        const double sd1 = sind/rho1;
×
481
        const double cd1 = sqrt(1-e2)*cosd/rho1;
×
482
        const double sdd = e2*sind*cosd/(rho1*rho2);   // sin(d1-d2)
×
483
        const double cdd = sqrt(1-sqr(sdd));           // cos(d1-d2)
×
484
        const double tf = penumbra ? tf1 : tf2;
×
485
        const double L = penumbra ? L1 : L2;
×
486

487
        SolarEclipseComputer::GeoPoint coordinates{99., 0.};
×
488

489
        const double sinQ = sin(Q);
×
490
        const double cosQ = cos(Q);
×
491

492
        const double Lz = L - zeta*tf; // radius of shadow at distance zeta from the fundamental plane
×
493
        const double xi  = x - Lz * sinQ;
×
494
        const double eta = y - Lz * cosQ;
×
495

496
        const double eta1 = eta / rho1;
×
497
        const double zeta1 = (zeta / rho2 + eta1 * sdd) / cdd;
×
498

499
        if(abs(eta) > 1.0001 || abs(xi) > 1.0001 || abs(zeta) > 1.0001)
×
500
        {
501
                qWarning().noquote() << QString("Unnormalized vector (xi,eta,zeta) found: (%1, %2, %3); Q = %4°")
×
502
                                            .arg(xi,0,'g',17).arg(eta,0,'g',17).arg(zeta,0,'g',17)
×
503
                                            .arg(StelUtils::fmodpos(Q*180/M_PI, 360), 0,'g',17);
×
504
        }
505

506
        const double b = -eta1*sd1+zeta1*cd1;
×
507
        const double theta = atan2(xi,b)*M_180_PI;
×
508
        double lngDeg = theta-mu;
×
509
        lngDeg = StelUtils::fmodpos(lngDeg, 360.);
×
510
        if (lngDeg > 180.) lngDeg -= 360.;
×
511
        const double sfn1 = eta1*cd1+zeta1*sd1;
×
512
        const double cfn1 = sqrt(1.-sfn1*sfn1);
×
513
        const double tanLat = ff*sfn1/cfn1;
×
514
        coordinates.latitude = atan(tanLat)*M_180_PI;
×
515
        coordinates.longitude = lngDeg;
×
516

517
        return coordinates;
×
518
}
519

520
}
521

522
EclipseBesselElements calcSolarEclipseBessel()
×
523
{
524
        // Besselian elements
525
        // Source: Explanatory Supplement to the Astronomical Ephemeris
526
        // and the American Ephemeris and Nautical Almanac (1961)
527

528
        EclipseBesselElements out;
529

530
        StelCore* core = StelApp::getInstance().getCore();
×
531
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
532
        const bool topocentricWereEnabled = core->getUseTopocentricCoordinates();
×
533
        if(topocentricWereEnabled)
×
534
        {
535
                core->setUseTopocentricCoordinates(false);
×
536
                core->update(0);
×
537
        }
538

539
        double raMoon, deMoon, raSun, deSun;
540
        StelUtils::rectToSphe(&raSun, &deSun, ssystem->getSun()->getEquinoxEquatorialPos(core));
×
541
        StelUtils::rectToSphe(&raMoon, &deMoon, ssystem->getMoon()->getEquinoxEquatorialPos(core));
×
542

543
        double sdistanceAu = ssystem->getSun()->getEquinoxEquatorialPos(core).norm();
×
544
        const double earthRadius = ssystem->getEarth()->getEquatorialRadius()*AU;
×
545
        // Moon's distance in Earth's radius
546
        double mdistanceER = ssystem->getMoon()->getEquinoxEquatorialPos(core).norm() * AU / earthRadius;
×
547
        // Greenwich Apparent Sidereal Time
548
        const double gast = get_apparent_sidereal_time(core->getJD(), core->getJDE());
×
549

550
        // Avoid bug for special cases happen around Vernal Equinox
551
        double raDiff = StelUtils::fmodpos(raMoon-raSun, 2.*M_PI);
×
552
        if (raDiff>M_PI) raDiff-=2.*M_PI;
×
553

554
        constexpr double SunEarth = 109.12278;
×
555
        // ratio of Sun-Earth radius : 109.12278 = 696000/6378.1366
556
        // Earth's equatorial radius = 6378.1366
557
        // Source: IERS Conventions (2003)
558
        // https://www.iers.org/IERS/EN/Publications/TechnicalNotes/tn32.html
559

560
        // NASA's solar eclipse predictions use solar radius of 696,000 km
561
        // calculated from arctan of IAU 1976 solar radius (959.63 arcsec at 1 au).
562

563
        const double rss = sdistanceAu * 23454.7925; // from 1 AU/Earth's radius : 149597870.8/6378.1366
×
564
        const double b = mdistanceER / rss;
×
565
        const double a = raSun - ((b * cos(deMoon) * raDiff) / ((1 - b) * cos(deSun)));
×
566
        out.d = deSun - (b * (deMoon - deSun) / (1 - b));
×
567
        out.x = cos(deMoon) * sin((raMoon - a));
×
568
        out.x *= mdistanceER;
×
569
        out.y = cos(out.d) * sin(deMoon);
×
570
        out.y -= cos(deMoon) * sin(out.d) * cos((raMoon - a));
×
571
        out.y *= mdistanceER;
×
572
        double z = sin(deMoon) * sin(out.d);
×
573
        z += cos(deMoon) * cos(out.d) * cos((raMoon - a));
×
574
        z *= mdistanceER;
×
575
        const double k = 0.2725076;
×
576
        const double s = 0.272281;
×
577
        // Ratio of Moon/Earth's radius 0.2725076 is recommended by IAU for both k & s
578
        // s = 0.272281 is used by Fred Espenak/NASA for total eclipse to eliminate extreme cases
579
        // when the Moon's apparent diameter is very close to the Sun but cannot completely cover it.
580
        // we will use two values (same with NASA), because durations seem to agree with NASA.
581
        // Source: Solar Eclipse Predictions and the Mean Lunar Radius
582
        // http://eclipsewise.com/solar/SEhelp/SEradius.html
583

584
        // Parameters of the shadow cone
585
        const double f1 = asin((SunEarth + k) / (rss * (1. - b)));
×
586
        out.tf1 = tan(f1);
×
587
        const double f2 = asin((SunEarth - s) / (rss * (1. - b)));
×
588
        out.tf2 = tan(f2);
×
589
        out.L1 = z * out.tf1 + (k / cos(f1));
×
590
        out.L2 = z * out.tf2 - (s / cos(f2));
×
591
        out.mu = gast - a * M_180_PI;
×
592
        out.mu = StelUtils::fmodpos(out.mu, 360.);
×
593

594
        if(topocentricWereEnabled)
×
595
        {
596
                core->setUseTopocentricCoordinates(true);
×
597
                core->update(0);
×
598
        }
599

600
        return out;
×
601
}
602

603
EclipseBesselParameters calcBesselParameters(bool penumbra)
×
604
{
605
        EclipseBesselParameters out;
606
        StelCore* core = StelApp::getInstance().getCore();
×
607
        double JD = core->getJD();
×
608
        core->setJD(JD - 5./1440.);
×
609
        core->update(0);
×
610
        const auto ep1 = calcSolarEclipseBessel();
×
611
        const double x1 = ep1.x, y1 = ep1.y, d1 = ep1.d, mu1 = ep1.mu, L11 = ep1.L1, L21 = ep1.L2;
×
612
        core->setJD(JD + 5./1440.);
×
613
        core->update(0);
×
614
        const auto ep2 = calcSolarEclipseBessel();
×
615
        const double x2 = ep2.x, y2 = ep2.y, d2 = ep2.d, mu2 = ep2.mu, L12 = ep2.L1, L22 = ep2.L2;
×
616

617
        out.xdot = (x2-x1)*6.;
×
618
        out.ydot = (y2-y1)*6.;
×
619
        out.ddot = (d2-d1)*6.*M_PI_180;
×
620
        out.mudot = (mu2-mu1);
×
621
        if (out.mudot<0.) out.mudot += 360.; // make sure it is positive in case mu2 < mu1
×
622
        out.mudot = out.mudot*6.* M_PI_180;
×
623
        core->setJD(JD);
×
624
        core->update(0);
×
625
        out.elems = calcSolarEclipseBessel();
×
626
        const auto& ep = out.elems;
×
627
        double tf,L;
628
        if (penumbra)
×
629
        {
630
                L = ep.L1;
×
631
                tf = ep.tf1;
×
632
                out.ldot = (L12-L11)*6.;
×
633
        }
634
        else
635
        {
636
                L = ep.L2;
×
637
                tf = ep.tf2;
×
638
                out.ldot = (L22-L21)*6.;
×
639
        }
640
        out.etadot = out.mudot * ep.x * std::sin(ep.d);
×
641
        out.bdot = -(out.ydot-out.etadot);
×
642
        out.cdot = out.xdot + out.mudot * ep.y * std::sin(ep.d) + out.mudot * L * tf * std::cos(ep.d);
×
643

644
        return out;
×
645
}
646

647
void calcSolarEclipseData(double JD, double &dRatio, double &latDeg, double &lngDeg, double &altitude,
×
648
                          double &pathWidth, double &duration, double &magnitude)
649
{
650
        StelCore* core = StelApp::getInstance().getCore();
×
651
        const double currentJD = core->getJDOfLastJDUpdate(); // save current JD
×
652
        const qint64 millis = core->getMilliSecondsOfLastJDUpdate(); // keep time in sync
×
653
        const bool saveTopocentric = core->getUseTopocentricCoordinates();
×
654

655
        core->setUseTopocentricCoordinates(false);
×
656
        core->setJD(JD);
×
657
        core->update(0);
×
658

659
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
660
        static const double earthRadius = ssystem->getEarth()->getEquatorialRadius()*AU;
×
661
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
662
        static const double e2 = f*(2.-f);
×
663
        static const double ff = 1./(1.-f);
×
664

665
        const auto ep = calcSolarEclipseBessel();
×
666
        const double rho1 = sqrt(1. - e2 * cos(ep.d) * cos(ep.d));
×
667
        const double eta1 = ep.y / rho1;
×
668
        const double sd1 = sin(ep.d) / rho1;
×
669
        const double cd1 = sqrt(1. - e2) * cos(ep.d) / rho1;
×
670
        const double rho2 = sqrt(1.- e2 * sin(ep.d) * sin(ep.d));
×
671
        const double sd1d2 = e2*sin(ep.d)*cos(ep.d) / (rho1*rho2);
×
672
        const double cd1d2 = sqrt(1. - sd1d2 * sd1d2);
×
673
        const double p = 1. - ep.x * ep.x - eta1 * eta1;
×
674

675
        if (p > 0.) // Central eclipse : Moon's shadow axis is touching Earth
×
676
        {
677
                const double zeta1 = sqrt(p);
×
678
                const double zeta = rho2 * (zeta1 * cd1d2 - eta1 * sd1d2);
×
679
                const double L2a = ep.L2 - zeta * ep.tf2;
×
680
                const double b = -ep.y * sin(ep.d) + zeta * cos(ep.d);
×
681
                const double theta = atan2(ep.x, b) * M_180_PI;
×
682
                lngDeg = theta - ep.mu;
×
683
                lngDeg = StelUtils::fmodpos(lngDeg, 360.);
×
684
                if (lngDeg > 180.) lngDeg -= 360.;
×
685
                const double sfn1 = eta1 * cd1 + zeta1 * sd1;
×
686
                const double cfn1 = sqrt(1. - sfn1 * sfn1);
×
687
                latDeg = atan(ff * sfn1 / cfn1) / M_PI_180;
×
688
                const double L1a = ep.L1 - zeta * ep.tf1;
×
689
                magnitude = L1a / (L1a + L2a);
×
690
                dRatio = 1.+(magnitude-1.)*2.;
×
691

692
                core->setJD(JD - 5./1440.);
×
693
                core->update(0);
×
694

695
                const auto ep1 = calcSolarEclipseBessel();
×
696

697
                core->setJD(JD + 5./1440.);
×
698
                core->update(0);
×
699

700
                const auto ep2 = calcSolarEclipseBessel();
×
701

702
                // Hourly rate
703
                const double xdot = (ep2.x - ep1.x) * 6.;
×
704
                const double ydot = (ep2.y - ep1.y) * 6.;
×
705
                const double ddot = (ep2.d - ep1.d) * 6.;
×
706
                double mudot = (ep2.mu - ep1.mu);
×
707
                if (mudot<0.) mudot += 360.; // make sure it is positive in case ep2.mu < ep1.mu
×
708
                mudot = mudot * 6.* M_PI_180;
×
709

710
                // Duration of central eclipse in minutes
711
                const double etadot = mudot * ep.x * sin(ep.d) - ddot * zeta;
×
712
                const double xidot = mudot * (-ep.y * sin(ep.d) + zeta * cos(ep.d));
×
713
                const double n = sqrt((xdot - xidot) * (xdot - xidot) + (ydot - etadot) * (ydot - etadot));
×
714
                duration = L2a*120./n; // positive = annular eclipse, negative = total eclipse
×
715

716
                // Approximate altitude
717
                altitude = asin(cfn1*cos(ep.d)*cos(theta * M_PI_180)+sfn1*sin(ep.d)) / M_PI_180;
×
718

719
                // Path width in kilometers
720
                // Explanatory Supplement to the Astronomical Almanac
721
                // Seidelmann, P. Kenneth, ed. (1992). University Science Books. ISBN 978-0-935702-68-2
722
                // https://archive.org/details/131123ExplanatorySupplementAstronomicalAlmanac
723
                // Path width for central solar eclipses which only part of umbra/antumbra touches Earth
724
                // are too wide and could give a false impression, annular eclipse of 2003 May 31, for example.
725
                // We have to check this in the next step by calculating northern/southern limit of umbra/antumbra.
726
                // Don't show the path width if there is no northern limit or southern limit.
727
                // We will eventually have to calculate both limits, if we want to draw eclipse path on world map.
728
                const double p1 = zeta * zeta;
×
729
                const double p2 = ep.x * (xdot - xidot) / n;
×
730
                const double p3 = eta1 * (ydot - etadot) / n;
×
731
                const double p4 = (p2 + p3) * (p2 + p3);
×
732
                pathWidth = abs(earthRadius*2.*L2a/sqrt(p1+p4));
×
733
        }
734
        else  // Partial eclipse or non-central eclipse
735
        {
736
                const double yy1 = ep.y / rho1;
×
737
                double xi = ep.x / sqrt(ep.x * ep.x + yy1 * yy1);
×
738
                const double eta1 = yy1 / sqrt(ep.x * ep.x + yy1 * yy1);
×
739
                const double sd1 = sin(ep.d) / rho1;
×
740
                const double cd1 = sqrt(1.- e2) * cos(ep.d) / rho1;
×
741
                const double rho2 = sqrt(1.- e2 * sin(ep.d) * sin(ep.d));
×
742
                const double sd1d2 = e2 * sin(ep.d) * cos(ep.d) / (rho1 * rho2);
×
743
                double zeta = rho2 * (-(eta1) * sd1d2);
×
744
                const double b = -eta1 * sd1;
×
745
                double theta = atan2(xi, b);
×
746
                const double sfn1 = eta1*cd1;
×
747
                const double cfn1 = sqrt(1.- sfn1 * sfn1);
×
748
                double lat = ff * sfn1 / cfn1;
×
749
                lat = atan(lat);
×
750
                const double L1 = ep.L1 - zeta * ep.tf1;
×
751
                const double L2 = ep.L2 - zeta * ep.tf2;
×
752
                const double c = 1. / sqrt(1.- e2 * sin(lat) * sin(lat));
×
753
                const double s = (1.- e2) * c;
×
754
                const double rs = s * sin(lat);
×
755
                const double rc = c * cos(lat);
×
756
                xi = rc * sin(theta);
×
757
                const double eta = rs * cos(ep.d) - rc * sin(ep.d) * cos(theta);
×
758
                const double u = ep.x - xi;
×
759
                const double v = ep.y - eta;
×
760
                magnitude = (L1 - sqrt(u * u + v * v)) / (L1 + L2);
×
761
                dRatio = 1.+ (magnitude - 1.)* 2.;
×
762
                theta = theta / M_PI_180;
×
763
                lngDeg = theta - ep.mu;
×
764
                lngDeg = StelUtils::fmodpos(lngDeg, 360.);
×
765
                if (lngDeg > 180.) lngDeg -= 360.;
×
766
                latDeg = lat / M_PI_180;
×
767
                duration = 0.;
×
768
                pathWidth = 0.;
×
769
        }
770
        core->setJD(currentJD);
×
771
        core->setMilliSecondsOfLastJDUpdate(millis); // restore millis.
×
772
        core->setUseTopocentricCoordinates(saveTopocentric);
×
773
        core->update(0);
×
774
}
×
775

776
SolarEclipseComputer::SolarEclipseComputer(StelCore* core, StelLocaleMgr* localeMgr)
×
777
        : core(core)
×
778
        , localeMgr(localeMgr)
×
779
{
780
}
×
781

782
bool SolarEclipseComputer::bothPenumbraLimitsPresent(const double JDMid) const
×
783
{
784
        core->setJD(JDMid);
×
785
        core->update(0);
×
786
        const auto ep = calcSolarEclipseBessel();
×
787
        // FIXME: can the rise-set line exist at greatest eclipse but not exist at some other phase?
788
        // It seems that ellipticity of the Earth could result in this, because greatest eclipse is
789
        // defined relative to Earth's center rather than to its rim.
790
        const auto coordinates = getRiseSetLineCoordinates(true, ep.x, ep.y, ep.d, ep.L1, ep.mu);
×
791
        return coordinates.latitude > 90;
×
792
}
793

794
auto SolarEclipseComputer::generateEclipseMap(const double JDMid) const -> EclipseMapData
×
795
{
796
        const bool savedTopocentric = core->getUseTopocentricCoordinates();
×
797
        const double currentJD = core->getJD(); // save current JD
×
798
        core->setUseTopocentricCoordinates(false);
×
799
        core->update(0);
×
800

801
        EclipseMapData data;
×
802

803
        bool partialEclipse = false;
×
804
        bool nonCentralEclipse = false;
×
805
        double dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude;
806
        core->setJD(JDMid);
×
807
        core->update(0);
×
808
        const auto ep = calcSolarEclipseBessel();
×
809
        const double x = ep.x, y = ep.y;
×
810
        double gamma = std::sqrt(x*x+y*y);
×
811
        // Type of eclipse
812
        if (abs(gamma) > 0.9972 && abs(gamma) < (1.5433 + ep.L2))
×
813
        {
814
                if (abs(gamma) < 0.9972 + abs(ep.L2))
×
815
                {
816
                        partialEclipse = false;
×
817
                        nonCentralEclipse = true; // non-central total/annular eclipse
×
818
                }
819
                else
820
                        partialEclipse = true;
×
821
        }
822
        const double JDP1 = getJDofContact(JDMid,true,true,true,true);
×
823
        const double JDP4 = getJDofContact(JDMid,false,true,true,true);
×
824

825
        double JDP2 = 0., JDP3 = 0.;
×
826
        GeoPoint coordinates;
827
        // Check northern/southern limits of penumbra at greatest eclipse
828
        const bool bothPenumbralLimits = bothPenumbraLimitsPresent(JDMid);
×
829

830
        if (bothPenumbralLimits)
×
831
        {
832
                JDP2 = getJDofContact(JDMid,true,true,true,false);
×
833
                JDP3 = getJDofContact(JDMid,false,true,true,false);
×
834
        }
835

836
        // Generate GE
837
        data.greatestEclipse.JD = JDMid;
×
838
        calcSolarEclipseData(JDMid,dRatio,data.greatestEclipse.latitude,data.greatestEclipse.longitude,altitude,pathWidth,duration,magnitude);
×
839

840
        // Generate P1
841
        data.firstContactWithEarth.JD = JDP1;
×
842
        calcSolarEclipseData(JDP1,dRatio,data.firstContactWithEarth.latitude,data.firstContactWithEarth.longitude,altitude,pathWidth,duration,magnitude);
×
843

844
        // Generate P4
845
        data.lastContactWithEarth.JD = JDP4;
×
846
        calcSolarEclipseData(JDP4,dRatio,data.lastContactWithEarth.latitude,data.lastContactWithEarth.longitude,altitude,pathWidth,duration,magnitude);
×
847

848
        // Northern/southern limits of penumbra
849
        computeNSLimitsOfShadow(JDP1, JDP4, true, data.penumbraLimits);
×
850

851
        // Eclipse begins/ends at sunrise/sunset curve
852
        if (bothPenumbralLimits)
×
853
        {
854
                double latP2, lngP2, latP3, lngP3;
855
                bool first = true;
×
856
                for (int j = 0; j < 2; j++)
×
857
                {
858
                        if (j != 0) first = false;
×
859
                        // P1 to P2 curve
860
                        core->setJD(JDP2);
×
861
                        core->update(0);
×
862
                        auto ep = calcSolarEclipseBessel();
×
863
                        coordinates = getContactCoordinates(ep.x, ep.y, ep.d, ep.mu);
×
864
                        latP2 = coordinates.latitude;
×
865
                        lngP2 = coordinates.longitude;
×
866
                        auto& limit = data.riseSetLimits[j].emplace<EclipseMapData::TwoLimits>();
×
867

868
                        limit.p12curve.emplace_back(data.firstContactWithEarth.longitude,
×
869
                                                    data.firstContactWithEarth.latitude);
×
870
                        double JD = JDP1;
×
871
                        int i = 0;
×
872
                        while (JD < JDP2)
×
873
                        {
874
                                JD = JDP1 + i/1440.0;
×
875
                                core->setJD(JD);
×
876
                                core->update(0);
×
877
                                ep = calcSolarEclipseBessel();
×
878
                                coordinates = getRiseSetLineCoordinates(first, ep.x, ep.y, ep.d, ep.L1, ep.mu);
×
879
                                if (coordinates.latitude <= 90.)
×
880
                                        limit.p12curve.emplace_back(coordinates.longitude, coordinates.latitude);
×
881
                                i++;
×
882
                        }
883
                        limit.p12curve.emplace_back(lngP2, latP2);
×
884

885
                        // P3 to P4 curve
886
                        core->setJD(JDP3);
×
887
                        core->update(0);
×
888
                        ep = calcSolarEclipseBessel();
×
889
                        coordinates = getContactCoordinates(ep.x, ep.y, ep.d, ep.mu);
×
890
                        latP3 = coordinates.latitude;
×
891
                        lngP3 = coordinates.longitude;
×
892
                        limit.p34curve.emplace_back(lngP3, latP3);
×
893
                        JD = JDP3;
×
894
                        i = 0;
×
895
                        while (JD < JDP4)
×
896
                        {
897
                                JD = JDP3 + i/1440.0;
×
898
                                core->setJD(JD);
×
899
                                core->update(0);
×
900
                                ep = calcSolarEclipseBessel();
×
901
                                coordinates = getRiseSetLineCoordinates(first, ep.x, ep.y, ep.d, ep.L1, ep.mu);
×
902
                                if (coordinates.latitude <= 90.)
×
903
                                        limit.p34curve.emplace_back(coordinates.longitude, coordinates.latitude);
×
904
                                i++;
×
905
                        }
906
                        limit.p34curve.emplace_back(data.lastContactWithEarth.longitude,
×
907
                                                    data.lastContactWithEarth.latitude);
×
908
                }
909
        }
910
        else
911
        {
912
                // Only northern or southern limit exists
913
                // Draw the curve between P1 to P4
914
                bool first = true;
×
915
                for (int j = 0; j < 2; j++)
×
916
                {
917
                        if (j != 0) first = false;
×
918
                        auto& limit = data.riseSetLimits[j].emplace<EclipseMapData::SingleLimit>();
×
919
                        limit.curve.emplace_back(data.firstContactWithEarth.longitude,
×
920
                                                 data.firstContactWithEarth.latitude);
×
921
                        double JD = JDP1;
×
922
                        int i = 0;
×
923
                        while (JD < JDP4)
×
924
                        {
925
                                JD = JDP1 + i/1440.0;
×
926
                                core->setJD(JD);
×
927
                                core->update(0);
×
928
                                const auto ep = calcSolarEclipseBessel();
×
929
                                coordinates = getRiseSetLineCoordinates(first, ep.x, ep.y, ep.d, ep.L1, ep.mu);
×
930
                                if (coordinates.latitude <= 90.)
×
931
                                        limit.curve.emplace_back(coordinates.longitude, coordinates.latitude);
×
932
                                i++;
×
933
                        }
934
                        limit.curve.emplace_back(data.lastContactWithEarth.longitude,
×
935
                                                 data.lastContactWithEarth.latitude);
×
936
                }
937
        }
938

939
        // Curve of maximum eclipse at sunrise/sunset
940
        // There are two parts of the curve
941
        bool first = true;
×
942
        for (int j = 0; j < 2; j++)
×
943
        {
944
                if (j!= 0) first = false;
×
945
                if(bothPenumbralLimits)
×
946
                {
947
                        // The curve corresponding to P1-P2 time interval
948
                        data.maxEclipseAtRiseSet.emplace_back();
×
949
                        auto* curve = &data.maxEclipseAtRiseSet.back();
×
950
                        auto JDmin = JDP1;
×
951
                        auto JDmax = JDP2;
×
952
                        int numPoints = 5;
×
953
                        bool goodPointFound = false;
×
954
                        do
955
                        {
956
                                curve->clear();
×
957
                                numPoints = 2*numPoints+1;
×
958
                                const auto step = (JDmax-JDmin)/numPoints;
×
959
                                // We use an extended interval of n to include min and max values of JD. The internal
960
                                // values of JD are chosen in such a way that after increasing numPoints at the next
961
                                // iteration we'd check the points between the ones we checked in the previous
962
                                // iteration, thus more efficiently searching for good points, avoiding rechecking
963
                                // the same JD values.
964
                                for(int n = -1; n < numPoints+1; ++n)
×
965
                                {
966
                                        const auto JD = std::clamp(JDmin + step*(n+0.5), JDmin, JDmax);
×
967
                                        const auto coordinates = getMaximumEclipseAtRiseSet(first,JD);
×
968
                                        curve->emplace_back(JD, coordinates.longitude, coordinates.latitude);
×
969
                                        if (abs(coordinates.latitude) <= 90.)
×
970
                                                goodPointFound = true;
×
971
                                        else if(goodPointFound)
×
972
                                                break; // we've obtained a bad point after a good one, can refine now
×
973
                                }
974
                        }
975
                        while(!goodPointFound && numPoints < 500);
×
976

977
                        // We can't refine the curve if we have no usable points, so clear it (don't
978
                        // remove because we need it to match first and second branches).
979
                        if(!goodPointFound) curve->clear();
×
980

981
                        // The curve corresponding to P3-P4 time interval
982
                        data.maxEclipseAtRiseSet.emplace_back();
×
983
                        curve = &data.maxEclipseAtRiseSet.back();
×
984
                        JDmin = JDP3;
×
985
                        JDmax = JDP4;
×
986
                        numPoints = 5;
×
987
                        goodPointFound = false;
×
988
                        do
989
                        {
990
                                curve->clear();
×
991
                                numPoints = 2*numPoints+1;
×
992
                                const auto step = (JDmax-JDmin)/numPoints;
×
993
                                // We use an extended interval of n to include min and max values of JD. The internal
994
                                // values of JD are chosen in such a way that after increasing numPoints at the next
995
                                // iteration we'd check the points between the ones we checked in the previous
996
                                // iteration, thus more efficiently searching for good points, avoiding rechecking
997
                                // the same JD values.
998
                                for(int n = -1; n < numPoints+1; ++n)
×
999
                                {
1000
                                        const auto JD = std::clamp(JDmin + step*(n+0.5), JDmin, JDmax);
×
1001
                                        const auto coordinates = getMaximumEclipseAtRiseSet(first,JD);
×
1002
                                        curve->emplace_back(JD, coordinates.longitude, coordinates.latitude);
×
1003
                                        if (abs(coordinates.latitude) <= 90.)
×
1004
                                                goodPointFound = true;
×
1005
                                        else if(goodPointFound)
×
1006
                                                break; // we've obtained a bad point after a good one, can refine now
×
1007
                                }
1008
                        }
1009
                        while(!goodPointFound && numPoints < 500);
×
1010

1011
                        // We can't refine the curve if we have no usable points, so clear it (don't
1012
                        // remove because we need it to match first and second branches).
1013
                        if(!goodPointFound) curve->clear();
×
1014
                }
1015
                else
1016
                {
1017
                        // The curve corresponding to P1-P4 time interval
1018
                        data.maxEclipseAtRiseSet.emplace_back();
×
1019
                        auto*const curve = &data.maxEclipseAtRiseSet.back();
×
1020
                        const auto JDmin = JDP1;
×
1021
                        const auto JDmax = JDP4;
×
1022
                        int numPoints = 5;
×
1023
                        bool goodPointFound = false;
×
1024
                        do
1025
                        {
1026
                                curve->clear();
×
1027
                                numPoints = 2*numPoints+1;
×
1028
                                const auto step = (JDmax-JDmin)/numPoints;
×
1029
                                // We use an extended interval of n to include min and max values of JD. The internal
1030
                                // values of JD are chosen in such a way that after increasing numPoints at the next
1031
                                // iteration we'd check the points between the ones we checked in the previous
1032
                                // iteration, thus more efficiently searching for good points, avoiding rechecking
1033
                                // the same JD values.
1034
                                for(int n = -1; n < numPoints+1; ++n)
×
1035
                                {
1036
                                        const auto JD = std::clamp(JDmin + step*(n+0.5), JDmin, JDmax);
×
1037
                                        const auto coordinates = getMaximumEclipseAtRiseSet(first,JD);
×
1038
                                        curve->emplace_back(JD, coordinates.longitude, coordinates.latitude);
×
1039
                                        if (abs(coordinates.latitude) <= 90.)
×
1040
                                                goodPointFound = true;
×
1041
                                        else if(goodPointFound)
×
1042
                                                break; // we've obtained a bad point after a good one, can refine now
×
1043
                                }
1044
                        }
1045
                        while(!goodPointFound && numPoints < 500);
×
1046

1047
                        // We can't refine the curve if we have no usable points, so clear it (don't
1048
                        // remove because we need it to match first and second branches).
1049
                        if(!goodPointFound) curve->clear();
×
1050
                }
1051
        }
1052
        // Refine at the beginnings and the ends of the lines so as to find the precise endpoints
1053
        for(unsigned c = 0; c < data.maxEclipseAtRiseSet.size(); ++c)
×
1054
        {
1055
                auto& points = data.maxEclipseAtRiseSet[c];
×
1056
                const bool first = c < data.maxEclipseAtRiseSet.size() / 2;
×
1057

1058
                // 1. Beginning of the line
1059
                const auto firstValidIt = std::find_if(points.begin(), points.end(),
×
1060
                                                       [](const auto& p){ return p.latitude <= 90; });
×
1061
                if (firstValidIt == points.end()) continue;
×
1062
                const int firstValidPos = firstValidIt - points.begin();
×
1063
                if (firstValidPos > 0)
×
1064
                {
1065
                        double lastInvalidTime = points[firstValidPos - 1].JD;
×
1066
                        double firstValidTime = points[firstValidPos].JD;
×
1067
                        // Bisect between these times. The sufficient number of iterations was found empirically.
1068
                        for (int n = 0; n < 15; ++n)
×
1069
                        {
1070
                                const auto currTime = (lastInvalidTime + firstValidTime) / 2;
×
1071
                                const auto coords = getMaximumEclipseAtRiseSet(first, currTime);
×
1072
                                if (coords.latitude > 90)
×
1073
                                {
1074
                                        lastInvalidTime = currTime;
×
1075
                                }
1076
                                else
1077
                                {
1078
                                        firstValidTime = currTime;
×
1079
                                        points.emplace_front(currTime, coords.longitude, coords.latitude);
×
1080
                                }
1081
                        }
1082
                }
1083

1084
                // 2. End of the line
1085
                const auto lastValidIt = std::find_if(points.rbegin(), points.rend(),
×
1086
                                                      [](const auto& p){ return p.latitude <= 90; });
×
1087
                if (lastValidIt == points.rend()) continue;
×
1088
                const int lastValidPos = points.size() - 1 - (lastValidIt - points.rbegin());
×
1089
                if (lastValidPos + 1u < points.size())
×
1090
                {
1091
                        double firstInvalidTime = points[lastValidPos + 1].JD;
×
1092
                        double lastValidTime = points[lastValidPos].JD;
×
1093
                        // Bisect between these times. The sufficient number of iterations was found empirically.
1094
                        for (int n = 0; n < 15; ++n)
×
1095
                        {
1096
                                const auto currTime = (firstInvalidTime + lastValidTime) / 2;
×
1097
                                const auto coords = getMaximumEclipseAtRiseSet(first, currTime);
×
1098
                                if (coords.latitude > 90)
×
1099
                                {
1100
                                        firstInvalidTime = currTime;
×
1101
                                }
1102
                                else
1103
                                {
1104
                                        lastValidTime = currTime;
×
1105
                                        points.emplace_back(currTime, coords.longitude, coords.latitude);
×
1106
                                }
1107
                        }
1108
                }
1109

1110
                // 3. Cleanup: remove invalid points, sort by time increase
1111
                points.erase(std::remove_if(points.begin(), points.end(), [](const auto& p) { return p.latitude > 90; }),
×
1112
                             points.end());
×
1113
                std::sort(points.begin(), points.end(), [](const auto& a, const auto& b) { return a.JD < b.JD; });
×
1114

1115
                // Refine too long internal segments
1116
                bool newPointsInserted;
1117
                do
×
1118
                {
1119
                        newPointsInserted = false;
×
1120
                        const unsigned origNumPoints = points.size();
×
1121
                        for(unsigned n = 1; n < origNumPoints; ++n)
×
1122
                        {
1123
                                const auto prevLat = points[n-1].latitude;
×
1124
                                const auto currLat = points[n].latitude;
×
1125
                                const auto prevLon = points[n-1].longitude;
×
1126
                                const auto currLon = points[n].longitude;
×
1127
                                auto lonDiff = currLon - prevLon;
×
1128
                                while(lonDiff >  180) lonDiff -= 360;
×
1129
                                while(lonDiff < -180) lonDiff += 360;
×
1130
                                constexpr double admissibleStepDeg = 5;
×
1131
                                constexpr double admissibleStepDegSqr = admissibleStepDeg*admissibleStepDeg;
×
1132
                                // We want to sample more densely near the pole, where the rise/set curve may have
1133
                                // more features, so using unscaled longitude as one of the coordinates is on purpose.
1134
                                if(Vec2d(prevLat - currLat, lonDiff).normSquared() < admissibleStepDegSqr)
×
1135
                                        continue;
×
1136

1137
                                const auto JD = (points[n-1].JD + points[n].JD) / 2;
×
1138
                                const auto coordinates = getMaximumEclipseAtRiseSet(first,JD);
×
1139
                                points.emplace_back(JD, coordinates.longitude, coordinates.latitude);
×
1140
                                newPointsInserted = true;
×
1141
                        }
1142
                        std::sort(points.begin(), points.end(), [](const auto& a, const auto& b) { return a.JD < b.JD; });
×
1143
                } while(newPointsInserted);
1144

1145
                // Connect the first and second branches of the lines
1146
                if(!first)
×
1147
                {
1148
                        const auto firstBranchIndex = c - data.maxEclipseAtRiseSet.size() / 2;
×
1149
                        auto& firstBranch  = data.maxEclipseAtRiseSet[firstBranchIndex];
×
1150
                        auto& secondBranch = data.maxEclipseAtRiseSet[c];
×
1151
                        if(firstBranch.empty() || secondBranch.empty())
×
1152
                                continue;
×
1153
                        // Connect the points that are closer to each other by time
1154
                        if(std::abs(secondBranch.back().JD - firstBranch.back().JD) < std::abs(secondBranch.front().JD - firstBranch.front().JD))
×
1155
                                secondBranch.emplace_back(firstBranch.back());
×
1156
                        else
1157
                                secondBranch.emplace_front(firstBranch.front());
×
1158
                }
1159
        }
1160

1161
        if (!partialEclipse)
×
1162
        {
1163
                double JDC1 = JDMid, JDC2 = JDMid;
×
1164
                const double JDU1 = getJDofContact(JDMid,true,false,true,true); // beginning of external (ant)umbral contact
×
1165
                const double JDU4 = getJDofContact(JDMid,false,false,true,true); // end of external (ant)umbral contact
×
1166
                calcSolarEclipseData(JDC1,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1167
                if (!nonCentralEclipse)
×
1168
                {
1169
                        // C1
1170
                        double JD = getJDofContact(JDMid,true,false,false,true);
×
1171
                        JD = int(JD)+(int((JD-int(JD))*86400.)-1)/86400.;
×
1172
                        calcSolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1173
                        int steps = 0;
×
1174
                        while (pathWidth<0.0001 && steps<20)
×
1175
                        {
1176
                                JD += .1/86400.;
×
1177
                                calcSolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1178
                                steps += 1;
×
1179
                        }
1180
                        JDC1 = JD;
×
1181
                        core->setJD(JDC1);
×
1182
                        core->update(0);
×
1183
                        auto ep = calcSolarEclipseBessel();
×
1184
                        coordinates = getContactCoordinates(ep.x, ep.y, ep.d, ep.mu);
×
1185
                        double latC1 = coordinates.latitude;
×
1186
                        double lngC1 = coordinates.longitude;
×
1187

1188
                        // Generate C1
1189
                        data.centralEclipseStart.JD = JDC1;
×
1190
                        data.centralEclipseStart.longitude = lngC1;
×
1191
                        data.centralEclipseStart.latitude = latC1;
×
1192

1193
                        // C2
1194
                        JD = getJDofContact(JDMid,false,false,false,true);
×
1195
                        JD = int(JD)+(int((JD-int(JD))*86400.)+1)/86400.;
×
1196
                        calcSolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1197
                        steps = 0;
×
1198
                        while (pathWidth<0.0001 && steps<20)
×
1199
                        {
1200
                                JD -= .1/86400.;
×
1201
                                calcSolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1202
                                steps += 1;
×
1203
                        }
1204
                        JDC2 = JD;
×
1205
                        core->setJD(JDC2);
×
1206
                        core->update(0);
×
1207
                        ep = calcSolarEclipseBessel();
×
1208
                        coordinates = getContactCoordinates(ep.x, ep.y, ep.d, ep.mu);
×
1209
                        double latC2 = coordinates.latitude;
×
1210
                        double lngC2 = coordinates.longitude;
×
1211

1212
                        // Generate C2
1213
                        data.centralEclipseEnd.JD = JDC2;
×
1214
                        data.centralEclipseEnd.longitude = lngC2;
×
1215
                        data.centralEclipseEnd.latitude = latC2;
×
1216

1217
                        // Center line
1218
                        JD = JDC1;
×
1219
                        int i = 0;
×
1220
                        double dRatioC1 = dRatio;
×
1221
                        calcSolarEclipseData(JDMid,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1222
                        double dRatioMid = dRatio;
×
1223
                        calcSolarEclipseData(JDC2,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1224
                        double dRatioC2 = dRatio;
×
1225
                        if (dRatioC1 >= 1. && dRatioMid >= 1. && dRatioC2 >= 1.)
×
1226
                                data.eclipseType = EclipseMapData::EclipseType::Total;
×
1227
                        else if (dRatioC1 < 1. && dRatioMid < 1. && dRatioC2 < 1.)
×
1228
                                data.eclipseType = EclipseMapData::EclipseType::Annular;
×
1229
                        else
1230
                                data.eclipseType = EclipseMapData::EclipseType::Hybrid;
×
1231
                        data.centerLine.emplace_back(lngC1, latC1);
×
1232
                        while (JD+(1./1440.) < JDC2)
×
1233
                        {
1234
                                JD = JDC1 + i/1440.; // generate every one minute
×
1235
                                calcSolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1236
                                data.centerLine.emplace_back(lngDeg, latDeg);
×
1237
                                i++;
×
1238
                        }
1239
                        data.centerLine.emplace_back(lngC2, latC2);
×
1240
                }
1241
                else
1242
                {
1243
                        JDC1 = JDMid;
×
1244
                        JDC2 = JDMid;
×
1245
                }
1246

1247
                // Umbra/antumbra outline
1248
                // we want to draw (ant)umbral shadow on world map at exact times like 09:00, 09:10, 09:20, ...
1249
                double beginJD = int(JDU1)+(10.*int(1440.*(JDU1-int(JDU1))/10.)+10.)/1440.;
×
1250
                double endJD = int(JDU4)+(10.*int(1440.*(JDU4-int(JDU4))/10.))/1440.;
×
1251
                double JD = beginJD;
×
1252
                int i = 0;
×
1253
                double lat0 = 0., lon0 = 0.;
×
1254
                while (JD < endJD)
×
1255
                {
1256
                        JD = beginJD + i/144.; // generate every 10 minutes
×
1257
                        core->setJD(JD);
×
1258
                        core->update(0);
×
1259
                        const auto ep = calcSolarEclipseBessel();
×
1260
                        calcSolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude);
×
1261
                        double angle = 0.;
×
1262
                        bool firstPoint = false;
×
1263
                        auto& outline = data.umbraOutlines.emplace_back();
×
1264
                        outline.JD = JD;
×
1265
                        if (dRatio>=1.)
×
1266
                                outline.eclipseType = EclipseMapData::EclipseType::Total;
×
1267
                        else
1268
                                outline.eclipseType = EclipseMapData::EclipseType::Annular;
×
1269
                        int pointNumber = 0;
×
1270
                        while (pointNumber < 60)
×
1271
                        {
1272
                                angle = pointNumber*M_PI*2./60.;
×
1273
                                coordinates = getShadowOutlineCoordinates(angle, ep.x, ep.y, ep.d, ep.L2, ep.tf2, ep.mu);
×
1274
                                if (coordinates.latitude <= 90.)
×
1275
                                {
1276
                                        outline.curve.emplace_back(coordinates.longitude, coordinates.latitude);
×
1277
                                        if (!firstPoint)
×
1278
                                        {
1279
                                                lat0 = coordinates.latitude;
×
1280
                                                lon0 = coordinates.longitude;
×
1281
                                                firstPoint = true;
×
1282
                                        }
1283
                                }
1284
                                pointNumber++;
×
1285
                        }
1286
                        outline.curve.emplace_back(lon0, lat0); // completing the circle
×
1287
                        i++;
×
1288
                }
1289

1290
                // Northern/southern limits of umbra
1291
                computeNSLimitsOfShadow(JDP1, JDP4, false, data.umbraLimits);
×
1292
        }
1293

1294
        core->setJD(currentJD);
×
1295
        core->setUseTopocentricCoordinates(savedTopocentric);
×
1296
        core->update(0);
×
1297

1298
        return data;
×
1299
}
×
1300

1301
void SolarEclipseComputer::computeNSLimitsOfShadow(const double JDP1, const double JDP4, const bool penumbra,
×
1302
                                                   std::vector<std::vector<EclipseMapData::GeoTimePoint>>& limits) const
1303
{
1304
        // Reference: Explanatory Supplement to the Astronomical Ephemeris
1305
        // and the American Ephemeris and Nautical Almanac, 3rd Edition (2013)
1306

1307
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
1308
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
1309
        static const double e2 = f*(2.-f);
×
1310

1311
        const int iMax = std::ceil((JDP4-JDP1)*1440);
×
1312
        std::vector<ShadowLimitPoints> solutionsPerTime;
×
1313
        solutionsPerTime.reserve(iMax);
×
1314

1315
        constexpr double minutesToDays = 1./(24*60);
×
1316
        constexpr double secondsToDays = 1./(24*3600);
×
1317

1318
        // First sample the sets of Q values over all the time of the eclipse.
1319
        for(int i = 0; i < iMax; ++i)
×
1320
        {
1321
                const double JD = JDP1 + i*minutesToDays;
×
1322
                solutionsPerTime.emplace_back(getShadowLimitQs(core, JD, e2, penumbra));
×
1323
        }
1324

1325
        // Check that each set of solutions has an even number of solutions.
1326
        // If it's odd, the set is broken so should be removed.
1327
        for(unsigned i = 0; i < solutionsPerTime.size(); )
×
1328
        {
1329
                if(solutionsPerTime[i].values.size() % 2)
×
1330
                {
1331
                        qWarning() << "Found an odd number of values of Q:" << solutionsPerTime[i].values.size();
×
1332
                        solutionsPerTime.erase(solutionsPerTime.begin()+i);
×
1333
                }
1334
                else
1335
                {
1336
                        ++i;
×
1337
                }
1338
        }
1339

1340
        // Search for time points where number of Q values changes and
1341
        // refine each case to get closer to the point of the jump.
1342
        for(unsigned i = 1; i < solutionsPerTime.size(); ++i)
×
1343
        {
1344
                const auto& solA = solutionsPerTime[i-1];
×
1345
                const auto& solB = solutionsPerTime[i];
×
1346
                if(std::abs(solA.JD - solB.JD) <= 0.001*secondsToDays)
×
1347
                {
1348
                        // Already fine enough.
1349
                        continue;
×
1350
                }
1351

1352
                if(solA.values.size() != solB.values.size())
×
1353
                {
1354
                        const auto midJD = (solA.JD + solB.JD)/2;
×
1355
                        solutionsPerTime.insert(solutionsPerTime.begin()+i,
×
1356
                                                getShadowLimitQs(core, midJD, e2, penumbra));
×
1357
                        if(solutionsPerTime[i].values.size() % 2)
×
1358
                        {
1359
                                qWarning() << "Found an odd number of values of Q while searching for "
×
1360
                                              "JD of change in number of solutions:"
×
1361
                                           << solutionsPerTime[i].values.size();
×
1362
                        }
1363
                        // Retry with the first of the new intervals at the next iteration
1364
                        --i;
×
1365
                }
1366
        }
1367

1368
        // Search for time points where sign of zeta switches and
1369
        // refine each case to get closer to the zero crossing.
1370
        for(unsigned i = 1; i < solutionsPerTime.size(); ++i)
×
1371
        {
1372
                const auto& solA = solutionsPerTime[i-1];
×
1373
                const auto& solB = solutionsPerTime[i];
×
1374
                if(solA.values.size() != solB.values.size())
×
1375
                {
1376
                        // Even if there's a sign change, we've already refined these
1377
                        // points, so it's not a problem that we skip this case here.
1378
                        continue;
×
1379
                }
1380

1381
                if(std::abs(solA.JD - solB.JD) <= 0.001*secondsToDays)
×
1382
                {
1383
                        // Already fine enough.
1384
                        continue;
×
1385
                }
1386

1387
                for(int n = 0; n < solA.values.size(); ++n)
×
1388
                {
1389
                        if(solA.values[n].zeta * solB.values[n].zeta < 0)
×
1390
                        {
1391
                                const auto midJD = (solA.JD + solB.JD)/2;
×
1392
                                solutionsPerTime.insert(solutionsPerTime.begin()+i,
×
1393
                                                        getShadowLimitQs(core, midJD, e2, penumbra));
×
1394
                                if(solutionsPerTime[i].values.size() % 2)
×
1395
                                {
1396
                                        qWarning() << "Found an odd number of values of Q while searching for "
×
1397
                                                      "JD of zeta sign change:"
×
1398
                                                   << solutionsPerTime[i].values.size();
×
1399
                                }
1400
                                // Retry with the first of the new intervals at the next iteration
1401
                                --i;
×
1402
                        }
1403
                }
1404
        }
1405

1406
        if(solutionsPerTime.empty()) return;
×
1407

1408
        // Now treat all runs of the same solution counts as simultaneous runs of multiple lines, where for each
1409
        // JD the point belonging to line n is the solution number n (the solutions are already sorted by Q).
1410

1411
        // 1. Compute the lines ignoring the sign of zeta
1412
        struct Point
1413
        {
1414
                GeoPoint geo;
1415
                double JD;
1416
                double zeta;
1417
                Point(const GeoPoint& geo, double JD, double zeta) : geo(geo), JD(JD), zeta(zeta) {}
×
1418
        };
1419
        std::vector<std::vector<Point>> lines;
×
1420
        lines.resize(solutionsPerTime[0].values.size());
×
1421
        for(unsigned i = 0, startN = 0; i < solutionsPerTime.size(); ++i)
×
1422
        {
1423
                const auto& sol = solutionsPerTime[i];
×
1424
                if(i > 0 && sol.values.size() != solutionsPerTime[i-1].values.size())
×
1425
                {
1426
                        startN = lines.size();
×
1427
                        lines.resize(lines.size() + sol.values.size());
×
1428
                }
1429
                for(unsigned n = 0; n < sol.values.size(); ++n)
×
1430
                {
1431
                        const double JD = sol.JD;
×
1432
                        const double Q = sol.values[n].Q;
×
1433
                        const double zeta = sol.values[n].zeta;
×
1434
                        const auto tp = computeTimePoint(sol.bp, f, Q, zeta, penumbra);
×
1435
                        lines[startN + n].emplace_back(tp, JD, zeta);
×
1436
                }
1437
        }
1438

1439
        // 2. Remove the points under the horizon (i.e. where zeta < 0)
1440
        for(unsigned n = 0; n < lines.size(); ++n)
×
1441
        {
1442
                auto& line = lines[n];
×
1443
                if(line.empty()) continue;
×
1444

1445
                const auto negZetaIt = std::find_if(line.begin(), line.end(), [](auto& p){ return p.zeta < 0; });
×
1446
                if(negZetaIt == line.end()) continue; // whole line is visible
×
1447

1448
                const auto nonNegZetaIt = std::find_if(line.begin(), line.end(),
×
1449
                                                       [](auto& p){ return p.zeta >= 0; });
×
1450
                if(nonNegZetaIt == line.end())
×
1451
                {
1452
                        // Whole line is under the horizon
1453
                        line.clear();
×
1454
                        continue;
×
1455
                }
1456

1457
                if(nonNegZetaIt == line.begin())
×
1458
                {
1459
                        // Line starts with non-negative zetas, then gets under the horizon.  Move the second
1460
                        // part of the line to a new line, skipping all leading points with negative zeta.
1461
                        const auto nextNonNegZetaIt = std::find_if(negZetaIt, line.end(),
×
1462
                                                                   [](auto& p){ return p.zeta >= 0; });
×
1463
                        if(nextNonNegZetaIt != line.end())
×
1464
                                lines.emplace_back(nextNonNegZetaIt, line.end());
×
1465
                        line.erase(negZetaIt, line.end());
×
1466
                }
1467
                else
1468
                {
1469
                        // Line starts with negative zetas and then there appear positive-zeta points.
1470
                        // Remove the negative-zeta head.
1471
                        const auto nextNonNegZetaIt = std::find_if(negZetaIt, line.end(),
×
1472
                                                                   [](auto& p){ return p.zeta >= 0; });
×
1473
                        line.erase(negZetaIt, nextNonNegZetaIt);
×
1474
                        if(!line.empty())
×
1475
                        {
1476
                                // The remaining points may still contain negative zetas,
1477
                                // so restart processing from the same line.
1478
                                --n;
×
1479
                        }
1480
                }
1481
        }
1482

1483
        // Finally, fill in the limits
1484
        limits.clear();
×
1485
        for(const auto& line : lines)
×
1486
        {
1487
                if(line.empty()) continue;
×
1488
                auto& limit = limits.emplace_back();
×
1489
                for(const auto& p : line)
×
1490
                {
1491
                        limit.emplace_back(p.JD, p.geo.longitude, p.geo.latitude);
×
1492
                }
1493
        }
1494
}
×
1495

1496
auto SolarEclipseComputer::getContactCoordinates(double x, double y, double d, double mu) const -> GeoPoint
×
1497
{
1498
        // Source: Explanatory Supplement to the Astronomical Ephemeris 
1499
        // and the American Ephemeris and Nautical Almanac (1961)
1500
        GeoPoint coordinates;
1501
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
1502
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
1503
        static const double e2 = f*(2.-f);
×
1504
        static const double ff = 1./(1.-f);
×
1505
        const double rho1 = std::sqrt(1.-e2*std::cos(d)*std::cos(d));
×
1506
        const double yy1 = y/rho1;
×
1507
        const double m1 = std::sqrt(x*x+yy1*yy1);
×
1508
        const double eta1 = yy1/m1;
×
1509
        const double sd1 = std::sin(d)/rho1;
×
1510
        const double cd1 = std::sqrt(1.-e2)*std::cos(d)/rho1;
×
1511
        const double theta = std::atan2(x/m1,-eta1*sd1)*M_180_PI;
×
1512
        double lngDeg = StelUtils::fmodpos(theta - mu + 180., 360.) - 180.;
×
1513
        double latDeg = ff*std::tan(std::asin(eta1*cd1));
×
1514
        coordinates.latitude = std::atan(latDeg)*M_180_PI;
×
1515
        coordinates.longitude = lngDeg;
×
1516
        return coordinates;
×
1517
}
1518

1519
auto SolarEclipseComputer::getRiseSetLineCoordinates(bool first, double x,double y,double d,double L,double mu) const -> GeoPoint
×
1520
{
1521
        // Reference for terminology and variable naming:
1522
        // Explanatory Supplement to the Astronomical Ephemeris and the
1523
        // American Ephemeris and Nautical Almanac, 3rd Edition (2013).
1524
        //
1525
        // The computation of the intersection is my own derivation, I haven't found it in the book.
1526
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
1527
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
1528
        static const double ff = 1./(1.-f);
×
1529

1530
        GeoPoint coordinates(99., 0.);
×
1531
        using namespace std;
1532
        const double sind = sin(d);
×
1533
        const double cosd = cos(d);
×
1534
        static const double e2 = f*(2.-f);
×
1535
        const double rho1 = sqrt(1-e2*sqr(cosd));
×
1536
        const double rho2 = sqrt(1-e2*sqr(sind));
×
1537
        const double sd1 = sind/rho1;
×
1538
        const double cd1 = sqrt(1-e2)*cosd/rho1;
×
1539
        const double sdd = e2*sind*cosd/(rho1*rho2);   // sin(d1-d2)
×
1540
        const double cdd = sqrt(1-sqr(sdd));           // cos(d1-d2)
×
1541

1542
        // Semi-minor axis of the elliptic cross section of the
1543
        // Earth in the fundamental plane (in Earth radii).
1544
        const double k = 1/sqrt(sqr(sind)+sqr(cosd)/(1-e2));
×
1545
        /*
1546
           We solve simultaneous equations: one for the ellipse of the Earth's border as
1547
           crossed by the fundamental plane, and the other is the circle of the shadow edge:
1548

1549
                                ⎧ xi^2+eta^2/k^2=1
1550
                                ⎨
1551
                                ⎩ (xi-x)^2+(eta-y)^2=L^2
1552

1553
            If we parametrize the solution for the Earth's border equation as
1554

1555
                                      xi=cos(t),
1556
                                      eta=k*sin(t),
1557

1558
            we'll get a single equation for the shadow border in terms of t:
1559

1560
                            (cos(t)-x)^2+(k*sin(t)-y)^2=L^2.
1561

1562
            This equation can be solved using Newton's method, which we'll now do.
1563
        */
1564
        const auto lhsAndDerivative = [=](const double t) -> std::pair<double,double>
×
1565
        {
1566
                const auto cost = cos(t);
×
1567
                const auto sint = sin(t);
×
1568
                const auto lhs = sqr(cost-x) + sqr(k*sint-y) - sqr(L);
×
1569
                const auto lhsPrime = 2*x*sint + 2*cost*((k*k-1)*sint - k*y);
×
1570
                return std::make_pair(lhs,lhsPrime);
×
1571
        };
×
1572

1573
        std::vector<double> ts;
×
1574
        double t = 0;
×
1575
        bool rootFound = false;
×
1576
        do
×
1577
        {
1578
                if(ts.size() == 2) break; // there can't be more than 2 roots, so don't spend time uselessly
×
1579

1580
                rootFound = false;
×
1581
                bool finalIteration = false;
×
1582
                for(int n = 0; n < 50; ++n)
×
1583
                {
1584
                        const auto [lhs, lhsPrime] = lhsAndDerivative(t);
×
1585

1586
                        // Cancel the known roots to avoid finding them instead of the remaining ones
1587
                        auto newLHSPrime = lhsPrime;
×
1588
                        auto newLHS = lhs;
×
1589
                        for(const auto rootT : ts)
×
1590
                        {
1591
                                // We need a 2pi-periodic root-canceling function,
1592
                                // so take a sine of half the difference.
1593
                                const auto sinDiff = sin((t - rootT)/2);
×
1594
                                const auto cosDiff = cos((t - rootT)/2);
×
1595

1596
                                newLHS /= sinDiff;
×
1597
                                newLHSPrime = (newLHSPrime - 0.5*cosDiff*newLHS)/sinDiff;
×
1598
                        }
1599

1600
                        if(abs(newLHS) < 1e-10)
×
1601
                                finalIteration = true;
×
1602

1603
                        const auto deltaT = newLHS / newLHSPrime;
×
1604
                        if(newLHSPrime==0 || abs(deltaT) > 1000)
×
1605
                        {
1606
                                // We are shooting too far away, convergence may be too slow.
1607
                                // Let's try perturbing t and retrying.
1608
                                t += 0.01;
×
1609
                                finalIteration = false;
×
1610
                                continue;
×
1611
                        }
1612
                        t -= deltaT;
×
1613
                        t = StelUtils::fmodpos(t, 2*M_PI);
×
1614

1615
                        if(finalIteration)
×
1616
                        {
1617
                                ts.emplace_back(t);
×
1618
                                // Set a new initial value, but try to avoid setting it to 0 if the current
1619
                                // root is close to it (all values here are arbitrary in other respects).
1620
                                t = abs(t) > 0.5 ? 0 : -M_PI/2;
×
1621

1622
                                rootFound = true;
×
1623
                                break;
×
1624
                        }
1625
                }
1626
        }
1627
        while(rootFound);
1628

1629
        if(ts.empty()) return coordinates;
×
1630

1631
        double xi, eta;
1632
        if(ts.size() == 1)
×
1633
        {
1634
                const auto t = ts[0];
×
1635
                xi = cos(t);
×
1636
                eta = k*sin(t);
×
1637
        }
1638
        else
1639
        {
1640
                // Whether a solution is "first" or "second" depends on which side from the
1641
                // (0,0)-(x,y) line it is. To find this out we'll use the z component of
1642
                // the vector product (x,y,0)×(xi,eta,0).
1643
                const auto sin_t0 = sin(ts[0]);
×
1644
                const auto cos_t0 = cos(ts[0]);
×
1645
                const auto sin_t1 = sin(ts[1]);
×
1646
                const auto cos_t1 = cos(ts[1]);
×
1647

1648
                const auto xi0 = cos_t0;
×
1649
                const auto xi1 = cos_t1;
×
1650
                const auto eta0 = k*sin_t0;
×
1651
                const auto eta1 = k*sin_t1;
×
1652

1653
                const auto vecProdZ0 = x * eta0 - y * xi0;
×
1654

1655
                const bool use0 = first ? vecProdZ0 < 0 : vecProdZ0 > 0;
×
1656

1657
                xi  = use0 ? xi0  : xi1;
×
1658
                eta = use0 ? eta0 : eta1;
×
1659
        }
1660

1661
        const double eta1 = eta / rho1;
×
1662
        const double zeta1 = (0 + eta1 * sdd) / cdd;
×
1663

1664
        const double b = -eta1*sd1+zeta1*cd1;
×
1665
        const double theta = atan2(xi,b)*M_180_PI;
×
1666
        double lngDeg = theta-mu;
×
1667
        lngDeg = StelUtils::fmodpos(lngDeg, 360.);
×
1668
        if (lngDeg > 180.) lngDeg -= 360.;
×
1669
        const double sfn1 = eta1*cd1+zeta1*sd1;
×
1670
        const double cfn1 = sqrt(1.-sfn1*sfn1);
×
1671
        const double tanLat = ff*sfn1/cfn1;
×
1672
        coordinates.latitude = atan(tanLat)*M_180_PI;
×
1673
        coordinates.longitude = lngDeg;
×
1674
        return coordinates;
×
1675
}
×
1676

1677
auto SolarEclipseComputer::getShadowOutlineCoordinates(double angle,double x,double y,double d,double L,double tf,double mu) const -> GeoPoint
×
1678
{
1679
        // Source: Explanatory Supplement to the Astronomical Ephemeris 
1680
        // and the American Ephemeris and Nautical Almanac (1961)
1681
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
1682
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
1683
        static const double e2 = f*(2.-f);
×
1684
        static const double ff = 1./(1.-f);
×
1685
        const double sinAngle=std::sin(angle);
×
1686
        const double cosAngle=std::cos(angle);
×
1687
        const double cosD = std::cos(d);
×
1688
        const double rho1 = std::sqrt(1.-e2*cosD*cosD);
×
1689
        const double sd1 = std::sin(d)/rho1;
×
1690
        const double cd1 = std::sqrt(1.-e2)*cosD/rho1;
×
1691

1692
        GeoPoint coordinates(99., 0.);
×
1693

1694
        double L1, xi, eta1, zeta1 = 0;
×
1695
        for(int n = 0; n < 3; ++n)
×
1696
        {
1697
                L1 = L-zeta1*tf;
×
1698
                xi = x-L1*sinAngle;
×
1699
                eta1 = (y-L1*cosAngle)/rho1;
×
1700
                const double zeta1sqr = 1.-xi*xi-eta1*eta1;
×
1701
                if (zeta1sqr < 0) return coordinates;
×
1702
                zeta1 = std::sqrt(zeta1sqr);
×
1703
        }
1704

1705
        double b = -eta1*sd1+zeta1*cd1;
×
1706
        double theta = std::atan2(xi,b)*M_180_PI;
×
1707
        double lngDeg = theta-mu;
×
1708
        lngDeg = StelUtils::fmodpos(lngDeg, 360.);
×
1709
        if (lngDeg > 180.) lngDeg -= 360.;
×
1710

1711
        double sfn1 = eta1*cd1+zeta1*sd1;
×
1712
        double cfn1 = std::sqrt(1.-sfn1*sfn1);
×
1713
        double tanLat = ff*sfn1/cfn1;
×
1714
        coordinates.latitude = std::atan(tanLat)*M_180_PI;
×
1715
        coordinates.longitude = lngDeg;
×
1716

1717
        return coordinates;
×
1718
}
1719

1720
auto SolarEclipseComputer::getMaximumEclipseAtRiseSet(bool first, double JD) const -> GeoPoint
×
1721
{
1722
        // Reference: Explanatory Supplement to the Astronomical Ephemeris
1723
        // and the American Ephemeris and Nautical Almanac, 3rd Edition (2013)
1724
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
1725
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
1726
        static const double e2 = f*(2.-f);
×
1727
        static const double ff = 1./(1.-f);
×
1728
        core->setJD(JD);
×
1729
        core->update(0);
×
1730
        const auto bp = calcBesselParameters(true);
×
1731
        const double bdot = bp.bdot, cdot = bp.cdot;
×
1732
        const double x = bp.elems.x, y = bp.elems.y, d = bp.elems.d, L1 = bp.elems.L1, mu = bp.elems.mu;
×
1733

1734
        using namespace std;
1735

1736
        const double sind = sin(d);
×
1737
        const double cosd = cos(d);
×
1738
        const double rho1 = sqrt(1-e2*sqr(cosd));
×
1739
        const double rho2 = sqrt(1-e2*sqr(sind));
×
1740
        const double sd1 = sind/rho1;
×
1741
        const double cd1 = sqrt(1-e2)*cosd/rho1;
×
1742
        const double sdd = e2*sind*cosd/(rho1*rho2);   // sin(d1-d2)
×
1743
        const double cdd = sqrt(1-sqr(sdd));           // cos(d1-d2)
×
1744

1745
        double qa = atan2(bdot,cdot);
×
1746
        if (!first) // there are two parts of the curve
×
1747
                qa += M_PI;
×
1748
        const double sgqa = x*cos(qa)-y*sin(qa);
×
1749

1750
        GeoPoint coordinates(99., 0.);
×
1751

1752
        // Iteration as described in equations (11.89) and (11.94) in the reference book
1753
        double rho = 1, gamma;
×
1754
        for(int n = 0; n < 3; ++n)
×
1755
        {
1756
                if(abs(sgqa / rho) > 1) return coordinates;
×
1757
                const double gqa = asin(sgqa / rho);
×
1758
                gamma = gqa+qa;
×
1759
                const double cosGamma = cos(gamma);
×
1760
                const double rho1sinGamma = rho1 * sin(gamma);
×
1761
                // simplified sin(atan2(rho1 * sin(gamma), cos(gamma)))
1762
                const double sinGammaPrime = rho1sinGamma / sqrt(sqr(rho1sinGamma)+sqr(cosGamma));
×
1763
                rho = sinGammaPrime / sin(gamma);
×
1764
        }
1765

1766
        const double xi = rho * sin(gamma);
×
1767
        const double eta = rho * cos(gamma);
×
1768

1769
        if (sqr(x-xi)+sqr(y-eta) > sqr(L1)) return coordinates;
×
1770

1771
        const double eta1 = eta / rho1;
×
1772
        const double zeta1 = (0 + eta1 * sdd) / cdd;
×
1773

1774
        const double b = -eta1*sd1+zeta1*cd1;
×
1775
        const double theta = atan2(xi,b)*M_180_PI;
×
1776
        double lngDeg = theta-mu;
×
1777
        lngDeg = StelUtils::fmodpos(lngDeg, 360.);
×
1778
        if (lngDeg > 180.) lngDeg -= 360.;
×
1779
        const double sfn1 = eta1*cd1+zeta1*sd1;
×
1780
        const double cfn1 = sqrt(1.-sfn1*sfn1);
×
1781
        const double tanLat = ff*sfn1/cfn1;
×
1782
        coordinates.latitude = atan(tanLat)*M_180_PI;
×
1783
        coordinates.longitude = lngDeg;
×
1784
        return coordinates;
×
1785
}
1786

1787
double SolarEclipseComputer::getDeltaTimeOfContact(double JD, bool beginning, bool penumbra, bool external, bool outerContact) const
×
1788
{
1789
        static SolarSystem* ssystem = GETSTELMODULE(SolarSystem);
×
1790
        static const double f = 1.0 - ssystem->getEarth()->getOneMinusOblateness(); // flattening
×
1791
        static const double e2 = f*(2.-f);
×
1792
        const int sign = outerContact ? 1 : -1; // there are outer & inner contacts
×
1793
        core->setJD(JD);
×
1794
        core->update(0);
×
1795
        const auto bp = calcBesselParameters(true);
×
1796
        const double xdot = bp.xdot;
×
1797
        double ydot = bp.ydot;
×
1798
        const double x = bp.elems.x, y = bp.elems.y, d = bp.elems.d, L1 = bp.elems.L1, L2 = bp.elems.L2;
×
1799
        const double rho1 = std::sqrt(1.-e2*std::cos(d)*std::cos(d));
×
1800
        double s,dt;
1801
        if (!penumbra)
×
1802
                ydot = ydot/rho1;
×
1803
        const double n = std::sqrt(xdot*xdot+ydot*ydot);
×
1804
        const double y1 = y/rho1;
×
1805
        const double m = std::sqrt(x*x+y*y);
×
1806
        const double m1 = std::sqrt(x*x+y1*y1);
×
1807
        const double rho = m/m1;
×
1808
        const double L = penumbra ? L1 : L2;
×
1809

1810
        if (external)
×
1811
        {
1812
                s = (x*ydot-y*xdot)/(n*(L+sign*rho)); // shadow's limb
×
1813
        }
1814
        else
1815
        {
1816
                s = (x*ydot-xdot*y1)/n; // center of shadow
×
1817
        }
1818
        double cs = std::sqrt(1.-s*s);
×
1819
        if (beginning)
×
1820
                cs *= -1.;
×
1821
        if (external)
×
1822
        {
1823
                dt = (L+sign*rho)*cs/n;
×
1824
                if (outerContact)
×
1825
                        dt -= ((x*xdot+y*ydot)/(n*n));
×
1826
                else
1827
                        dt = -((x*xdot+y*ydot)/(n*n))-dt;
×
1828
        }
1829
        else
1830
                dt = cs/n-((x*xdot+y1*ydot)/(n*n));
×
1831
        return dt;
×
1832
}
1833

1834
double SolarEclipseComputer::getJDofContact(double JD, bool beginning, bool penumbra, bool external, bool outerContact) const
×
1835
{
1836
        double dt = 1.;
×
1837
        int iterations = 0;
×
1838
        while (std::abs(dt)>(0.1/86400.) && (iterations < 10))
×
1839
        {
1840
                dt = getDeltaTimeOfContact(JD,beginning,penumbra,external,outerContact);
×
1841
                JD += dt/24.;
×
1842
                iterations++;
×
1843
        }
1844
        return JD;
×
1845
}
1846

1847
double SolarEclipseComputer::getJDofMinimumDistance(double JD) const
×
1848
{
1849
        const double currentJD = core->getJD(); // save current JD
×
1850
        double dt = 1.;
×
1851
        int iterations = 0;
×
1852
        while (std::abs(dt)>(0.1/86400.) && (iterations < 20)) // 0.1 second of accuracy
×
1853
        {
1854
                core->setJD(JD);
×
1855
                core->update(0);
×
1856
                const auto bp = calcBesselParameters(true);
×
1857
                const double xdot = bp.xdot, ydot = bp.ydot;
×
1858
                const double x = bp.elems.x, y = bp.elems.y;
×
1859
                double n2 = xdot*xdot + ydot*ydot;
×
1860
                dt = -(x*xdot + y*ydot)/n2;
×
1861
                JD += dt/24.;
×
1862
                iterations++;
×
1863
        }
1864
        core->setJD(currentJD);
×
1865
        core->update(0);
×
1866
        return JD;
×
1867
}
1868

1869
bool SolarEclipseComputer::generatePNGMap(const EclipseMapData& data, const QString& filePath) const
×
1870
{
1871
        QImage img(":/graphicGui/miscWorldMap.jpg");
×
1872

1873
        QPainter painter(&img);
×
1874
        painter.setRenderHint(QPainter::Antialiasing);
×
1875
        painter.translate(img.width() / 2., img.height() / 2.);
×
1876
        painter.scale(1,-1); // latitude grows upwards
×
1877

1878
        const double scale = img.width() / 360.;
×
1879
        const double penWidth = std::lround(img.width() / 2048.) / scale;
×
1880
        painter.scale(scale, scale);
×
1881

1882
        const auto updatePen = [&painter,penWidth](const EclipseMapData::EclipseType type =
×
1883
                                                           EclipseMapData::EclipseType::Undefined)
×
1884
        {
1885
                switch(type)
×
1886
                {
1887
                case EclipseMapData::EclipseType::Total:
×
1888
                        painter.setPen(QPen(totalEclipseColor, penWidth));
×
1889
                        break;
×
1890
                case EclipseMapData::EclipseType::Annular:
×
1891
                        painter.setPen(QPen(annularEclipseColor, penWidth));
×
1892
                        break;
×
1893
                case EclipseMapData::EclipseType::Hybrid:
×
1894
                        painter.setPen(QPen(hybridEclipseColor, penWidth));
×
1895
                        break;
×
1896
                default:
×
1897
                        painter.setPen(QPen(eclipseLimitsColor, penWidth));
×
1898
                        break;
×
1899
                }
1900
        };
×
1901

1902
        updatePen();
×
1903
        std::vector<QPointF> points;
×
1904
        for(const auto& penumbraLimit : data.penumbraLimits)
×
1905
        {
1906
                points.clear();
×
1907
                points.reserve(penumbraLimit.size());
×
1908
                for(const auto& p : penumbraLimit)
×
1909
                        points.emplace_back(p.longitude, p.latitude);
×
1910
                drawGeoLinesForEquirectMap(painter, points);
×
1911
        }
1912

1913
        for(const auto& riseSetLimit : data.riseSetLimits)
×
1914
        {
1915
                struct RiseSetLimitVisitor
1916
                {
1917
                        QPainter& painter;
1918
                        RiseSetLimitVisitor(QPainter& painter)
×
1919
                                : painter(painter)
×
1920
                        {
1921
                        }
×
1922
                        void operator()(const EclipseMapData::TwoLimits& limits) const
×
1923
                        {
1924
                                // P1-P2 curve
1925
                                std::vector<QPointF> points;
×
1926
                                points.reserve(limits.p12curve.size());
×
1927
                                for(const auto& p : limits.p12curve)
×
1928
                                        points.emplace_back(p.longitude, p.latitude);
×
1929
                                drawGeoLinesForEquirectMap(painter, points);
×
1930

1931
                                // P3-P4 curve
1932
                                points.clear();
×
1933
                                points.reserve(limits.p34curve.size());
×
1934
                                for(const auto& p : limits.p34curve)
×
1935
                                        points.emplace_back(p.longitude, p.latitude);
×
1936
                                drawGeoLinesForEquirectMap(painter, points);
×
1937
                        }
×
1938
                        void operator()(const EclipseMapData::SingleLimit& limit) const
×
1939
                        {
1940
                                std::vector<QPointF> points;
×
1941
                                points.reserve(limit.curve.size());
×
1942
                                for(const auto& p : limit.curve)
×
1943
                                        points.emplace_back(p.longitude, p.latitude);
×
1944
                                drawGeoLinesForEquirectMap(painter, points);
×
1945
                        }
×
1946
                };
1947
                std::visit(RiseSetLimitVisitor{painter}, riseSetLimit);
×
1948
        }
1949

1950
        for(const auto& curve : data.maxEclipseAtRiseSet)
×
1951
        {
1952
                points.clear();
×
1953
                points.reserve(curve.size());
×
1954
                for(const auto& p : curve)
×
1955
                        points.emplace_back(p.longitude, p.latitude);
×
1956
                drawGeoLinesForEquirectMap(painter, points);
×
1957
        }
1958

1959
        if(!data.centerLine.empty())
×
1960
        {
1961
                updatePen(data.eclipseType);
×
1962
                points.clear();
×
1963
                points.reserve(data.centerLine.size());
×
1964
                for(const auto& p : data.centerLine)
×
1965
                        points.emplace_back(p.longitude, p.latitude);
×
1966
                drawGeoLinesForEquirectMap(painter, points);
×
1967
        }
1968

1969
        for(const auto& umbraLimit : data.umbraLimits)
×
1970
        {
1971
                updatePen(data.eclipseType);
×
1972
                points.clear();
×
1973
                points.reserve(umbraLimit.size());
×
1974
                for(const auto& p : umbraLimit)
×
1975
                        points.emplace_back(p.longitude, p.latitude);
×
1976
                drawGeoLinesForEquirectMap(painter, points);
×
1977
        }
1978

1979
        for(const auto& outline : data.umbraOutlines)
×
1980
        {
1981
                updatePen(outline.eclipseType);
×
1982
                points.clear();
×
1983
                points.reserve(outline.curve.size());
×
1984
                for(const auto& p : outline.curve)
×
1985
                        points.emplace_back(p.longitude, p.latitude);
×
1986
                drawGeoLinesForEquirectMap(painter, points);
×
1987
        }
1988

1989
        return img.save(filePath, "PNG");
×
1990
}
×
1991

1992
void SolarEclipseComputer::generateKML(const EclipseMapData& data, const QString& dateString, QTextStream& stream) const
×
1993
{
1994
        using EclipseType = EclipseMapData::EclipseType;
1995

1996
        stream << "<?xml version='1.0' encoding='UTF-8'?>\n<kml xmlns='http://www.opengis.net/kml/2.2'>\n<Document>" << '\n';
×
1997
        stream << "<name>"+q_("Solar Eclipse")+" "+dateString+"</name>\n<description>"+q_("Created by Stellarium")+"</description>\n";
×
1998
        stream << "<Style id='Hybrid'>\n<LineStyle>\n<color>" << toKMLColorString(hybridEclipseColor) << "</color>\n<width>1</width>\n</LineStyle>\n";
×
1999
        stream << "<PolyStyle>\n<color>" << toKMLColorString(hybridEclipseColor) << "</color>\n</PolyStyle>\n</Style>\n";
×
2000
        stream << "<Style id='Total'>\n<LineStyle>\n<color>" << toKMLColorString(totalEclipseColor) << "</color>\n<width>1</width>\n</LineStyle>\n";
×
2001
        stream << "<PolyStyle>\n<color>" << toKMLColorString(totalEclipseColor) << "</color>\n</PolyStyle>\n</Style>\n";
×
2002
        stream << "<Style id='Annular'>\n<LineStyle>\n<color>" << toKMLColorString(annularEclipseColor) << "</color>\n<width>1</width>\n</LineStyle>\n";
×
2003
        stream << "<PolyStyle>\n<color>" << toKMLColorString(annularEclipseColor) << "</color>\n</PolyStyle>\n</Style>\n";
×
2004
        stream << "<Style id='PLimits'>\n<LineStyle>\n<color>" << toKMLColorString(eclipseLimitsColor) << "</color>\n<width>1</width>\n</LineStyle>\n";
×
2005
        stream << "<PolyStyle>\n<color>" << toKMLColorString(eclipseLimitsColor) << "</color>\n</PolyStyle>\n</Style>\n";
×
2006

2007
        {
2008
                const auto timeStr = localeMgr->getPrintableTimeLocal(data.greatestEclipse.JD);
×
2009
                stream << "<Placemark>\n<name>"+q_("Greatest eclipse")+" ("+timeStr+")</name>\n<Point>\n<coordinates>";
×
2010
                stream << data.greatestEclipse.longitude << "," << data.greatestEclipse.latitude << ",0.0\n";
×
2011
                stream << "</coordinates>\n</Point>\n</Placemark>\n";
×
2012
        }
×
2013

2014
        {
2015
                const auto timeStr = localeMgr->getPrintableTimeLocal(data.firstContactWithEarth.JD);
×
2016
                stream << "<Placemark>\n<name>"+q_("First contact with Earth")+" ("+timeStr+")</name>\n<Point>\n<coordinates>";
×
2017
                stream << data.firstContactWithEarth.longitude << "," << data.firstContactWithEarth.latitude << ",0.0\n";
×
2018
                stream << "</coordinates>\n</Point>\n</Placemark>\n";
×
2019
        }
×
2020

2021
        {
2022
                const auto timeStr = localeMgr->getPrintableTimeLocal(data.lastContactWithEarth.JD);
×
2023
                stream << "<Placemark>\n<name>"+q_("Last contact with Earth")+" ("+timeStr+")</name>\n<Point>\n<coordinates>";
×
2024
                stream << data.lastContactWithEarth.longitude << "," << data.lastContactWithEarth.latitude << ",0.0\n";
×
2025
                stream << "</coordinates>\n</Point>\n</Placemark>\n";
×
2026
        }
×
2027

2028
        const auto startLinePlaceMark = [&stream](const QString& name, const EclipseMapData::EclipseType type = EclipseType::Undefined)
×
2029
        {
2030
                switch(type)
×
2031
                {
2032
                case EclipseMapData::EclipseType::Total:
×
2033
                        stream << "<Placemark>\n<name>" << name << "</name>\n<styleUrl>#Total</styleUrl>\n<LineString>\n<extrude>1</extrude>\n";
×
2034
                        break;
×
2035
                case EclipseMapData::EclipseType::Annular:
×
2036
                        stream << "<Placemark>\n<name>" << name << "</name>\n<styleUrl>#Annular</styleUrl>\n<LineString>\n<extrude>1</extrude>\n";
×
2037
                        break;
×
2038
                case EclipseMapData::EclipseType::Hybrid:
×
2039
                        stream << "<Placemark>\n<name>" << name << "</name>\n<styleUrl>#Hybrid</styleUrl>\n<LineString>\n<extrude>1</extrude>\n";
×
2040
                        break;
×
2041
                default:
×
2042
                        stream << "<Placemark>\n<name>" << name << "</name>\n<styleUrl>#PLimits</styleUrl>\n<LineString>\n<extrude>1</extrude>\n";
×
2043
                        break;
×
2044
                }
2045
        };
×
2046

2047
        for(const auto& penumbraLimit : data.penumbraLimits)
×
2048
        {
2049
                startLinePlaceMark("PenumbraLimit");
×
2050
                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2051
                for(const auto& p : penumbraLimit)
×
2052
                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2053
                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2054
        }
2055

2056
        for(const auto& riseSetLimit : data.riseSetLimits)
×
2057
        {
2058
                struct RiseSetLimitVisitor
2059
                {
2060
                        QTextStream& stream;
2061
                        std::function<void(const QString&, EclipseType)> startLinePlaceMark;
2062
                        RiseSetLimitVisitor(QTextStream& stream, const std::function<void(const QString&, EclipseType)>& startLinePlaceMark)
×
2063
                                : stream(stream), startLinePlaceMark(startLinePlaceMark)
×
2064
                        {
2065
                        }
×
2066
                        void operator()(const EclipseMapData::TwoLimits& limits) const
×
2067
                        {
2068
                                // P1-P2 curve
2069
                                startLinePlaceMark("RiseSetLimit", EclipseType::Undefined);
×
2070
                                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2071
                                for(const auto& p : limits.p12curve)
×
2072
                                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2073
                                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2074

2075
                                // P3-P4 curve
2076
                                startLinePlaceMark("RiseSetLimit", EclipseType::Undefined);
×
2077
                                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2078
                                for(const auto& p : limits.p34curve)
×
2079
                                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2080
                                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2081
                        }
×
2082
                        void operator()(const EclipseMapData::SingleLimit& limit) const
×
2083
                        {
2084
                                startLinePlaceMark("RiseSetLimit", EclipseType::Undefined);
×
2085
                                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2086
                                for(const auto& p : limit.curve)
×
2087
                                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2088
                                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2089
                        }
×
2090
                };
2091
                std::visit(RiseSetLimitVisitor{stream, startLinePlaceMark}, riseSetLimit);
×
2092
        }
2093

2094
        for(const auto& curve : data.maxEclipseAtRiseSet)
×
2095
        {
2096
                startLinePlaceMark("MaxEclipseSunriseSunset");
×
2097
                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2098
                for(const auto& p : curve)
×
2099
                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2100
                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2101
        }
2102

2103
        if(data.centralEclipseStart.JD > 0)
×
2104
        {
2105
                const auto timeStr = localeMgr->getPrintableTimeLocal(data.centralEclipseStart.JD);
×
2106
                stream << "<Placemark>\n<name>"+q_("Central eclipse begins")+" ("+timeStr+")</name>\n<Point>\n<coordinates>";
×
2107
                stream << data.centralEclipseStart.longitude << "," << data.centralEclipseStart.latitude << ",0.0\n";
×
2108
                stream << "</coordinates>\n</Point>\n</Placemark>\n";
×
2109
        }
×
2110

2111
        if(data.centralEclipseEnd.JD > 0)
×
2112
        {
2113
                const auto timeStr = localeMgr->getPrintableTimeLocal(data.centralEclipseEnd.JD);
×
2114
                stream << "<Placemark>\n<name>"+q_("Central eclipse ends")+" ("+timeStr+")</name>\n<Point>\n<coordinates>";
×
2115
                stream << data.centralEclipseEnd.longitude << "," << data.centralEclipseEnd.latitude << ",0.0\n";
×
2116
                stream << "</coordinates>\n</Point>\n</Placemark>\n";
×
2117
        }
×
2118

2119
        if(!data.centerLine.empty())
×
2120
        {
2121
                startLinePlaceMark("Center line", data.eclipseType);
×
2122
                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2123
                for(const auto& p : data.centerLine)
×
2124
                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2125
                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2126
        }
2127

2128
        for(const auto& outline : data.umbraOutlines)
×
2129
        {
2130
                const auto timeStr = localeMgr->getPrintableTimeLocal(outline.JD);
×
2131
                startLinePlaceMark(timeStr, outline.eclipseType);
×
2132
                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2133
                for(const auto& p : outline.curve)
×
2134
                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2135
                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2136
        }
×
2137

2138
        for(const auto& umbraLimit : data.umbraLimits)
×
2139
        {
2140
                startLinePlaceMark("Limit", data.eclipseType);
×
2141
                stream << "<tessellate>1</tessellate>\n<altitudeMode>absoluto</altitudeMode>\n<coordinates>\n";
×
2142
                for(const auto& p : umbraLimit)
×
2143
                        stream << p.longitude << "," << p.latitude << ",0.0\n";
×
2144
                stream << "</coordinates>\n</LineString>\n</Placemark>\n";
×
2145
        }
2146

2147
        stream << "</Document>\n</kml>\n";
×
2148
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc