1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2012 Rene Kuettner <rene@bitkanal.net>
4 //
5
6 #include "EclipsesItem.h"
7
8 #include "MarbleDebug.h"
9
10 #include <QIcon>
11
12 namespace Marble
13 {
14
EclipsesItem(EclSolar * ecl,int index,QObject * parent)15 EclipsesItem::EclipsesItem( EclSolar *ecl, int index, QObject *parent )
16 : QObject( parent ),
17 m_ecl( ecl ),
18 m_index( index ),
19 m_calculationsNeedUpdate( true ),
20 m_isTotal( false ),
21 m_phase( TotalSun ),
22 m_magnitude( 0. ),
23 m_centralLine(Tessellate),
24 m_umbra( Tessellate ),
25 m_southernPenumbra( Tessellate ),
26 m_northernPenumbra( Tessellate ),
27 m_shadowConeUmbra( Tessellate ),
28 m_shadowConePenumbra( Tessellate ),
29 m_shadowCone60MagPenumbra( Tessellate )
30 {
31 initialize();
32 }
33
~EclipsesItem()34 EclipsesItem::~EclipsesItem()
35 {
36 }
37
index() const38 int EclipsesItem::index() const
39 {
40 return m_index;
41 }
42
takesPlaceAt(const QDateTime & dateTime) const43 bool EclipsesItem::takesPlaceAt( const QDateTime &dateTime ) const
44 {
45 return ( ( m_startDatePartial <= dateTime ) &&
46 ( m_endDatePartial >= dateTime ) );
47 }
48
phase() const49 EclipsesItem::EclipsePhase EclipsesItem::phase() const
50 {
51 return m_phase;
52 }
53
icon() const54 QIcon EclipsesItem::icon() const
55 {
56 switch( m_phase ) {
57 case EclipsesItem::TotalMoon:
58 return QIcon(QStringLiteral(":res/lunar_total.png"));
59 case EclipsesItem::PartialMoon:
60 return QIcon(QStringLiteral(":res/lunar_partial.png"));
61 case EclipsesItem::PenumbralMoon:
62 return QIcon(QStringLiteral(":res/lunar_penumbra.png"));
63 case EclipsesItem::PartialSun:
64 return QIcon(QStringLiteral(":res/solar_partial.png"));
65 case EclipsesItem::NonCentralAnnularSun:
66 case EclipsesItem::AnnularSun:
67 return QIcon(QStringLiteral(":res/solar_annular.png"));
68 case EclipsesItem::AnnularTotalSun:
69 case EclipsesItem::NonCentralTotalSun:
70 case EclipsesItem::TotalSun:
71 return QIcon(QStringLiteral(":res/solar_total.png"));
72 }
73
74 return QIcon();
75 }
76
phaseText() const77 QString EclipsesItem::phaseText() const
78 {
79 switch( m_phase ) {
80 case TotalMoon: return tr( "Moon, Total" );
81 case PartialMoon: return tr( "Moon, Partial" );
82 case PenumbralMoon: return tr( "Moon, Penumbral" );
83 case PartialSun: return tr( "Sun, Partial" );
84 case NonCentralAnnularSun: return tr( "Sun, non-central, Annular" );
85 case NonCentralTotalSun: return tr( "Sun, non-central, Total" );
86 case AnnularSun: return tr( "Sun, Annular" );
87 case TotalSun: return tr( "Sun, Total" );
88 case AnnularTotalSun: return tr( "Sun, Annular/Total" );
89 }
90
91 return QString();
92 }
93
magnitude() const94 double EclipsesItem::magnitude() const
95 {
96 return m_magnitude;
97 }
98
dateMaximum() const99 const QDateTime& EclipsesItem::dateMaximum() const
100 {
101 return m_dateMaximum;
102 }
103
startDatePartial() const104 const QDateTime& EclipsesItem::startDatePartial() const
105 {
106 return m_startDatePartial;
107 }
108
endDatePartial() const109 const QDateTime& EclipsesItem::endDatePartial() const
110 {
111 return m_endDatePartial;
112 }
113
partialDurationHours() const114 int EclipsesItem::partialDurationHours() const
115 {
116 return (m_endDatePartial.toTime_t() -
117 m_startDatePartial.toTime_t()) / 3600;
118 }
119
startDateTotal() const120 const QDateTime& EclipsesItem::startDateTotal() const
121 {
122 return m_startDateTotal;
123 }
124
endDateTotal() const125 const QDateTime& EclipsesItem::endDateTotal() const
126 {
127 return m_endDateTotal;
128 }
129
maxLocation()130 const GeoDataCoordinates& EclipsesItem::maxLocation()
131 {
132 if( m_calculationsNeedUpdate ) {
133 calculate();
134 }
135
136 return m_maxLocation;
137 }
138
centralLine()139 const GeoDataLineString& EclipsesItem::centralLine()
140 {
141 if( m_calculationsNeedUpdate ) {
142 calculate();
143 }
144
145 return m_centralLine;
146 }
147
umbra()148 const GeoDataLinearRing& EclipsesItem::umbra()
149 {
150 if( m_calculationsNeedUpdate ) {
151 calculate();
152 }
153
154 return m_umbra;
155 }
156
southernPenumbra()157 const GeoDataLineString& EclipsesItem::southernPenumbra()
158 {
159 if( m_calculationsNeedUpdate ) {
160 calculate();
161 }
162
163 return m_southernPenumbra;
164 }
165
northernPenumbra()166 const GeoDataLineString& EclipsesItem::northernPenumbra()
167 {
168 if( m_calculationsNeedUpdate ) {
169 calculate();
170 }
171
172 return m_northernPenumbra;
173 }
174
shadowConeUmbra()175 GeoDataLinearRing EclipsesItem::shadowConeUmbra()
176 {
177 if( m_calculationsNeedUpdate ) {
178 calculate();
179 }
180
181 return m_shadowConeUmbra;
182 }
183
shadowConePenumbra()184 GeoDataLinearRing EclipsesItem::shadowConePenumbra()
185 {
186 if( m_calculationsNeedUpdate ) {
187 calculate();
188 }
189
190 return m_shadowConePenumbra;
191 }
192
shadowCone60MagPenumbra()193 GeoDataLinearRing EclipsesItem::shadowCone60MagPenumbra()
194 {
195 if( m_calculationsNeedUpdate ) {
196 calculate();
197 }
198
199 return m_shadowCone60MagPenumbra;
200 }
201
sunBoundaries()202 const QList<GeoDataLinearRing>& EclipsesItem::sunBoundaries()
203 {
204 if( m_calculationsNeedUpdate ) {
205 calculate();
206 }
207
208 return m_sunBoundaries;
209 }
210
initialize()211 void EclipsesItem::initialize()
212 {
213 // set basic information
214 int year, month, day, hour, min, phase;
215 double secs, tz;
216
217 phase = m_ecl->getEclYearInfo( m_index, year, month, day,
218 hour, min, secs,
219 tz, m_magnitude );
220
221 switch( phase ) {
222 case -4: m_phase = EclipsesItem::TotalMoon; break;
223 case -3: m_phase = EclipsesItem::PartialMoon; break;
224 case -2:
225 case -1: m_phase = EclipsesItem::PenumbralMoon; break;
226 case 1: m_phase = EclipsesItem::PartialSun; break;
227 case 2: m_phase = EclipsesItem::NonCentralAnnularSun; break;
228 case 3: m_phase = EclipsesItem::NonCentralTotalSun; break;
229 case 4: m_phase = EclipsesItem::AnnularSun; break;
230 case 5: m_phase = EclipsesItem::TotalSun; break;
231 case 6: m_phase = EclipsesItem::AnnularTotalSun; break;
232 default:
233 mDebug() << "Invalid phase for eclipse at" << year << "/" <<
234 day << "/" << month << "!";
235 }
236
237 m_dateMaximum = QDateTime( QDate( year, month, day ),
238 QTime( hour, min, secs ),
239 Qt::LocalTime );
240
241 // get global start/end date of eclipse
242
243 double mjd_start, mjd_end;
244 m_ecl->putEclSelect( m_index );
245
246 if( m_ecl->getPartial( mjd_start, mjd_end ) != 0 ) {
247 m_ecl->getDatefromMJD( mjd_start, year, month, day, hour, min, secs );
248 m_startDatePartial = QDateTime( QDate( year, month, day ),
249 QTime( hour, min, secs ),
250 Qt::LocalTime );
251 m_ecl->getDatefromMJD( mjd_end, year, month, day, hour, min, secs );
252 m_endDatePartial = QDateTime( QDate( year, month, day ),
253 QTime( hour, min, secs ),
254 Qt::LocalTime );
255 } else {
256 // duration is shorter than 1 min
257 m_startDatePartial = m_dateMaximum;
258 m_endDatePartial = m_dateMaximum;
259 }
260
261 m_isTotal = ( m_ecl->getTotal( mjd_start, mjd_end ) != 0 );
262 if( m_isTotal ) {
263 m_ecl->getDatefromMJD( mjd_start, year, month, day, hour, min, secs );
264 m_startDateTotal = QDateTime( QDate( year, month, day ),
265 QTime( hour, min, secs ),
266 Qt::LocalTime );
267 m_ecl->getDatefromMJD( mjd_end, year, month, day, hour, min, secs );
268 m_endDateTotal = QDateTime( QDate( year, month, day ),
269 QTime( hour, min, secs ),
270 Qt::LocalTime );
271 }
272
273 // detailed calculations are done when required
274 m_calculationsNeedUpdate = true;
275 }
276
calculate()277 void EclipsesItem::calculate()
278 {
279 int np, kp, j;
280 double lat1, lng1, lat2, lng2, lat3, lng3, lat4, lng4;
281 double ltf[60], lnf[60];
282
283 m_ecl->putEclSelect( m_index );
284
285 // FIXME: set observer location
286 m_ecl->getMaxPos( lat1, lng1 );
287 m_ecl->setLocalPos( lat1, lng1, 0 );
288
289 // eclipse's maximum location
290 m_maxLocation = GeoDataCoordinates( lng1, lat1, 0., GeoDataCoordinates::Degree );
291
292 // calculate central line
293 np = m_ecl->eclPltCentral( true, lat1, lng1 );
294 kp = np;
295 m_centralLine.clear();
296 m_centralLine << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
297 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
298 0., GeoDataCoordinates::Degree );
299
300 if( np > 3 ) { // central eclipse
301 while( np > 3 ) {
302 np = m_ecl->eclPltCentral( false, lat1, lng1 );
303 if( np > 3 ) {
304 m_centralLine << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
305 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
306 0., GeoDataCoordinates::Degree );
307 }
308 }
309 }
310
311 // calculate umbra
312 np = kp;
313 m_umbra.clear();
314 if( np > 3 ) { // total or annual eclipse
315 // northern /southern boundaries of umbra
316 np = m_ecl->centralBound( true, lat1, lng1, lat2, lng2 );
317
318 GeoDataLinearRing lowerUmbra( Tessellate ), upperUmbra( Tessellate );
319 lowerUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
320 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
321 0., GeoDataCoordinates::Degree );
322 upperUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
323 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
324 0., GeoDataCoordinates::Degree );
325
326 while( np > 0 ) {
327 np = m_ecl->centralBound( false, lat1, lng1, lat2, lng2 );
328 if( lat1 <= 90. ) {
329 lowerUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
330 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
331 0., GeoDataCoordinates::Degree );
332 }
333 if( lat1 <= 90. ) {
334 upperUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng2, GeoDataCoordinates::Degree),
335 GeoDataCoordinates::normalizeLat(lat2, GeoDataCoordinates::Degree),
336 0., GeoDataCoordinates::Degree );
337 }
338 }
339
340 GeoDataLinearRing invertedUpperUmbra( Tessellate );
341 QVector<GeoDataCoordinates>::const_iterator iter = upperUmbra.constEnd() - 1;
342 for( ; iter != upperUmbra.constBegin(); --iter ) {
343 invertedUpperUmbra << *iter;
344 }
345 invertedUpperUmbra << upperUmbra.first();
346 upperUmbra = invertedUpperUmbra;
347
348 m_umbra << lowerUmbra << upperUmbra;
349 }
350
351 // shadow cones
352 m_shadowConeUmbra.clear();
353 m_shadowConePenumbra.clear();
354 m_shadowCone60MagPenumbra.clear();
355
356 m_ecl->getLocalMax( lat2, lat3, lat4 );
357
358 m_ecl->getShadowCone( lat2, true, 40, ltf, lnf );
359 for( j = 0; j < 40; ++j ) {
360 if( ltf[j] < 100. ) {
361 m_shadowConeUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lnf[j], GeoDataCoordinates::Degree),
362 GeoDataCoordinates::normalizeLat(ltf[j], GeoDataCoordinates::Degree),
363 0., GeoDataCoordinates::Degree );
364 }
365 }
366
367 m_ecl->setPenumbraAngle( 1., 0 );
368 m_ecl->getShadowCone( lat2, false, 60, ltf, lnf );
369 for( j = 0; j < 60; ++j ) {
370 if( ltf[j] < 100. ) {
371 m_shadowConePenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lnf[j], GeoDataCoordinates::Degree),
372 GeoDataCoordinates::normalizeLat(ltf[j], GeoDataCoordinates::Degree),
373 0., GeoDataCoordinates::Degree );
374 }
375 }
376
377 m_ecl->setPenumbraAngle( 0.6, 1 );
378 m_ecl->getShadowCone( lat2, false, 60, ltf, lnf );
379 for( j = 0; j < 60; ++j ) {
380 if( ltf[j] < 100. ) {
381 m_shadowCone60MagPenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lnf[j], GeoDataCoordinates::Degree),
382 GeoDataCoordinates::normalizeLat(ltf[j], GeoDataCoordinates::Degree),
383 0., GeoDataCoordinates::Degree );
384 }
385 }
386
387 m_ecl->setPenumbraAngle( 1., 0 );
388
389 // eclipse boundaries
390 m_southernPenumbra.clear();
391 m_northernPenumbra.clear();
392
393 np = m_ecl->GNSBound( true, true, lat1, lng2 );
394 while( np > 0 ) {
395 np = m_ecl->GNSBound( false, true, lat1, lng1 );
396 if( ( np > 0 ) && ( lat1 <= 90. ) ) {
397 m_southernPenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
398 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
399 0., GeoDataCoordinates::Degree );
400 }
401 }
402
403 np = m_ecl->GNSBound( true, false, lat1, lng1 );
404 while( np > 0 ) {
405 np = m_ecl->GNSBound( false, false, lat1, lng1 );
406 if( ( np > 0 ) && ( lat1 <= 90. ) ) {
407 m_northernPenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
408 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
409 0., GeoDataCoordinates::Degree );
410 }
411 }
412
413 // sunrise / sunset boundaries
414
415 QList<GeoDataLinearRing*> sunBoundaries;
416 np = m_ecl->GRSBound( true, lat1, lng1, lat3, lng3 );
417
418 GeoDataLinearRing *lowerBoundary = new GeoDataLinearRing( Tessellate );
419 *lowerBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
420 GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
421 0., GeoDataCoordinates::Degree );
422
423 GeoDataLinearRing *upperBoundary = new GeoDataLinearRing( Tessellate );
424 *upperBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng3, GeoDataCoordinates::Degree),
425 GeoDataCoordinates::normalizeLat(lat3, GeoDataCoordinates::Degree),
426 0., GeoDataCoordinates::Degree );
427
428 m_sunBoundaries.clear();
429
430 while ( np > 0 ) {
431 np = m_ecl->GRSBound( false, lat2, lng2, lat4, lng4 );
432 bool pline = fabs( lng1 - lng2 ) < 10.; // during partial eclipses, the Rise/Set
433 // lines switch at one stage.
434 // This will prevent an ugly line between
435 // the switch points. If there is a
436 // longitude jump then add the current
437 // section to our sun boundaries collection
438 // and start a new section
439 if ( !pline && !lowerBoundary->isEmpty() ) {
440 sunBoundaries.prepend( lowerBoundary );
441 lowerBoundary = new GeoDataLinearRing( Tessellate );
442 }
443 if ( ( np > 0 ) && ( lat2 <= 90. ) && ( lat1 <= 90. ) ) {
444 *lowerBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng2, GeoDataCoordinates::Degree),
445 GeoDataCoordinates::normalizeLat(lat2, GeoDataCoordinates::Degree),
446 0., GeoDataCoordinates::Degree );
447 }
448 pline = fabs( lng3 - lng4 ) < 10.; // during partial eclipses, the Rise/Set lines
449 // switch at one stage.
450 // This will prevent an ugly line between the
451 // switch points. If there is a longitude jump
452 // then add the current section to our sun
453 // boundaries collection and start a new section
454 if ( !pline && !upperBoundary->isEmpty() ) {
455 sunBoundaries.prepend( upperBoundary );
456 upperBoundary = new GeoDataLinearRing( Tessellate );
457 }
458 if ( pline && ( np > 0 ) && ( lat4 <= 90. ) && ( lat3 <= 90. ) ) {
459 *upperBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng4, GeoDataCoordinates::Degree),
460 GeoDataCoordinates::normalizeLat(lat4, GeoDataCoordinates::Degree),
461 0., GeoDataCoordinates::Degree );
462 }
463
464 lng1 = lng2;
465 lat1 = lat2;
466 lng3 = lng4;
467 lat3 = lat4;
468 }
469
470 if ( !lowerBoundary->isEmpty() ) {
471 sunBoundaries.prepend(lowerBoundary);
472 } else {
473 delete lowerBoundary;
474 }
475 if ( !upperBoundary->isEmpty() ) {
476 sunBoundaries.prepend(upperBoundary);
477 } else {
478 delete upperBoundary;
479 }
480
481 for ( int result = 0; result < 2; ++result ) {
482 GeoDataLinearRing sunBoundary( Tessellate );
483
484 sunBoundary = *sunBoundaries.last();
485 sunBoundaries.pop_back();
486
487 while ( sunBoundaries.size() > 0) {
488 int closestSection = -1;
489
490 // TODO: Now that MableMath is not public anymore we need a
491 // GeoDataCoordinates::distance() method in Marble.
492 GeoDataLineString ruler;
493 ruler << sunBoundary.last() << sunBoundary.first();
494 qreal closestDistance = ruler.length( 1 );
495 int closestEnd = 0; // 0 = start of section, 1 = end of section
496
497 // Look for a section that is closest to our sunBoundary section.
498 for ( int it = 0; it < sunBoundaries.size(); ++it ) {
499 GeoDataLineString distanceStartSection;
500 distanceStartSection << sunBoundary.last() << sunBoundaries.at( it )->first();
501
502 GeoDataLineString distanceEndSection;
503 distanceEndSection << sunBoundary.last() << sunBoundaries.at( it )->last();
504 if ( distanceStartSection.length( 1 ) < closestDistance ) {
505 closestDistance = distanceStartSection.length( 1 );
506 closestSection = it;
507 closestEnd = 0;
508 }
509 if ( distanceEndSection.length(1) < closestDistance ) {
510 closestDistance = distanceEndSection.length( 1 );
511 closestSection = it;
512 closestEnd = 1;
513 }
514 }
515
516 if ( closestSection == -1 ) {
517 // There is no other section that is closer to the end of
518 // our sunBoundary section than the startpoint of our
519 // sunBoundary itself
520 break;
521 }
522 else {
523 // We now concatenate the closest section to the sunBoundary.
524 // First we might have to invert it so that we concatenate
525 // the right end
526 if ( closestEnd == 1 ) {
527 // TODO: replace this with a GeoDataLinearRing::invert()
528 // method that needs to be added to Marble ...
529 GeoDataLinearRing * invertedBoundary = new GeoDataLinearRing( Tessellate );
530 QVector<GeoDataCoordinates>::const_iterator iter = sunBoundaries.at( closestSection )->constEnd();
531 --iter;
532 for( ; iter != sunBoundaries.at( closestSection )->constBegin(); --iter ) {
533 *invertedBoundary << *iter;
534 }
535 *invertedBoundary << sunBoundaries.at( closestSection )->first();
536 delete sunBoundaries[closestSection];
537 sunBoundaries[closestSection] = invertedBoundary;
538 }
539 sunBoundary << *sunBoundaries[closestSection];
540
541 // Now remove the section that we've just added from the list
542 delete sunBoundaries[closestSection];
543 sunBoundaries.removeAt( closestSection );
544 }
545 }
546
547 m_sunBoundaries << sunBoundary;
548
549 if ( sunBoundaries.size() == 0 ) break;
550 }
551
552 m_calculationsNeedUpdate = false;
553 }
554
555 } // Namespace Marble
556
557 #include "moc_EclipsesItem.cpp"
558
559