File indexing completed on 2024-05-05 03:50:42

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2012 Rene Kuettner <rene@bitkanal.net>
0004 //
0005 
0006 #include "EclipsesItem.h"
0007 
0008 #include "MarbleDebug.h"
0009 
0010 #include <QIcon>
0011 
0012 namespace Marble
0013 {
0014 
0015 EclipsesItem::EclipsesItem( EclSolar *ecl, int index, QObject *parent )
0016     : QObject( parent ),
0017       m_ecl( ecl ),
0018       m_index( index ),
0019       m_calculationsNeedUpdate( true ),
0020       m_isTotal( false ),
0021       m_phase( TotalSun ),
0022       m_magnitude( 0. ),
0023       m_centralLine(Tessellate),
0024       m_umbra( Tessellate ),
0025       m_southernPenumbra( Tessellate ),
0026       m_northernPenumbra( Tessellate ),
0027       m_shadowConeUmbra( Tessellate ),
0028       m_shadowConePenumbra( Tessellate ),
0029       m_shadowCone60MagPenumbra( Tessellate )
0030 {
0031     initialize();
0032 }
0033 
0034 EclipsesItem::~EclipsesItem()
0035 {
0036 }
0037 
0038 int EclipsesItem::index() const
0039 {
0040     return m_index;
0041 }
0042 
0043 bool EclipsesItem::takesPlaceAt( const QDateTime &dateTime ) const
0044 {
0045     return ( ( m_startDatePartial <= dateTime ) &&
0046              ( m_endDatePartial >= dateTime ) );
0047 }
0048 
0049 EclipsesItem::EclipsePhase EclipsesItem::phase() const
0050 {
0051     return m_phase;
0052 }
0053 
0054 QIcon EclipsesItem::icon() const
0055 {
0056     switch( m_phase ) {
0057         case EclipsesItem::TotalMoon:
0058             return QIcon(QStringLiteral(":res/lunar_total.png"));
0059         case EclipsesItem::PartialMoon:
0060             return QIcon(QStringLiteral(":res/lunar_partial.png"));
0061         case EclipsesItem::PenumbralMoon:
0062             return QIcon(QStringLiteral(":res/lunar_penumbra.png"));
0063         case EclipsesItem::PartialSun:
0064             return QIcon(QStringLiteral(":res/solar_partial.png"));
0065         case EclipsesItem::NonCentralAnnularSun:
0066         case EclipsesItem::AnnularSun:
0067             return QIcon(QStringLiteral(":res/solar_annular.png"));
0068         case EclipsesItem::AnnularTotalSun:
0069         case EclipsesItem::NonCentralTotalSun:
0070         case EclipsesItem::TotalSun:
0071             return QIcon(QStringLiteral(":res/solar_total.png"));
0072     }
0073 
0074     return QIcon();
0075 }
0076 
0077 QString EclipsesItem::phaseText() const
0078 {
0079     switch( m_phase ) {
0080         case TotalMoon:             return tr( "Moon, Total" );
0081         case PartialMoon:           return tr( "Moon, Partial" );
0082         case PenumbralMoon:         return tr( "Moon, Penumbral" );
0083         case PartialSun:            return tr( "Sun, Partial" );
0084         case NonCentralAnnularSun:  return tr( "Sun, non-central, Annular" );
0085         case NonCentralTotalSun:    return tr( "Sun, non-central, Total" );
0086         case AnnularSun:            return tr( "Sun, Annular" );
0087         case TotalSun:              return tr( "Sun, Total" );
0088         case AnnularTotalSun:       return tr( "Sun, Annular/Total" );
0089     }
0090 
0091     return QString();
0092 }
0093 
0094 double EclipsesItem::magnitude() const
0095 {
0096     return m_magnitude;
0097 }
0098 
0099 const QDateTime& EclipsesItem::dateMaximum() const
0100 {
0101     return m_dateMaximum;
0102 }
0103 
0104 const QDateTime& EclipsesItem::startDatePartial() const
0105 {
0106     return m_startDatePartial;
0107 }
0108 
0109 const QDateTime& EclipsesItem::endDatePartial() const
0110 {
0111     return m_endDatePartial;
0112 }
0113 
0114 int EclipsesItem::partialDurationHours() const
0115 {
0116     return (m_endDatePartial.toTime_t() -
0117             m_startDatePartial.toTime_t()) / 3600;
0118 }
0119 
0120 const QDateTime& EclipsesItem::startDateTotal() const
0121 {
0122     return m_startDateTotal;
0123 }
0124 
0125 const QDateTime& EclipsesItem::endDateTotal() const
0126 {
0127     return m_endDateTotal;
0128 }
0129 
0130 const GeoDataCoordinates& EclipsesItem::maxLocation()
0131 {
0132     if( m_calculationsNeedUpdate ) {
0133         calculate();
0134     }
0135 
0136     return m_maxLocation;
0137 }
0138 
0139 const GeoDataLineString& EclipsesItem::centralLine()
0140 {
0141     if( m_calculationsNeedUpdate ) {
0142         calculate();
0143     }
0144 
0145     return m_centralLine;
0146 }
0147 
0148 const GeoDataLinearRing& EclipsesItem::umbra()
0149 {
0150     if( m_calculationsNeedUpdate ) {
0151         calculate();
0152     }
0153 
0154     return m_umbra;
0155 }
0156 
0157 const GeoDataLineString& EclipsesItem::southernPenumbra()
0158 {
0159     if( m_calculationsNeedUpdate ) {
0160         calculate();
0161     }
0162 
0163     return m_southernPenumbra;
0164 }
0165 
0166 const GeoDataLineString& EclipsesItem::northernPenumbra()
0167 {
0168     if( m_calculationsNeedUpdate ) {
0169         calculate();
0170     }
0171 
0172     return m_northernPenumbra;
0173 }
0174 
0175 GeoDataLinearRing EclipsesItem::shadowConeUmbra()
0176 {
0177     if( m_calculationsNeedUpdate ) {
0178         calculate();
0179     }
0180 
0181     return m_shadowConeUmbra;
0182 }
0183 
0184 GeoDataLinearRing EclipsesItem::shadowConePenumbra()
0185 {
0186     if( m_calculationsNeedUpdate ) {
0187         calculate();
0188     }
0189 
0190     return m_shadowConePenumbra;
0191 }
0192 
0193 GeoDataLinearRing EclipsesItem::shadowCone60MagPenumbra()
0194 {
0195     if( m_calculationsNeedUpdate ) {
0196         calculate();
0197     }
0198 
0199     return m_shadowCone60MagPenumbra;
0200 }
0201 
0202 const QList<GeoDataLinearRing>& EclipsesItem::sunBoundaries()
0203 {
0204     if( m_calculationsNeedUpdate ) {
0205         calculate();
0206     }
0207 
0208     return m_sunBoundaries;
0209 }
0210 
0211 void EclipsesItem::initialize()
0212 {
0213     // set basic information
0214     int year, month, day, hour, min, phase;
0215     double secs, tz;
0216 
0217     phase = m_ecl->getEclYearInfo( m_index, year, month, day,
0218                                             hour, min, secs,
0219                                             tz, m_magnitude );
0220 
0221     switch( phase ) {
0222         case -4: m_phase = EclipsesItem::TotalMoon; break;
0223         case -3: m_phase = EclipsesItem::PartialMoon; break;
0224         case -2:
0225         case -1: m_phase = EclipsesItem::PenumbralMoon; break;
0226         case  1: m_phase = EclipsesItem::PartialSun; break;
0227         case  2: m_phase = EclipsesItem::NonCentralAnnularSun; break;
0228         case  3: m_phase = EclipsesItem::NonCentralTotalSun; break;
0229         case  4: m_phase = EclipsesItem::AnnularSun; break;
0230         case  5: m_phase = EclipsesItem::TotalSun; break;
0231         case  6: m_phase = EclipsesItem::AnnularTotalSun; break;
0232         default:
0233             mDebug() << "Invalid phase for eclipse at" << year << "/" <<
0234                         day << "/" << month << "!";
0235     }
0236 
0237     m_dateMaximum = QDateTime( QDate( year, month, day ),
0238                                QTime( hour, min, secs ),
0239                                Qt::LocalTime );
0240 
0241     // get global start/end date of eclipse
0242 
0243     double mjd_start, mjd_end;
0244     m_ecl->putEclSelect( m_index );
0245 
0246     if( m_ecl->getPartial( mjd_start, mjd_end ) != 0 ) {
0247         m_ecl->getDatefromMJD( mjd_start, year, month, day, hour, min, secs );
0248         m_startDatePartial = QDateTime( QDate( year, month, day ),
0249                                         QTime( hour, min, secs ),
0250                                         Qt::LocalTime );
0251         m_ecl->getDatefromMJD( mjd_end, year, month, day, hour, min, secs );
0252         m_endDatePartial = QDateTime( QDate( year, month, day ),
0253                                       QTime( hour, min, secs ),
0254                                       Qt::LocalTime );
0255     } else {
0256         // duration is shorter than 1 min
0257         m_startDatePartial = m_dateMaximum;
0258         m_endDatePartial = m_dateMaximum;
0259     }
0260 
0261     m_isTotal = ( m_ecl->getTotal( mjd_start, mjd_end ) != 0 );
0262     if( m_isTotal ) {
0263         m_ecl->getDatefromMJD( mjd_start, year, month, day, hour, min, secs );
0264         m_startDateTotal = QDateTime( QDate( year, month, day ),
0265                                       QTime( hour, min, secs ),
0266                                       Qt::LocalTime );
0267         m_ecl->getDatefromMJD( mjd_end, year, month, day, hour, min, secs );
0268         m_endDateTotal = QDateTime( QDate( year, month, day ),
0269                                     QTime( hour, min, secs ),
0270                                     Qt::LocalTime );
0271     }
0272 
0273     // detailed calculations are done when required
0274     m_calculationsNeedUpdate = true;
0275 }
0276 
0277 void EclipsesItem::calculate()
0278 {
0279     int np, kp, j;
0280     double lat1, lng1, lat2, lng2, lat3, lng3, lat4, lng4;
0281     double ltf[60], lnf[60];
0282 
0283     m_ecl->putEclSelect( m_index );
0284 
0285     // FIXME: set observer location
0286     m_ecl->getMaxPos( lat1, lng1 );
0287     m_ecl->setLocalPos( lat1, lng1, 0 );
0288 
0289     // eclipse's maximum location
0290     m_maxLocation = GeoDataCoordinates( lng1, lat1, 0., GeoDataCoordinates::Degree );
0291 
0292     // calculate central line
0293     np = m_ecl->eclPltCentral( true, lat1, lng1 );
0294     kp = np;
0295     m_centralLine.clear();
0296     m_centralLine << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0297                                          GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0298                                          0., GeoDataCoordinates::Degree );
0299 
0300     if( np > 3 ) { // central eclipse
0301         while( np > 3 ) {
0302             np = m_ecl->eclPltCentral( false, lat1, lng1 );
0303             if( np > 3 ) {
0304                 m_centralLine << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0305                                                      GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0306                                                      0., GeoDataCoordinates::Degree );
0307             }
0308         }
0309     }
0310 
0311     // calculate umbra
0312     np = kp;
0313     m_umbra.clear();
0314     if( np > 3 ) { // total or annual eclipse
0315         // northern /southern boundaries of umbra
0316         np = m_ecl->centralBound( true, lat1, lng1, lat2, lng2 );
0317 
0318         GeoDataLinearRing lowerUmbra( Tessellate ), upperUmbra( Tessellate );
0319         lowerUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0320                                           GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0321                                           0., GeoDataCoordinates::Degree );
0322         upperUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0323                                           GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0324                                           0., GeoDataCoordinates::Degree );
0325 
0326         while( np > 0 ) {
0327             np = m_ecl->centralBound( false, lat1, lng1, lat2, lng2 );
0328             if( lat1 <= 90. ) {
0329                 lowerUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0330                                                   GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0331                                                   0., GeoDataCoordinates::Degree );
0332             }
0333             if( lat1 <= 90. ) {
0334                 upperUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng2, GeoDataCoordinates::Degree),
0335                                                   GeoDataCoordinates::normalizeLat(lat2, GeoDataCoordinates::Degree),
0336                                                   0., GeoDataCoordinates::Degree );
0337             }
0338         }
0339 
0340         GeoDataLinearRing invertedUpperUmbra( Tessellate );
0341         QVector<GeoDataCoordinates>::const_iterator iter = upperUmbra.constEnd() - 1;
0342         for( ; iter != upperUmbra.constBegin(); --iter ) {
0343             invertedUpperUmbra << *iter;
0344         }
0345         invertedUpperUmbra << upperUmbra.first();
0346         upperUmbra = invertedUpperUmbra;
0347 
0348         m_umbra << lowerUmbra << upperUmbra;
0349     }
0350 
0351     // shadow cones
0352     m_shadowConeUmbra.clear();
0353     m_shadowConePenumbra.clear();
0354     m_shadowCone60MagPenumbra.clear();
0355 
0356     m_ecl->getLocalMax( lat2, lat3, lat4 );
0357 
0358     m_ecl->getShadowCone( lat2, true, 40, ltf, lnf );
0359     for( j = 0; j < 40; ++j ) {
0360         if( ltf[j] < 100. ) {
0361             m_shadowConeUmbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lnf[j], GeoDataCoordinates::Degree),
0362                                                      GeoDataCoordinates::normalizeLat(ltf[j], GeoDataCoordinates::Degree),
0363                                                      0., GeoDataCoordinates::Degree );
0364         }
0365     }
0366 
0367     m_ecl->setPenumbraAngle( 1., 0 );
0368     m_ecl->getShadowCone( lat2, false, 60, ltf, lnf );
0369     for( j = 0; j < 60; ++j ) {
0370         if( ltf[j] < 100. ) {
0371             m_shadowConePenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lnf[j], GeoDataCoordinates::Degree),
0372                                                         GeoDataCoordinates::normalizeLat(ltf[j], GeoDataCoordinates::Degree),
0373                                                         0., GeoDataCoordinates::Degree );
0374         }
0375     }
0376 
0377     m_ecl->setPenumbraAngle( 0.6, 1 );
0378     m_ecl->getShadowCone( lat2, false, 60, ltf, lnf );
0379     for( j = 0; j < 60; ++j ) {
0380         if( ltf[j] < 100. ) {
0381             m_shadowCone60MagPenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lnf[j], GeoDataCoordinates::Degree),
0382                                                              GeoDataCoordinates::normalizeLat(ltf[j], GeoDataCoordinates::Degree),
0383                                                              0., GeoDataCoordinates::Degree );
0384         }
0385     }
0386 
0387     m_ecl->setPenumbraAngle( 1., 0 );
0388 
0389     // eclipse boundaries
0390     m_southernPenumbra.clear();
0391     m_northernPenumbra.clear();
0392 
0393     np = m_ecl->GNSBound( true, true, lat1, lng2 );
0394     while( np > 0 ) {
0395         np = m_ecl->GNSBound( false, true, lat1, lng1 );
0396         if( ( np > 0 ) && ( lat1 <= 90. ) ) {
0397             m_southernPenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0398                                                       GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0399                                                       0., GeoDataCoordinates::Degree );
0400         }
0401     }
0402 
0403     np = m_ecl->GNSBound( true, false, lat1, lng1 );
0404     while( np > 0 ) {
0405         np = m_ecl->GNSBound( false, false, lat1, lng1 );
0406         if( ( np > 0 ) && ( lat1 <= 90. ) ) {
0407             m_northernPenumbra << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0408                                                       GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0409                                                       0., GeoDataCoordinates::Degree );
0410         }
0411     }
0412 
0413     // sunrise / sunset boundaries
0414 
0415     QList<GeoDataLinearRing*> sunBoundaries;
0416     np = m_ecl->GRSBound( true, lat1, lng1, lat3, lng3 );
0417 
0418     GeoDataLinearRing *lowerBoundary = new GeoDataLinearRing( Tessellate );
0419     *lowerBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng1, GeoDataCoordinates::Degree),
0420                                           GeoDataCoordinates::normalizeLat(lat1, GeoDataCoordinates::Degree),
0421                                           0., GeoDataCoordinates::Degree );
0422 
0423     GeoDataLinearRing *upperBoundary = new GeoDataLinearRing( Tessellate );
0424     *upperBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng3, GeoDataCoordinates::Degree),
0425                                           GeoDataCoordinates::normalizeLat(lat3, GeoDataCoordinates::Degree),
0426                                           0., GeoDataCoordinates::Degree );
0427 
0428     m_sunBoundaries.clear();
0429 
0430     while ( np > 0 ) {
0431         np = m_ecl->GRSBound( false, lat2, lng2, lat4, lng4 );
0432         bool pline = fabs( lng1 - lng2 ) < 10.; // during partial eclipses, the Rise/Set
0433                                                 // lines switch at one stage.
0434                                                 // This will prevent an ugly line between
0435                                                 // the switch points. If there is a
0436                                                 // longitude jump then add the current
0437                                                 // section to our sun boundaries collection
0438                                                 // and start a new section
0439         if ( !pline && !lowerBoundary->isEmpty() ) {
0440             sunBoundaries.prepend( lowerBoundary );
0441             lowerBoundary = new GeoDataLinearRing( Tessellate );
0442         }
0443         if ( ( np > 0 ) && ( lat2 <= 90. ) && ( lat1 <= 90. ) ) {
0444             *lowerBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng2, GeoDataCoordinates::Degree),
0445                                                   GeoDataCoordinates::normalizeLat(lat2, GeoDataCoordinates::Degree),
0446                                                   0., GeoDataCoordinates::Degree );
0447         }
0448         pline = fabs( lng3 - lng4 ) < 10.; // during partial eclipses, the Rise/Set lines
0449                                            // switch at one stage.
0450                                            // This will prevent an ugly line between the
0451                                            // switch points. If there is a longitude jump
0452                                            // then add the current section to our sun
0453                                            // boundaries collection and start a new section
0454         if ( !pline && !upperBoundary->isEmpty() ) {
0455             sunBoundaries.prepend( upperBoundary );
0456             upperBoundary = new GeoDataLinearRing( Tessellate );
0457         }
0458         if ( pline && ( np > 0 ) && ( lat4 <= 90. ) && ( lat3 <= 90. ) ) {
0459             *upperBoundary << GeoDataCoordinates( GeoDataCoordinates::normalizeLon(lng4, GeoDataCoordinates::Degree),
0460                                                   GeoDataCoordinates::normalizeLat(lat4, GeoDataCoordinates::Degree),
0461                                                   0., GeoDataCoordinates::Degree );
0462         }
0463 
0464         lng1 = lng2;
0465         lat1 = lat2;
0466         lng3 = lng4;
0467         lat3 = lat4;
0468     }
0469 
0470     if ( !lowerBoundary->isEmpty() ) {
0471         sunBoundaries.prepend(lowerBoundary);
0472     } else {
0473         delete lowerBoundary;
0474     }
0475     if ( !upperBoundary->isEmpty() ) {
0476         sunBoundaries.prepend(upperBoundary);
0477     } else {
0478         delete upperBoundary;
0479     }
0480 
0481     for ( int result = 0; result < 2; ++result ) {
0482         GeoDataLinearRing sunBoundary( Tessellate );
0483 
0484         sunBoundary = *sunBoundaries.last();
0485         sunBoundaries.pop_back();
0486 
0487         while ( sunBoundaries.size() > 0) {
0488             int closestSection = -1;
0489 
0490             // TODO: Now that MableMath is not public anymore we need a
0491             // GeoDataCoordinates::distance() method in Marble.
0492             GeoDataLineString ruler;
0493             ruler << sunBoundary.last() << sunBoundary.first();
0494             qreal closestDistance = ruler.length( 1 );
0495             int closestEnd = 0;  // 0 = start of section, 1 = end of section
0496 
0497             // Look for a section that is closest to our sunBoundary section.
0498             for ( int it = 0; it < sunBoundaries.size(); ++it ) {
0499                 GeoDataLineString distanceStartSection;
0500                 distanceStartSection << sunBoundary.last() << sunBoundaries.at( it )->first();
0501 
0502                 GeoDataLineString distanceEndSection;
0503                 distanceEndSection << sunBoundary.last() << sunBoundaries.at( it )->last();
0504                 if ( distanceStartSection.length( 1 ) < closestDistance ) {
0505                     closestDistance = distanceStartSection.length( 1 );
0506                     closestSection = it;
0507                     closestEnd = 0;
0508                 }
0509                 if ( distanceEndSection.length(1) < closestDistance ) {
0510                     closestDistance = distanceEndSection.length( 1 );
0511                     closestSection = it;
0512                     closestEnd = 1;
0513                 }
0514             }
0515 
0516             if ( closestSection == -1 ) {
0517                 // There is no other section that is closer to the end of
0518                 // our sunBoundary section than the startpoint of our
0519                 // sunBoundary itself
0520                 break;
0521             }
0522             else {
0523                 // We now concatenate the closest section to the sunBoundary.
0524                 // First we might have to invert it so that we concatenate
0525                 // the right end
0526                 if ( closestEnd == 1 ) {
0527                     // TODO: replace this with a GeoDataLinearRing::invert()
0528                     // method that needs to be added to Marble ...
0529                     GeoDataLinearRing * invertedBoundary = new GeoDataLinearRing( Tessellate );
0530                     QVector<GeoDataCoordinates>::const_iterator iter = sunBoundaries.at( closestSection )->constEnd();
0531                     --iter;
0532                     for( ; iter != sunBoundaries.at( closestSection )->constBegin(); --iter ) {
0533                         *invertedBoundary << *iter;
0534                     }
0535                     *invertedBoundary << sunBoundaries.at( closestSection )->first();
0536                     delete sunBoundaries[closestSection];
0537                     sunBoundaries[closestSection] = invertedBoundary;
0538                 }
0539                 sunBoundary << *sunBoundaries[closestSection];
0540 
0541                 // Now remove the section that we've just added from the list
0542                 delete sunBoundaries[closestSection];
0543                 sunBoundaries.removeAt( closestSection );
0544             }
0545         }
0546 
0547         m_sunBoundaries << sunBoundary;
0548 
0549         if ( sunBoundaries.size() == 0 ) break;
0550     }
0551 
0552     m_calculationsNeedUpdate = false;
0553 }
0554 
0555 } // Namespace Marble
0556 
0557 #include "moc_EclipsesItem.cpp"
0558