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