diff --git a/src/gui/AstroCalcDialog.cpp b/src/gui/AstroCalcDialog.cpp index 91aaa013898f9..86d2e866784f5 100644 --- a/src/gui/AstroCalcDialog.cpp +++ b/src/gui/AstroCalcDialog.cpp @@ -4084,50 +4084,182 @@ BesselParameters::BesselParameters(double &xdot, double &ydot, double &ddot, dou cdot = xdot+mudot*y*std::sin(d)+mudot*L*tf*std::cos(d); } -void AstroCalcDialog::saveSolarEclipseKML() +void AstroCalcDialog::generateKML(const EclipseMapData& data, const QString& dateString, QTextStream& stream) const { - // Make sure that we have circumstances of an eclipse in the table - if (ui->solareclipsecontactsTreeWidget->topLevelItemCount() == 0) - return; + using EclipseType = EclipseMapData::EclipseType; + + stream << "\n\n" << '\n'; + stream << ""+q_("Solar Eclipse")+dateString+"\n"+q_("Created by Stellarium")+"\n"; + stream << "\n"; + stream << "\n"; + stream << "\n"; + stream << "\n"; - const double currentJD = core->getJD(); // save current JD - const bool saveTopocentric = core->getUseTopocentricCoordinates(); - core->setUseTopocentricCoordinates(false); - core->update(0); - double JD = ui->solareclipsecontactsTreeWidget->topLevelItem(1)->data(SolarEclipseContactDate, Qt::UserRole).toDouble(); - // Find exact time of minimum distance between axis of lunar shadow cone to the center of Earth - JD = getJDofMinimumDistance(JD); - double JDMid = JD; - int Year, Month, Day; - StelUtils::getDateFromJulianDay(JDMid, &Year, &Month, &Day); - // Use year-month-day in the file name - QString eclipseDateStr = QString("-%1-%2-%3").arg(QString::number(Year), QString::number(Month), QString::number(Day)); - QString filter = "KML"; - filter.append(" (*.kml)"); - QString defaultFilter("(*.kml)"); - QString filePath = QFileDialog::getSaveFileName(&StelMainView::getInstance(), - q_("Save KML as..."), - QDir::homePath() + "/solareclipse"+eclipseDateStr+".kml", - filter, - &defaultFilter); - if (filePath.isEmpty()) { - core->setUseTopocentricCoordinates(saveTopocentric); - core->update(0); - return; + const auto timeStr = localeMgr->getPrintableTimeLocal(data.greatestEclipse.JD); + stream << "\n"+q_("Greatest eclipse")+" ("+timeStr+")\n\n"; + stream << data.greatestEclipse.longitude << "," << data.greatestEclipse.latitude << ",0.0\n"; + stream << "\n\n\n"; } - QFile file(filePath); - if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - core->setUseTopocentricCoordinates(saveTopocentric); - core->update(0); - QMessageBox::critical(&StelMainView::getInstance(), q_("Failed to open output file"), - q_("Failed to open output KML file: %1").arg(file.errorString()), QMessageBox::Ok); - return; + const auto timeStr = localeMgr->getPrintableTimeLocal(data.firstContactWithEarth.JD); + stream << "\n"+q_("First contact with Earth")+" ("+timeStr+")\n\n"; + stream << data.firstContactWithEarth.longitude << "," << data.firstContactWithEarth.latitude << ",0.0\n"; + stream << "\n\n\n"; } - QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + { + const auto timeStr = localeMgr->getPrintableTimeLocal(data.lastContactWithEarth.JD); + stream << "\n"+q_("Last contact with Earth")+" ("+timeStr+")\n\n"; + stream << data.lastContactWithEarth.longitude << "," << data.lastContactWithEarth.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + const auto startLinePlaceMark = [&stream](const QString& name, const EclipseMapData::EclipseType type = EclipseType::Undefined) + { + switch(type) + { + case EclipseMapData::EclipseType::Total: + stream << "\n" << name << "\n#Total\n\n1\n"; + break; + case EclipseMapData::EclipseType::Annular: + stream << "\n" << name << "\n#Annular\n\n1\n"; + break; + case EclipseMapData::EclipseType::Hybrid: + stream << "\n" << name << "\n#Hybrid\n\n1\n"; + break; + default: + stream << "\n" << name << "\n#PLimits\n\n1\n"; + break; + } + }; + + for(const auto& penumbraLimit : data.penumbraLimits) + { + startLinePlaceMark("PenumbraLimit"); + stream << "1\nabsoluto\n\n"; + for(const auto& p : penumbraLimit) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + for(const auto& riseSetLimit : data.riseSetLimits) + { + struct RiseSetLimitVisitor + { + QTextStream& stream; + std::function startLinePlaceMark; + RiseSetLimitVisitor(QTextStream& stream, const std::function& startLinePlaceMark) + : stream(stream), startLinePlaceMark(startLinePlaceMark) + { + } + void operator()(const EclipseMapData::TwoLimits& limits) const + { + // P1-P2 curve + startLinePlaceMark("RiseSetLimit", EclipseType::Undefined); + stream << "1\nabsoluto\n\n"; + for(const auto& p : limits.p12curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + + // P3-P4 curve + startLinePlaceMark("RiseSetLimit", EclipseType::Undefined); + stream << "1\nabsoluto\n\n"; + for(const auto& p : limits.p34curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + void operator()(const EclipseMapData::SingleLimit& limit) const + { + startLinePlaceMark("RiseSetLimit", EclipseType::Undefined); + stream << "1\nabsoluto\n\n"; + for(const auto& p : limit.curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + }; + std::visit(RiseSetLimitVisitor{stream, startLinePlaceMark}, riseSetLimit); + } + + for(const auto& curve : data.maxEclipseAtRiseSet) + { + startLinePlaceMark("MaxEclipseSunriseSunset"); + stream << "1\nabsoluto\n\n"; + for(const auto& p : curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + if(data.centralEclipseStart.JD > 0) + { + const auto timeStr = localeMgr->getPrintableTimeLocal(data.centralEclipseStart.JD); + stream << "\n"+q_("Central eclipse begins")+" ("+timeStr+")\n\n"; + stream << data.centralEclipseStart.longitude << "," << data.centralEclipseStart.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + if(data.centralEclipseEnd.JD > 0) + { + const auto timeStr = localeMgr->getPrintableTimeLocal(data.centralEclipseEnd.JD); + stream << "\n"+q_("Central eclipse ends")+" ("+timeStr+")\n\n"; + stream << data.centralEclipseEnd.longitude << "," << data.centralEclipseEnd.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + if(!data.centerLine.empty()) + { + startLinePlaceMark("Center line", data.eclipseType); + stream << "1\nabsoluto\n\n"; + for(const auto& p : data.centerLine) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + for(const auto& outline : data.umbraOutlines) + { + const auto timeStr = localeMgr->getPrintableTimeLocal(outline.JD); + startLinePlaceMark(timeStr, outline.eclipseType); + stream << "1\nabsoluto\n\n"; + for(const auto& p : outline.curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + for(const auto& outline : data.extremeUmbraLimit1) + { + startLinePlaceMark("Limit", outline.eclipseType); + stream << "1\nabsoluto\n\n"; + for(const auto& p : outline.curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + for(const auto& outline : data.extremeUmbraLimit2) + { + startLinePlaceMark("Limit", outline.eclipseType); + stream << "1\nabsoluto\n\n"; + for(const auto& p : outline.curve) + stream << p.longitude << "," << p.latitude << ",0.0\n"; + stream << "\n\n\n"; + } + + stream << "\n\n"; +} + +auto AstroCalcDialog::generateEclipseMap(const double JDMid) -> EclipseMapData +{ + const bool savedTopocentric = core->getUseTopocentricCoordinates(); + const double currentJD = core->getJD(); // save current JD + core->setUseTopocentricCoordinates(false); + core->update(0); + + EclipseMapData data; + bool partialEclipse = false; bool nonCentralEclipse = false; double x,y,d,tf1,tf2,L1,L2,mu; @@ -4167,59 +4299,33 @@ void AstroCalcDialog::saveSolarEclipseKML() JDP3 = getJDofContact(JDMid,false,true,true,false); } - QTextStream stream(&file); - stream << "\n\n" << '\n'; - stream << ""+q_("Solar Eclipse")+eclipseDateStr+"\n"+q_("Created by Stellarium")+"\n"; - stream << "\n"; - stream << "\n"; - stream << "\n"; - stream << "\n"; + // Generate GE + data.greatestEclipse.JD = JDMid; + SolarEclipseData(JDMid,dRatio,data.greatestEclipse.latitude,data.greatestEclipse.longitude,altitude,pathWidth,duration,magnitude); + + // Generate P1 + data.firstContactWithEarth.JD = JDP1; + SolarEclipseData(JDP1,dRatio,data.firstContactWithEarth.latitude,data.firstContactWithEarth.longitude,altitude,pathWidth,duration,magnitude); - // Plot GE - SolarEclipseData(JDMid,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); - QString eclipseTime = localeMgr->getPrintableTimeLocal(JDMid); - stream << "\n"+q_("Greatest eclipse")+" ("+eclipseTime+")\n\n"; - stream << lngDeg << "," << latDeg << ",0.0\n"; - stream << "\n\n\n"; - - // Plot P1 - SolarEclipseData(JDP1,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); - double latP1 = latDeg, lngP1 = lngDeg; - eclipseTime = localeMgr->getPrintableTimeLocal(JDP1); - stream << "\n"+q_("First contact with Earth")+" ("+eclipseTime+")\n\n"; - stream << lngDeg << "," << latDeg << ",0.0\n"; - stream << "\n\n\n"; - - // Plot P4 - SolarEclipseData(JDP4,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); - double latP4 = latDeg, lngP4 = lngDeg; - eclipseTime = localeMgr->getPrintableTimeLocal(JDP4); - stream << "\n"+q_("Last contact with Earth")+" ("+eclipseTime+")\n\n"; - stream << lngDeg << "," << latDeg << ",0.0\n"; - stream << "\n\n\n"; + // Generate P4 + data.lastContactWithEarth.JD = JDP4; + SolarEclipseData(JDP4,dRatio,data.lastContactWithEarth.latitude,data.lastContactWithEarth.longitude,altitude,pathWidth,duration,magnitude); // Northern/southern Limits of penumbra bool north = true; for (int j = 0; j < 2; j++) { if (j != 0) north = false; - stream << "\nPenumbraLimit\n#PLimits\n\n1\n"; - stream << "1\nabsoluto\n\n"; - JD = JDP1; + double JD = JDP1; int i = 0; while (JD < JDP4) { JD = JDP1 + i/1440.0; coordinates = getNSLimitofShadow(JD,north,true); if (coordinates.first <= 90.) - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; + data.penumbraLimits[j].emplace_back(coordinates.second, coordinates.first); i++; } - stream << "\n\n\n"; } // Eclipse begins/ends at sunrise/sunset curve @@ -4237,10 +4343,11 @@ void AstroCalcDialog::saveSolarEclipseKML() coordinates = getContactCoordinates(x,y,d,mu); latP2 = coordinates.first; lngP2 = coordinates.second; - stream << "\nRiseSetLimit\n#PLimits\n\n1\n"; - stream << "1\nabsoluto\n\n"; - stream << lngP1 << "," << latP1 << ",0.0\n"; - JD = JDP1; + auto& limit = data.riseSetLimits[j].emplace(); + + limit.p12curve.emplace_back(data.firstContactWithEarth.longitude, + data.firstContactWithEarth.latitude); + double JD = JDP1; int i = 0; while (JD < JDP2) { @@ -4250,13 +4357,10 @@ void AstroCalcDialog::saveSolarEclipseKML() SolarEclipseBessel(x,y,d,tf1,tf2,L1,L2,mu); coordinates = getRiseSetLineCoordinates(first,x,y,d,L1,mu); if (coordinates.first <= 90.) - { - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; - } + limit.p12curve.emplace_back(coordinates.second, coordinates.first); i++; } - stream << lngP2 << "," << latP2 << ",0.0\n"; - stream << "\n\n\n"; + limit.p12curve.emplace_back(lngP2, latP2); // P3 to P4 curve core->setJD(JDP3); @@ -4265,9 +4369,7 @@ void AstroCalcDialog::saveSolarEclipseKML() coordinates = getContactCoordinates(x,y,d,mu); latP3 = coordinates.first; lngP3 = coordinates.second; - stream << "\nRiseSetLimit\n#PLimits\n\n1\n"; - stream << "1\nabsoluto\n\n"; - stream << lngP3 << "," << latP3 << ",0.0\n"; + limit.p34curve.emplace_back(lngP3, latP3); JD = JDP3; i = 0; while (JD < JDP4) @@ -4278,13 +4380,11 @@ void AstroCalcDialog::saveSolarEclipseKML() SolarEclipseBessel(x,y,d,tf1,tf2,L1,L2,mu); coordinates = getRiseSetLineCoordinates(first,x,y,d,L1,mu); if (coordinates.first <= 90.) - { - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; - } + limit.p34curve.emplace_back(coordinates.second, coordinates.first); i++; } - stream << lngP4 << "," << latP4 << ",0.0\n"; - stream << "\n\n\n"; + limit.p34curve.emplace_back(data.lastContactWithEarth.longitude, + data.lastContactWithEarth.latitude); } } else @@ -4295,10 +4395,10 @@ void AstroCalcDialog::saveSolarEclipseKML() for (int j = 0; j < 2; j++) { if (j != 0) first = false; - stream << "\nRiseSetLimit\n#PLimits\n\n1\n"; - stream << "1\nabsoluto\n\n"; - stream << lngP1 << "," << latP1 << ",0.0\n"; - JD = JDP1; + auto& limit = data.riseSetLimits[j].emplace(); + limit.curve.emplace_back(data.firstContactWithEarth.longitude, + data.firstContactWithEarth.latitude); + double JD = JDP1; int i = 0; while (JD < JDP4) { @@ -4308,13 +4408,11 @@ void AstroCalcDialog::saveSolarEclipseKML() SolarEclipseBessel(x,y,d,tf1,tf2,L1,L2,mu); coordinates = getRiseSetLineCoordinates(first,x,y,d,L1,mu); if (coordinates.first <= 90.) - { - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; - } + limit.curve.emplace_back(coordinates.second, coordinates.first); i++; } - stream << lngP4 << "," << latP4 << ",0.0\n"; - stream << "\n\n\n"; + limit.curve.emplace_back(data.lastContactWithEarth.longitude, + data.lastContactWithEarth.latitude); } } @@ -4326,9 +4424,9 @@ void AstroCalcDialog::saveSolarEclipseKML() for (int j = 0; j < 2; j++) { if ( j!= 0) first = false; - stream << "\nMaxEclipseSunriseSunset\n#PLimits\n\n1\n"; - stream << "1\nabsoluto\n\n"; - JD = JDP1; + data.maxEclipseAtRiseSet.emplace_back(); + auto* curve = &data.maxEclipseAtRiseSet.back(); + double JD = JDP1; int i = 0, count = 0; double lat0 = 0., lon0 = 0., dlat, dlon, diff; while (JD < JDP4) @@ -4343,16 +4441,13 @@ void AstroCalcDialog::saveSolarEclipseKML() diff = std::sqrt(dlat*dlat+dlon*dlon); if (diff>10.) { - stream << "\n\n\n"; - stream << "\nMaxEclipseSunriseSunset\n#PLimits\n\n1\n"; - stream << "1\nabsoluto\n\n"; + data.maxEclipseAtRiseSet.emplace_back(); + curve = &data.maxEclipseAtRiseSet.back(); } if (!first && count == 1) startJD2 = JD; if (!first && count == 1 && bothPenumbralLimits && (startJD1 < JDMid) && (startJD2 < JDMid)) - { - stream << startLon1 << "," << startLat1 << ",0.0\n"; // connect start of part 1 to start of part 2 - } - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; + curve->emplace_back(startLon1, startLat1); // connect start of part 1 to start of part 2 + curve->emplace_back(coordinates.second, coordinates.first); if (first && bothPenumbralLimits) { endJD1 = JD; @@ -4372,10 +4467,7 @@ void AstroCalcDialog::saveSolarEclipseKML() } if (!first && bothPenumbralLimits) endJD2 = JD; if (!first && bothPenumbralLimits && (endJD1 > JDMid) && (endJD2 > JDMid)) - { - stream << endLon1 << "," << endLat1 << ",0.0\n"; // connect end of part 2 to end of part 1 - } - stream << "\n\n\n"; + curve->emplace_back(endLon1, endLat1); // connect end of part 2 to end of part 1 } if (!partialEclipse) @@ -4387,7 +4479,7 @@ void AstroCalcDialog::saveSolarEclipseKML() if (!nonCentralEclipse) { // C1 - JD = getJDofContact(JDMid,true,false,false,true); + double JD = getJDofContact(JDMid,true,false,false,true); JD = int(JD)+(int((JD-int(JD))*86400.)-1)/86400.; SolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); int steps = 0; @@ -4405,11 +4497,10 @@ void AstroCalcDialog::saveSolarEclipseKML() double latC1 = coordinates.first; double lngC1 = coordinates.second; - // Plot C1 - eclipseTime = localeMgr->getPrintableTimeLocal(JDC1); - stream << "\n"+q_("Central eclipse begins")+" ("+eclipseTime+")\n\n"; - stream << lngC1 << "," << latC1 << ",0.0\n"; - stream << "\n\n\n"; + // Generate C1 + data.centralEclipseStart.JD = JDC1; + data.centralEclipseStart.longitude = lngC1; + data.centralEclipseStart.latitude = latC1; // C2 JD = getJDofContact(JDMid,false,false,false,true); @@ -4430,11 +4521,10 @@ void AstroCalcDialog::saveSolarEclipseKML() double latC2 = coordinates.first; double lngC2 = coordinates.second; - // Plot C2 - eclipseTime = localeMgr->getPrintableTimeLocal(JDC2); - stream << "\n"+q_("Central eclipse ends")+" ("+eclipseTime+")\n\n"; - stream << lngC2 << "," << latC2 << ",0.0\n"; - stream << "\n\n\n"; + // Generate C2 + data.centralEclipseEnd.JD = JDC2; + data.centralEclipseEnd.longitude = lngC2; + data.centralEclipseEnd.latitude = latC2; // Center line JD = JDC1; @@ -4445,22 +4535,20 @@ void AstroCalcDialog::saveSolarEclipseKML() SolarEclipseData(JDC2,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); double dRatioC2 = dRatio; if (dRatioC1 >= 1. && dRatioMid >= 1. && dRatioC2 >= 1.) - stream << "\nCenter line\n#Total\n\n1\n"; + data.eclipseType = EclipseMapData::EclipseType::Total; else if (dRatioC1 < 1. && dRatioMid < 1. && dRatioC2 < 1.) - stream << "\nCenter line\n#Annular\n\n1\n"; + data.eclipseType = EclipseMapData::EclipseType::Annular; else - stream << "\nCenter line\n#Hybrid\n\n1\n"; - stream << "1\nabsoluto\n\n"; - stream << lngC1 << "," << latC1 << ",0.0\n"; + data.eclipseType = EclipseMapData::EclipseType::Hybrid; + data.centerLine.emplace_back(lngC1, latC1); while (JD+(1./1440.) < JDC2) { - JD = JDC1 + i/1440.; // plot every one minute + JD = JDC1 + i/1440.; // generate every one minute SolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); - stream << lngDeg << "," << latDeg << ",0.0\n"; + data.centerLine.emplace_back(lngDeg, latDeg); i++; } - stream << lngC2 << "," << latC2 << ",0.0\n"; - stream << "\n\n\n"; + data.centerLine.emplace_back(lngC2, latC2); } else { @@ -4477,24 +4565,24 @@ void AstroCalcDialog::saveSolarEclipseKML() // we want to draw (ant)umbral shadow on world map at exact times like 09:00, 09:10, 09:20, ... double beginJD = int(JDU1)+(10.*int(1440.*(JDU1-int(JDU1))/10.)+10.)/1440.; double endJD = int(JDU4)+(10.*int(1440.*(JDU4-int(JDU4))/10.))/1440.; - JD = beginJD; + double JD = beginJD; int i = 0; double lat0 = 0., lon0 = 0.; while (JD < endJD) { - JD = beginJD + i/144.; // plot every 10 minutes + JD = beginJD + i/144.; // generate every 10 minutes core->setJD(JD); core->update(0); SolarEclipseBessel(x,y,d,tf1,tf2,L1,L2,mu); SolarEclipseData(JD,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); double angle = 0.; bool firstPoint = false; - QString eclipseTime = localeMgr->getPrintableTimeLocal(JD); + auto& outline = data.umbraOutlines.emplace_back(); + outline.JD = JD; if (dRatio>=1.) - stream << "\n"+eclipseTime+"\n#Total\n\n1\n"; + outline.eclipseType = EclipseMapData::EclipseType::Total; else - stream << "\n"+eclipseTime+"\n#Annular\n\n1\n"; - stream << "1\nabsoluto\n\n"; + outline.eclipseType = EclipseMapData::EclipseType::Annular; int pointNumber = 0; while (pointNumber < 60) { @@ -4502,7 +4590,7 @@ void AstroCalcDialog::saveSolarEclipseKML() coordinates = getShadowOutlineCoordinates(angle,x,y,d,L2,tf2,mu); if (coordinates.first <= 90.) { - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; + outline.curve.emplace_back(coordinates.second, coordinates.first); if (!firstPoint) { lat0 = coordinates.first; @@ -4512,8 +4600,7 @@ void AstroCalcDialog::saveSolarEclipseKML() } pointNumber++; } - stream << lon0 << "," << lat0 << ",0.0\n"; // completing the circle - stream << "\n\n\n"; + outline.curve.emplace_back(lon0, lat0); // completing the circle i++; } @@ -4528,20 +4615,20 @@ void AstroCalcDialog::saveSolarEclipseKML() double dRatio,altitude,pathWidth,duration,magnitude; SolarEclipseData(JDC1,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); + auto& extremeLimit1 = data.extremeUmbraLimit1.emplace_back(); if (dRatioC1 >= 1. && dRatioMid >= 1. && dRatioC2 >= 1.) - stream << "\nLimit\n#Total\n\n1\n"; + extremeLimit1.eclipseType = EclipseMapData::EclipseType::Total; else if (dRatioC1 < 1. && dRatioMid < 1. && dRatioC2 < 1.) - stream << "\nLimit\n#Annular\n\n1\n"; + extremeLimit1.eclipseType = EclipseMapData::EclipseType::Annular; else - stream << "\nLimit\n#Hybrid\n\n1\n"; - stream << "1\nabsoluto\n\n"; + extremeLimit1.eclipseType = EclipseMapData::EclipseType::Hybrid; // 1st extreme limit at C1 if (C1a.first <= 90. || C1b.first <= 90.) { if (dRatio>=1.) - stream << C1a.second << "," << C1a.first << ",0.0\n"; + extremeLimit1.curve.emplace_back(C1a.second, C1a.first); else - stream << C1b.second << "," << C1b.first << ",0.0\n"; + extremeLimit1.curve.emplace_back(C1b.second, C1b.first); } JD = JDC1-20./1440.; i = 0; @@ -4550,7 +4637,7 @@ void AstroCalcDialog::saveSolarEclipseKML() JD = JDC1+(i-20.)/1440.; coordinates = getNSLimitofShadow(JD,true,false); if (coordinates.first <= 90.) - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; + extremeLimit1.curve.emplace_back(coordinates.second, coordinates.first); i++; } @@ -4559,27 +4646,26 @@ void AstroCalcDialog::saveSolarEclipseKML() if (C2a.first <= 90. || C2b.first <= 90.) { if (dRatio>=1.) - stream << C2a.second << "," << C2a.first << ",0.0\n"; + extremeLimit1.curve.emplace_back(C2a.second, C2a.first); else - stream << C2b.second << "," << C2b.first << ",0.0\n"; + extremeLimit1.curve.emplace_back(C2b.second, C2b.first); } - stream << "\n\n\n"; + auto& extremeLimit2 = data.extremeUmbraLimit1.emplace_back(); // 2nd extreme limit at C1 if (dRatioC1 >= 1. && dRatioMid >= 1. && dRatioC2 >= 1.) - stream << "\nLimit\n#Total\n\n1\n"; + extremeLimit2.eclipseType = EclipseMapData::EclipseType::Total; else if (dRatioC1 < 1. && dRatioMid < 1. && dRatioC2 < 1.) - stream << "\nLimit\n#Annular\n\n1\n"; + extremeLimit2.eclipseType = EclipseMapData::EclipseType::Annular; else - stream << "\nLimit\n#Hybrid\n\n1\n"; - stream << "1\nabsoluto\n\n"; + extremeLimit2.eclipseType = EclipseMapData::EclipseType::Hybrid; SolarEclipseData(JDC1,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); if (C1a.first <= 90. || C1b.first <= 90.) { if (dRatio>=1.) - stream << C1b.second << "," << C1b.first << ",0.0\n"; + extremeLimit2.curve.emplace_back(C1b.second, C1b.first); else - stream << C1a.second << "," << C1a.first << ",0.0\n"; + extremeLimit2.curve.emplace_back(C1a.second, C1a.first); } JD = JDC1-20./1440.; i = 0; @@ -4588,7 +4674,7 @@ void AstroCalcDialog::saveSolarEclipseKML() JD = JDC1+(i-20.)/1440.; coordinates = getNSLimitofShadow(JD,false,false); if (coordinates.first <= 90.) - stream << coordinates.second << "," << coordinates.first << ",0.0\n"; + extremeLimit2.curve.emplace_back(coordinates.second, coordinates.first); i++; } SolarEclipseData(JDC2,dRatio,latDeg,lngDeg,altitude,pathWidth,duration,magnitude); @@ -4596,19 +4682,65 @@ void AstroCalcDialog::saveSolarEclipseKML() if (C2a.first <= 90. || C2b.first <= 90.) { if (dRatio>=1.) - stream << C2b.second << "," << C2b.first << ",0.0\n"; + extremeLimit2.curve.emplace_back(C2b.second, C2b.first); else - stream << C2a.second << "," << C2a.first << ",0.0\n"; + extremeLimit2.curve.emplace_back(C2a.second, C2a.first); } - stream << "\n\n\n"; } - stream << "\n\n"; - file.close(); - QGuiApplication::restoreOverrideCursor(); core->setJD(currentJD); - core->setUseTopocentricCoordinates(saveTopocentric); + core->setUseTopocentricCoordinates(savedTopocentric); core->update(0); + + return data; +} + +void AstroCalcDialog::saveSolarEclipseKML() +{ + // Make sure that we have circumstances of an eclipse in the table + if (ui->solareclipsecontactsTreeWidget->topLevelItemCount() == 0) + return; + + const bool savedTopocentric = core->getUseTopocentricCoordinates(); + core->setUseTopocentricCoordinates(false); + core->update(0); + + const double eclipseJD = ui->solareclipsecontactsTreeWidget->topLevelItem(1)->data(SolarEclipseContactDate, Qt::UserRole).toDouble(); + // Find exact time of minimum distance between axis of lunar shadow cone to the center of Earth + const double JDMid = getJDofMinimumDistance(eclipseJD); + + int year, month, day; + StelUtils::getDateFromJulianDay(JDMid, &year, &month, &day); + + core->setUseTopocentricCoordinates(savedTopocentric); + core->update(0); + + // Use year-month-day in the file name + const auto eclipseDateStr = QString("-%1-%2-%3").arg(year).arg(month).arg(day); + QString filter = "KML"; + filter.append(" (*.kml)"); + QString defaultFilter("(*.kml)"); + QString filePath = QFileDialog::getSaveFileName(&StelMainView::getInstance(), + q_("Save KML as..."), + QDir::homePath() + "/solareclipse"+eclipseDateStr+".kml", + filter, + &defaultFilter); + if (filePath.isEmpty()) return; + + QFile file(filePath); + if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QMessageBox::critical(&StelMainView::getInstance(), q_("Failed to open output file"), + q_("Failed to open output KML file: %1").arg(file.errorString()), QMessageBox::Ok); + return; + } + + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + const auto data = generateEclipseMap(JDMid); + QTextStream stream(&file); + generateKML(data, eclipseDateStr, stream); + file.close(); + QGuiApplication::restoreOverrideCursor(); } QPair AstroCalcDialog::getNSLimitofShadow(double JD, bool northernLimit, bool penumbra) diff --git a/src/gui/AstroCalcDialog.hpp b/src/gui/AstroCalcDialog.hpp index b56d720b5fe3e..ece39bdec2020 100644 --- a/src/gui/AstroCalcDialog.hpp +++ b/src/gui/AstroCalcDialog.hpp @@ -23,6 +23,7 @@ #ifndef ASTROCALCDIALOG_HPP #define ASTROCALCDIALOG_HPP +#include #include #include #include @@ -76,6 +77,81 @@ class AstroCalcDialog : public StelDialog { Q_OBJECT + struct EclipseMapData + { + enum class EclipseType + { + Undefined, + Total, + Annular, + Hybrid, + }; + struct GeoPoint + { + double longitude; + double latitude; + + GeoPoint() = default; + GeoPoint(double lon, double lat) + : longitude(lon), latitude(lat) + { + } + }; + struct GeoTimePoint + { + double JD = -1; + double longitude; + double latitude; + + GeoTimePoint() = default; + GeoTimePoint(double JD, double lon, double lat) + : JD(JD), longitude(lon), latitude(lat) + { + } + }; + struct UmbraLimit + { + std::vector curve; + EclipseType eclipseType = EclipseType::Undefined; + }; + struct UmbraOutline + { + std::vector curve; + double JD; + EclipseType eclipseType = EclipseType::Undefined; + }; + GeoTimePoint greatestEclipse; + GeoTimePoint firstContactWithEarth; // AKA P1 + GeoTimePoint lastContactWithEarth; // AKA P4 + GeoTimePoint centralEclipseStart; // AKA C1 + GeoTimePoint centralEclipseEnd; // AKA C2 + + // The array elements are {northLimit, southLimit} + std::vector penumbraLimits[2]; + + // The curves in arrays are split into two lines by the computation algorithm + struct TwoLimits + { + std::vector p12curve; + std::vector p34curve; + }; + struct SingleLimit + { + std::vector curve; + }; + std::variant riseSetLimits[2]; + + // These curves appear to be split generally in multiple sections + std::vector> maxEclipseAtRiseSet; + + std::vector centerLine; + std::vector umbraOutlines; + std::vector extremeUmbraLimit1; + std::vector extremeUmbraLimit2; + + EclipseType eclipseType; + }; + public: //! Defines the number and the order of the columns in the table that lists celestial bodies positions //! @enum CPositionsColumns @@ -652,6 +728,9 @@ private slots: //! @todo Limit the width to the width of the screen *available to the window*. void updateTabBarListWidgetWidth(); + EclipseMapData generateEclipseMap(double JDMid); + void generateKML(const EclipseMapData& data, const QString& dateString, QTextStream& stream) const; + void enableAngularLimits(bool enable); QString getSelectedObjectNameI18n(StelObjectP selectedObject);