1 /*
2 * Stellarium
3 * Copyright (C) 2015 Alexander Wolf
4 * Copyright (C) 2016 Nick Fedoseev (visualization of ephemeris)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19
20 #include "StelApp.hpp"
21 #include "StelCore.hpp"
22 #include "StelModuleMgr.hpp"
23 #include "StelMovementMgr.hpp"
24 #include "StelObjectMgr.hpp"
25 #include "StelUtils.hpp"
26 #include "StelTranslator.hpp"
27 #include "StelLocaleMgr.hpp"
28 #include "StelFileMgr.hpp"
29 #include "AngleSpinBox.hpp"
30 #include "SolarSystem.hpp"
31 #include "Planet.hpp"
32 #include "NebulaMgr.hpp"
33 #include "Nebula.hpp"
34 #include "StelActionMgr.hpp"
35 #include "StelSkyCultureMgr.hpp"
36 #include "StelJsonParser.hpp"
37
38 #ifdef USE_STATIC_PLUGIN_SATELLITES
39 #include "../plugins/Satellites/src/Satellites.hpp"
40 #endif
41 #ifdef USE_STATIC_PLUGIN_QUASARS
42 #include "../plugins/Quasars/src/Quasars.hpp"
43 #endif
44 #ifdef USE_STATIC_PLUGIN_PULSARS
45 #include "../plugins/Pulsars/src/Pulsars.hpp"
46 #endif
47 #ifdef USE_STATIC_PLUGIN_EXOPLANETS
48 #include "../plugins/Exoplanets/src/Exoplanets.hpp"
49 #endif
50 #ifdef USE_STATIC_PLUGIN_NOVAE
51 #include "../plugins/Novae/src/Novae.hpp"
52 #endif
53 #ifdef USE_STATIC_PLUGIN_SUPERNOVAE
54 #include "../plugins/Supernovae/src/Supernovae.hpp"
55 #endif
56
57 #include <QFileDialog>
58 #include <QDir>
59 #include <QSortFilterProxyModel>
60 #include <QStringListModel>
61
62 #include "AstroCalcDialog.hpp"
63 #include "AstroCalcExtraEphemerisDialog.hpp"
64 #include "AstroCalcCustomStepsDialog.hpp"
65 #include "ui_astroCalcDialog.h"
66
67 #include "external/qcustomplot/qcustomplot.h"
68 #include "external/qxlsx/xlsxdocument.h"
69 #include "external/qxlsx/xlsxchartsheet.h"
70 #include "external/qxlsx/xlsxcellrange.h"
71 #include "external/qxlsx/xlsxchart.h"
72 #include "external/qxlsx/xlsxrichstring.h"
73 #include "external/qxlsx/xlsxworkbook.h"
74 using namespace QXlsx;
75
76 QVector<Ephemeris> AstroCalcDialog::EphemerisList;
77 int AstroCalcDialog::DisplayedPositionIndex = -1;
78 double AstroCalcDialog::brightLimit = 10.;
79 double AstroCalcDialog::minY = -90.;
80 double AstroCalcDialog::maxY = 90.;
81 double AstroCalcDialog::minYme = -90.;
82 double AstroCalcDialog::maxYme = 90.;
83 double AstroCalcDialog::minYsun = -90.;
84 double AstroCalcDialog::maxYsun = 90.;
85 double AstroCalcDialog::minYmoon = -90.;
86 double AstroCalcDialog::maxYmoon = 90.;
87 double AstroCalcDialog::minY1 = -1001.;
88 double AstroCalcDialog::maxY1 = 1001.;
89 double AstroCalcDialog::minY2 = -1001.;
90 double AstroCalcDialog::maxY2 = 1001.;
91 double AstroCalcDialog::transitX = -1.;
92 double AstroCalcDialog::minYld = 0.;
93 double AstroCalcDialog::maxYld = 90.;
94 double AstroCalcDialog::minYad = 0.;
95 double AstroCalcDialog::maxYad = 180.;
96 double AstroCalcDialog::minYadm = 0.;
97 double AstroCalcDialog::maxYadm = 180.;
98 double AstroCalcDialog::minYaz = 0.;
99 double AstroCalcDialog::maxYaz = 360.;
100 QString AstroCalcDialog::yAxis1Legend = "";
101 QString AstroCalcDialog::yAxis2Legend = "";
102 const QString AstroCalcDialog::dash = QChar(0x2014);
103 const QString AstroCalcDialog::delimiter(", ");
104
AstroCalcDialog(QObject * parent)105 AstroCalcDialog::AstroCalcDialog(QObject* parent)
106 : StelDialog("AstroCalc", parent)
107 , extraEphemerisDialog(Q_NULLPTR)
108 , customStepsDialog(Q_NULLPTR)
109 //, wutModel(Q_NULLPTR)
110 //, proxyModel(Q_NULLPTR)
111 , currentTimeLine(Q_NULLPTR)
112 , plotAltVsTime(false)
113 , plotAltVsTimeSun(false)
114 , plotAltVsTimeMoon(false)
115 , plotAltVsTimePositive(false)
116 , plotMonthlyElevation(false)
117 , plotMonthlyElevationPositive(false)
118 , plotDistanceGraph(false)
119 , plotAngularDistanceGraph(false)
120 , plotAziVsTime(false)
121 , altVsTimePositiveLimit(0)
122 , monthlyElevationPositiveLimit(0)
123 , graphsDuration(1)
124 , oldGraphJD(0)
125 , graphPlotNeedsRefresh(false)
126 {
127 ui = new Ui_astroCalcDialogForm;
128 core = StelApp::getInstance().getCore();
129 solarSystem = GETSTELMODULE(SolarSystem);
130 dsoMgr = GETSTELMODULE(NebulaMgr);
131 objectMgr = GETSTELMODULE(StelObjectMgr);
132 starMgr = GETSTELMODULE(StarMgr);
133 mvMgr = GETSTELMODULE(StelMovementMgr);
134 propMgr = StelApp::getInstance().getStelPropertyManager();
135 localeMgr = &StelApp::getInstance().getLocaleMgr();
136 conf = StelApp::getInstance().getSettings();
137 Q_ASSERT(ephemerisHeader.isEmpty());
138 Q_ASSERT(phenomenaHeader.isEmpty());
139 Q_ASSERT(positionsHeader.isEmpty());
140 Q_ASSERT(wutHeader.isEmpty());
141 Q_ASSERT(transitHeader.isEmpty());
142 }
143
~AstroCalcDialog()144 AstroCalcDialog::~AstroCalcDialog()
145 {
146 if (currentTimeLine)
147 {
148 currentTimeLine->stop();
149 delete currentTimeLine;
150 currentTimeLine = Q_NULLPTR;
151 }
152 delete ui;
153 delete extraEphemerisDialog;
154 delete customStepsDialog;
155 }
156
retranslate()157 void AstroCalcDialog::retranslate()
158 {
159 if (dialog)
160 {
161 ui->retranslateUi(dialog);
162 setCelestialPositionsHeaderNames();
163 setEphemerisHeaderNames();
164 setTransitHeaderNames();
165 setPhenomenaHeaderNames();
166 populateCelestialBodyList();
167 populateCelestialCategoryList();
168 populateEphemerisTimeStepsList();
169 populatePlanetList();
170 populateGroupCelestialBodyList();
171 currentCelestialPositions();
172 prepareAxesAndGraph();
173 prepareAziVsTimeAxesAndGraph();
174 populateFunctionsList();
175 prepareXVsTimeAxesAndGraph();
176 prepareMonthlyElevationAxesAndGraph();
177 prepareDistanceAxesAndGraph();
178 prepareAngularDistanceAxesAndGraph();
179 drawAltVsTimeDiagram();
180 drawAziVsTimeDiagram();
181 populateTimeIntervalsList();
182 populateWutGroups();
183 // Hack to shrink the tabs to optimal size after language change
184 // by causing the list items to be laid out again.
185 updateTabBarListWidgetWidth();
186 // TODO: make a new private function and call that here and in createDialogContent?
187 QString validDates = QString("%1 1582/10/15 - 9999/12/31").arg(q_("Gregorian dates. Valid range:"));
188 ui->dateFromDateTimeEdit->setToolTip(validDates);
189 ui->dateToDateTimeEdit->setToolTip(validDates);
190 ui->phenomenFromDateEdit->setToolTip(validDates);
191 ui->phenomenToDateEdit->setToolTip(validDates);
192 ui->transitFromDateEdit->setToolTip(validDates);
193 ui->transitToDateEdit->setToolTip(validDates);
194 }
195 }
196
createDialogContent()197 void AstroCalcDialog::createDialogContent()
198 {
199 ui->setupUi(dialog);
200
201 // Kinetic scrolling
202 kineticScrollingList << ui->celestialPositionsTreeWidget << ui->ephemerisTreeWidget << ui->phenomenaTreeWidget << ui->wutCategoryListWidget;
203 StelGui* gui= dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
204 if (gui)
205 {
206 enableKineticScrolling(gui->getFlagUseKineticScrolling());
207 connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
208 }
209
210 // Signals and slots
211 connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
212 connect(&StelApp::getInstance().getSkyCultureMgr(), SIGNAL(currentSkyCultureChanged(QString)), this, SLOT(populateCelestialNames(QString)));
213 ui->stackedWidget->setCurrentIndex(0);
214 ui->stackListWidget->setCurrentRow(0);
215 connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(close()));
216 connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
217
218 initListCelestialPositions();
219 initListPhenomena();
220 populateCelestialBodyList();
221 populateCelestialCategoryList();
222 populateEphemerisTimeStepsList();
223 populatePlanetList();
224 populateGroupCelestialBodyList();
225 // Altitude vs. Time feature
226 prepareAxesAndGraph();
227 drawCurrentTimeDiagram();
228 // Azimuth vs. Time feature
229 prepareAziVsTimeAxesAndGraph();
230 // Graphs feature
231 populateFunctionsList();
232 prepareXVsTimeAxesAndGraph();
233 // Monthly Elevation
234 prepareMonthlyElevationAxesAndGraph();
235 // WUT
236 initListWUT();
237 populateTimeIntervalsList();
238 populateWutGroups();
239 // PC
240 prepareDistanceAxesAndGraph();
241 prepareAngularDistanceAxesAndGraph();
242
243 ui->genericMarkerColor->setText("1");
244 ui->secondaryMarkerColor->setText("2");
245 ui->mercuryMarkerColor->setText(QChar(0x263F));
246 ui->venusMarkerColor->setText(QChar(0x2640));
247 ui->marsMarkerColor->setText(QChar(0x2642));
248 ui->jupiterMarkerColor->setText(QChar(0x2643));
249 ui->saturnMarkerColor->setText(QChar(0x2644));
250
251 const double JD = core->getJD() + core->getUTCOffset(core->getJD()) / 24;
252 QDateTime currentDT = StelUtils::jdToQDateTime(JD);
253 ui->dateFromDateTimeEdit->setDateTime(currentDT);
254 ui->dateToDateTimeEdit->setDateTime(currentDT.addMonths(1));
255 ui->phenomenFromDateEdit->setDateTime(currentDT);
256 ui->phenomenToDateEdit->setDateTime(currentDT.addMonths(1));
257 ui->transitFromDateEdit->setDateTime(currentDT);
258 ui->transitToDateEdit->setDateTime(currentDT.addMonths(1));
259 ui->monthlyElevationTimeInfo->setStyleSheet("font-size: 18pt; color: rgb(238, 238, 238);");
260
261 // TODO: Replace QDateTimeEdit by a new StelDateTimeEdit widget to apply full range of dates
262 // NOTE: https://github.com/Stellarium/stellarium/issues/711
263 const QDate minDate = QDate(1582, 10, 15); // QtDateTime's minimum date is 1.1.100AD, but appears to be always Gregorian.
264 QString validDates = QString("%1 1582/10/15 - 9999/12/31").arg(q_("Gregorian dates. Valid range:"));
265 ui->dateFromDateTimeEdit->setMinimumDate(minDate);
266 ui->dateFromDateTimeEdit->setToolTip(validDates);
267 ui->dateToDateTimeEdit->setMinimumDate(minDate);
268 ui->dateToDateTimeEdit->setToolTip(validDates);
269 ui->phenomenFromDateEdit->setMinimumDate(minDate);
270 ui->phenomenFromDateEdit->setToolTip(validDates);
271 ui->phenomenToDateEdit->setMinimumDate(minDate);
272 ui->phenomenToDateEdit->setToolTip(validDates);
273 ui->transitFromDateEdit->setMinimumDate(minDate);
274 ui->transitFromDateEdit->setToolTip(validDates);
275 ui->transitToDateEdit->setMinimumDate(minDate);
276 ui->transitToDateEdit->setToolTip(validDates);
277 ui->pushButtonExtraEphemerisDialog->setFixedSize(QSize(20, 20));
278 ui->pushButtonCustomStepsDialog->setFixedSize(QSize(26, 26));
279
280 // bug #1350669 (https://bugs.launchpad.net/stellarium/+bug/1350669)
281 connect(ui->celestialPositionsTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), ui->celestialPositionsTreeWidget, SLOT(repaint()));
282
283 ui->celestialMagnitudeDoubleSpinBox->setValue(conf->value("astrocalc/celestial_magnitude_limit", 6.0).toDouble());
284 connect(ui->celestialMagnitudeDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(saveCelestialPositionsMagnitudeLimit(double)));
285
286 ui->horizontalCoordinatesCheckBox->setChecked(conf->value("astrocalc/flag_horizontal_coordinates", false).toBool());
287 connect(ui->horizontalCoordinatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveCelestialPositionsHorizontalCoordinatesFlag(bool)));
288
289 connect(ui->celestialPositionsTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentCelestialPosition(QModelIndex)));
290 connect(ui->celestialPositionsUpdateButton, SIGNAL(clicked()), this, SLOT(currentCelestialPositions()));
291 connect(ui->celestialPositionsSaveButton, SIGNAL(clicked()), this, SLOT(saveCelestialPositions()));
292 connect(ui->celestialCategoryComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveCelestialPositionsCategory(int)));
293 connect(dsoMgr, SIGNAL(catalogFiltersChanged(Nebula::CatalogGroup)), this, SLOT(populateCelestialCategoryList()));
294 connect(dsoMgr, SIGNAL(catalogFiltersChanged(Nebula::CatalogGroup)), this, SLOT(currentCelestialPositions()));
295 connect(dsoMgr, SIGNAL(flagSizeLimitsUsageChanged(bool)), this, SLOT(currentCelestialPositions()));
296 connect(dsoMgr, SIGNAL(minSizeLimitChanged(double)), this, SLOT(currentCelestialPositions()));
297 connect(dsoMgr, SIGNAL(maxSizeLimitChanged(double)), this, SLOT(currentCelestialPositions()));
298
299 connectBoolProperty(ui->ephemerisShowLineCheckBox, "SolarSystem.ephemerisLineDisplayed");
300 connectBoolProperty(ui->ephemerisShowMarkersCheckBox, "SolarSystem.ephemerisMarkersDisplayed");
301 connectBoolProperty(ui->ephemerisShowDatesCheckBox, "SolarSystem.ephemerisDatesDisplayed");
302 connectBoolProperty(ui->ephemerisShowMagnitudesCheckBox, "SolarSystem.ephemerisMagnitudesDisplayed");
303 connectBoolProperty(ui->ephemerisHorizontalCoordinatesCheckBox, "SolarSystem.ephemerisHorizontalCoordinates");
304 initListEphemeris();
305 initEphemerisFlagNakedEyePlanets();
306 connect(ui->ephemerisHorizontalCoordinatesCheckBox, SIGNAL(toggled(bool)), this, SLOT(reGenerateEphemeris()));
307 connect(ui->allNakedEyePlanetsCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveEphemerisFlagNakedEyePlanets(bool)));
308 connect(ui->ephemerisPushButton, SIGNAL(clicked()), this, SLOT(generateEphemeris()));
309 connect(ui->ephemerisCleanupButton, SIGNAL(clicked()), this, SLOT(cleanupEphemeris()));
310 connect(ui->ephemerisSaveButton, SIGNAL(clicked()), this, SLOT(saveEphemeris()));
311 connect(ui->ephemerisTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentEphemeride(QModelIndex)));
312 connect(ui->ephemerisTreeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onChangedEphemerisPosition()));
313 connect(ui->ephemerisStepComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveEphemerisTimeStep(int)));
314 connect(ui->celestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveEphemerisCelestialBody(int)));
315 connect(ui->secondaryCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveEphemerisSecondaryCelestialBody(int)));
316
317 connectColorButton(ui->genericMarkerColor, "SolarSystem.ephemerisGenericMarkerColor", "color/ephemeris_generic_marker_color");
318 connectColorButton(ui->secondaryMarkerColor, "SolarSystem.ephemerisSecondaryMarkerColor", "color/ephemeris_secondary_marker_color");
319 connectColorButton(ui->selectedMarkerColor, "SolarSystem.ephemerisSelectedMarkerColor", "color/ephemeris_selected_marker_color");
320 connectColorButton(ui->mercuryMarkerColor, "SolarSystem.ephemerisMercuryMarkerColor", "color/ephemeris_mercury_marker_color");
321 connectColorButton(ui->venusMarkerColor, "SolarSystem.ephemerisVenusMarkerColor", "color/ephemeris_venus_marker_color");
322 connectColorButton(ui->marsMarkerColor, "SolarSystem.ephemerisMarsMarkerColor", "color/ephemeris_mars_marker_color");
323 connectColorButton(ui->jupiterMarkerColor, "SolarSystem.ephemerisJupiterMarkerColor", "color/ephemeris_jupiter_marker_color");
324 connectColorButton(ui->saturnMarkerColor, "SolarSystem.ephemerisSaturnMarkerColor", "color/ephemeris_saturn_marker_color");
325
326 initListTransit();
327 connect(ui->transitsCalculateButton, SIGNAL(clicked()), this, SLOT(generateTransits()));
328 connect(ui->transitsCleanupButton, SIGNAL(clicked()), this, SLOT(cleanupTransits()));
329 connect(ui->transitsSaveButton, SIGNAL(clicked()), this, SLOT(saveTransits()));
330 connect(ui->transitTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentTransit(QModelIndex)));
331 connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(setTransitCelestialBodyName()));
332
333 // Let's use DMS and decimal degrees as acceptable values for "Maximum allowed separation" input box
334 ui->allowedSeparationSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
335 ui->allowedSeparationSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
336 ui->allowedSeparationSpinBox->setMinimum(0.0, true);
337 ui->allowedSeparationSpinBox->setMaximum(20.0, true);
338 ui->allowedSeparationSpinBox->setWrapping(false);
339 ui->allowedSeparationSpinBox->setToolTip(QString("%1: %2 - %3").arg(q_("Valid range"), StelUtils::decDegToDmsStr(ui->allowedSeparationSpinBox->getMinimum(true)), StelUtils::decDegToDmsStr(ui->allowedSeparationSpinBox->getMaximum(true))));
340
341 ui->phenomenaOppositionCheckBox->setChecked(conf->value("astrocalc/flag_phenomena_opposition", false).toBool());
342 connect(ui->phenomenaOppositionCheckBox, SIGNAL(toggled(bool)), this, SLOT(savePhenomenaOppositionFlag(bool)));
343 ui->phenomenaPerihelionAphelionCheckBox->setChecked(conf->value("astrocalc/flag_phenomena_perihelion", false).toBool());
344 connect(ui->phenomenaPerihelionAphelionCheckBox, SIGNAL(toggled(bool)), this, SLOT(savePhenomenaPerihelionAphelionFlag(bool)));
345 ui->allowedSeparationSpinBox->setDegrees(conf->value("astrocalc/phenomena_angular_separation", 1.0).toDouble());
346 connect(ui->allowedSeparationSpinBox, SIGNAL(valueChanged()), this, SLOT(savePhenomenaAngularSeparation()));
347
348 connect(ui->phenomenaPushButton, SIGNAL(clicked()), this, SLOT(calculatePhenomena()));
349 connect(ui->phenomenaCleanupButton, SIGNAL(clicked()), this, SLOT(cleanupPhenomena()));
350 connect(ui->phenomenaTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectCurrentPhenomen(QModelIndex)));
351 connect(ui->phenomenaSaveButton, SIGNAL(clicked()), this, SLOT(savePhenomena()));
352 connect(ui->object1ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(savePhenomenaCelestialBody(int)));
353 connect(ui->object2ComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(savePhenomenaCelestialGroup(int)));
354
355 plotAltVsTimeSun = conf->value("astrocalc/altvstime_sun", false).toBool();
356 plotAltVsTimeMoon = conf->value("astrocalc/altvstime_moon", false).toBool();
357 plotAltVsTimePositive = conf->value("astrocalc/altvstime_positive_only", false).toBool();
358 ui->sunAltitudeCheckBox->setChecked(plotAltVsTimeSun);
359 ui->moonAltitudeCheckBox->setChecked(plotAltVsTimeMoon);
360 ui->positiveAltitudeOnlyCheckBox->setChecked(plotAltVsTimePositive);
361 ui->positiveAltitudeLimitSpinBox->setValue(conf->value("astrocalc/altvstime_positive_limit", 0).toInt());
362 connect(ui->sunAltitudeCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveAltVsTimeSunFlag(bool)));
363 connect(ui->moonAltitudeCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveAltVsTimeMoonFlag(bool)));
364 connect(ui->positiveAltitudeOnlyCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveAltVsTimePositiveFlag(bool)));
365 connect(ui->positiveAltitudeLimitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveAltVsTimePositiveLimit(int)));
366 connect(ui->altVsTimePlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverLine(QMouseEvent*)));
367 connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawAltVsTimeDiagram()));
368
369 connect(ui->altVsTimePlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(altTimeClick(QMouseEvent*)));
370 connect(ui->aziVsTimePlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(aziTimeClick(QMouseEvent*)));
371
372 connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(handleVisibleEnabled()));
373
374 connect(ui->aziVsTimePlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverAziLine(QMouseEvent*)));
375 connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawAziVsTimeDiagram()));
376
377 connect(core, SIGNAL(dateChanged()), this, SLOT(drawCurrentTimeDiagram()));
378 connect(this, SIGNAL(graphDayChanged()), this, SLOT(drawAltVsTimeDiagram()));
379 connect(this, SIGNAL(graphDayChanged()), this, SLOT(drawAziVsTimeDiagram()));
380
381 // Monthly Elevation
382 plotMonthlyElevationPositive = conf->value("astrocalc/me_positive_only", false).toBool();
383 ui->monthlyElevationPositiveCheckBox->setChecked(plotMonthlyElevationPositive);
384 ui->monthlyElevationPositiveLimitSpinBox->setValue(conf->value("astrocalc/me_positive_limit", 0).toInt());
385 ui->monthlyElevationTime->setValue(conf->value("astrocalc/me_time", 0).toInt());
386 syncMonthlyElevationTime();
387 connect(ui->monthlyElevationTime, SIGNAL(valueChanged(int)), this, SLOT(updateMonthlyElevationTime()));
388 connect(ui->monthlyElevationPositiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveMonthlyElevationPositiveFlag(bool)));
389 connect(ui->monthlyElevationPositiveLimitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveMonthlyElevationPositiveLimit(int)));
390 connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawMonthlyElevationGraph()));
391 connect(core, SIGNAL(dateChangedByYear()), this, SLOT(drawMonthlyElevationGraph()));
392
393 connect(ui->graphsCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveGraphsCelestialBody(int)));
394 connect(ui->graphsFirstComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveGraphsFirstId(int)));
395 connect(ui->graphsSecondComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveGraphsSecondId(int)));
396 graphsDuration = qBound(1,conf->value("astrocalc/graphs_duration",1).toInt() ,30);
397 ui->graphsDurationSpinBox->setValue(graphsDuration);
398 connect(ui->graphsDurationSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateGraphsDuration(int)));
399 connect(ui->drawGraphsPushButton, SIGNAL(clicked()), this, SLOT(drawXVsTimeGraphs()));
400 connect(ui->graphsPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverGraphs(QMouseEvent*)));
401 connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(updateXVsTimeGraphs()));
402
403 ui->angularDistanceLimitSpinBox->setValue(conf->value("astrocalc/angular_distance_limit", 40).toInt());
404 connect(ui->angularDistanceLimitSpinBox, SIGNAL(valueChanged(int)), this, SLOT(saveAngularDistanceLimit(int)));
405 connect(objectMgr, SIGNAL(selectedObjectChanged(StelModule::StelModuleSelectAction)), this, SLOT(drawAngularDistanceGraph()));
406 connect(core, SIGNAL(dateChanged()), this, SLOT(drawAngularDistanceGraph()));
407
408 connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(handleVisibleEnabled()));
409
410 /*
411 wutModel = new QStringListModel(this);
412 proxyModel = new QSortFilterProxyModel(ui->wutMatchingObjectsListView);
413 proxyModel->setSourceModel(wutModel);
414 proxyModel->sort(0, Qt::AscendingOrder);
415 proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
416 ui->wutMatchingObjectsListView->setModel(proxyModel);
417 */
418
419 ui->wutAngularSizeLimitCheckBox->setChecked(conf->value("astrocalc/wut_angular_limit_flag", false).toBool());
420 // Let's use DMS and decimal degrees as acceptable values for angular size limits
421 ui->wutAngularSizeLimitMinSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
422 ui->wutAngularSizeLimitMinSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
423 ui->wutAngularSizeLimitMinSpinBox->setMinimum(0.0, true);
424 ui->wutAngularSizeLimitMinSpinBox->setMaximum(10.0, true);
425 ui->wutAngularSizeLimitMinSpinBox->setWrapping(false);
426 ui->wutAngularSizeLimitMaxSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
427 ui->wutAngularSizeLimitMaxSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
428 ui->wutAngularSizeLimitMaxSpinBox->setMinimum(0.0, true);
429 ui->wutAngularSizeLimitMaxSpinBox->setMaximum(10.0, true);
430 ui->wutAngularSizeLimitMaxSpinBox->setWrapping(false);
431 ui->wutAltitudeMinSpinBox->setDisplayFormat(AngleSpinBox::DMSSymbols);
432 ui->wutAltitudeMinSpinBox->setPrefixType(AngleSpinBox::NormalPlus);
433 ui->wutAltitudeMinSpinBox->setMinimum(0.0, true);
434 ui->wutAltitudeMinSpinBox->setMaximum(90.0, true);
435 ui->wutAltitudeMinSpinBox->setWrapping(false);
436
437 // Convert from angular minutes
438 ui->wutAngularSizeLimitMinSpinBox->setDegrees(conf->value("astrocalc/wut_angular_limit_min", 10.0).toDouble()/60.0);
439 ui->wutAngularSizeLimitMaxSpinBox->setDegrees(conf->value("astrocalc/wut_angular_limit_max", 600.0).toDouble()/60.0);
440 ui->wutAltitudeMinSpinBox->setDegrees(conf->value("astrocalc/wut_altitude_min", 0.0).toDouble());
441 connect(ui->wutAngularSizeLimitCheckBox, SIGNAL(toggled(bool)), this, SLOT(saveWutAngularSizeFlag(bool)));
442 connect(ui->wutAngularSizeLimitMinSpinBox, SIGNAL(valueChanged()), this, SLOT(saveWutMinAngularSizeLimit()));
443 connect(ui->wutAngularSizeLimitMaxSpinBox, SIGNAL(valueChanged()), this, SLOT(saveWutMaxAngularSizeLimit()));
444 connect(ui->wutAltitudeMinSpinBox, SIGNAL(valueChanged()), this, SLOT(saveWutMinAltitude()));
445
446 ui->wutMagnitudeDoubleSpinBox->setValue(conf->value("astrocalc/wut_magnitude_limit", 10.0).toDouble());
447 connect(ui->wutMagnitudeDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(saveWutMagnitudeLimit(double)));
448 connect(ui->wutComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveWutTimeInterval(int)));
449 connect(ui->wutCategoryListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(calculateWutObjects()));
450 //connect(ui->wutMatchingObjectsTreeWidget->selectionModel() , SIGNAL(currentRowChanged(const QModelIndex&, const QModelIndex&)),
451 // this, SLOT(selectWutObject(const QModelIndex&)));
452 connect(ui->wutMatchingObjectsTreeWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(selectWutObject(QModelIndex)));
453 connect(ui->saveObjectsButton, SIGNAL(clicked()), this, SLOT(saveWutObjects()));
454 //connect(ui->wutMatchingObjectsLineEdit, SIGNAL(textChanged(const QString&)), proxyModel, SLOT(setFilterWildcard(const QString&)));
455 ui->wutMatchingObjectsLineEdit->setVisible(false);
456 connect(dsoMgr, SIGNAL(catalogFiltersChanged(Nebula::CatalogGroup)), this, SLOT(calculateWutObjects()));
457 connect(dsoMgr, SIGNAL(typeFiltersChanged(Nebula::TypeGroup)), this, SLOT(calculateWutObjects()));
458 connect(dsoMgr, SIGNAL(flagSizeLimitsUsageChanged(bool)), this, SLOT(calculateWutObjects()));
459 connect(dsoMgr, SIGNAL(minSizeLimitChanged(double)), this, SLOT(calculateWutObjects()));
460 connect(dsoMgr, SIGNAL(maxSizeLimitChanged(double)), this, SLOT(calculateWutObjects()));
461 connect(core, SIGNAL(dateChanged()), this, SLOT(calculateWutObjects()));
462
463 QAction *clearAction = ui->wutMatchingObjectsLineEdit->addAction(QIcon(":/graphicGui/uieBackspaceInputButton.png"), QLineEdit::ActionPosition::TrailingPosition);
464 connect(clearAction, SIGNAL(triggered()), this, SLOT(searchWutClear()));
465 StelModuleMgr& moduleMgr = StelApp::getInstance().getModuleMgr();
466 if (moduleMgr.isPluginLoaded("Quasars"))
467 {
468 #ifdef USE_STATIC_PLUGIN_QUASARS
469 Quasars* qsoMgr = GETSTELMODULE(Quasars);
470 connect(qsoMgr, SIGNAL(flagQuasarsVisibilityChanged(bool)), this, SLOT(calculateWutObjects()));
471 #endif
472 }
473 if (moduleMgr.isPluginLoaded("Pulsars"))
474 {
475 #ifdef USE_STATIC_PLUGIN_PULSARS
476 Pulsars* psrMgr = GETSTELMODULE(Pulsars);
477 connect(psrMgr, SIGNAL(flagPulsarsVisibilityChanged(bool)), this, SLOT(populateWutGroups()));
478 //connect(psrMgr, SIGNAL(flagPulsarsVisibilityChanged(bool)), this, SLOT(calculateWutObjects()));
479 #endif
480 }
481 if (moduleMgr.isPluginLoaded("Exoplanets"))
482 {
483 #ifdef USE_STATIC_PLUGIN_EXOPLANETS
484 Exoplanets* epMgr = GETSTELMODULE(Exoplanets);
485 connect(epMgr, SIGNAL(flagExoplanetsVisibilityChanged(bool)), this, SLOT(populateWutGroups()));
486 //connect(epMgr, SIGNAL(flagExoplanetsVisibilityChanged(bool)), this, SLOT(calculateWutObjects()));
487 #endif
488 }
489
490 currentCelestialPositions();
491
492 currentTimeLine = new QTimer(this);
493 connect(currentTimeLine, SIGNAL(timeout()), this, SLOT(drawCurrentTimeDiagram()));
494 connect(currentTimeLine, SIGNAL(timeout()), this, SLOT(computePlanetaryData()));
495 currentTimeLine->start(1000); // Update 'now' line position every second
496
497 connect(ui->firstCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveFirstCelestialBody(int)));
498 connect(ui->secondCelestialBodyComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(saveSecondCelestialBody(int)));
499 connect(ui->pcDistanceGraphPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverDistanceGraph(QMouseEvent*)));
500 connect(core, SIGNAL(dateChanged()), this, SLOT(drawDistanceGraph()));
501
502 connect(solarSystem, SIGNAL(solarSystemDataReloaded()), this, SLOT(updateSolarSystemData()));
503 connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(updateAstroCalcData()));
504 connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawAltVsTimeDiagram()));
505 connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawMonthlyElevationGraph()));
506 connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawDistanceGraph()));
507 connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(drawAngularDistanceGraph()));
508 connect(core, SIGNAL(locationChanged(StelLocation)), this, SLOT(initEphemerisFlagNakedEyePlanets()));
509
510 connect(ui->stackListWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(changePage(QListWidgetItem*, QListWidgetItem*)));
511 connect(ui->tabWidgetGraphs, SIGNAL(currentChanged(int)), this, SLOT(changeGraphsTab(int)));
512 connect(ui->tabWidgetPC, SIGNAL(currentChanged(int)), this, SLOT(changePCTab(int)));
513
514 connect(ui->pushButtonExtraEphemerisDialog, SIGNAL(clicked()), this, SLOT(showExtraEphemerisDialog()));
515 connect(ui->pushButtonCustomStepsDialog, SIGNAL(clicked()), this, SLOT(showCustomStepsDialog()));
516
517 updateTabBarListWidgetWidth();
518
519 ui->celestialPositionsUpdateButton->setShortcut(QKeySequence("Shift+F10"));
520 ui->ephemerisPushButton->setShortcut(QKeySequence("Shift+F10"));
521 ui->transitsCalculateButton->setShortcut(QKeySequence("Shift+F10"));
522 ui->phenomenaPushButton->setShortcut(QKeySequence("Shift+F10"));
523
524 // Let's improve visibility of the text
525 QString style = "QLabel { color: rgb(238, 238, 238); }";
526 ui->celestialPositionsTimeLabel->setStyleSheet(style);
527 ui->altVsTimeLabel->setStyleSheet(style);
528 //ui->altVsTimeTitle->setStyleSheet(style);
529 ui->aziVsTimeLabel->setStyleSheet(style);
530 //ui->aziVsTimeTitle->setStyleSheet(style);
531 ui->monthlyElevationLabel->setStyleSheet(style);
532 //ui->monthlyElevationTitle->setStyleSheet(style);
533 ui->graphsFirstLabel->setStyleSheet(style);
534 ui->graphsSecondLabel->setStyleSheet(style);
535 ui->graphsDurationLabel->setStyleSheet(style);
536 ui->graphsYearsLabel->setStyleSheet(style);
537 ui->angularDistanceNote->setStyleSheet(style);
538 ui->angularDistanceLimitLabel->setStyleSheet(style);
539 //ui->angularDistanceTitle->setStyleSheet(style);
540 ui->graphsNoteLabel->setStyleSheet(style);
541 ui->transitNotificationLabel->setStyleSheet(style);
542 style = "QCheckBox { color: rgb(238, 238, 238); }";
543 ui->sunAltitudeCheckBox->setStyleSheet(style);
544 ui->moonAltitudeCheckBox->setStyleSheet(style);
545 ui->positiveAltitudeOnlyCheckBox->setStyleSheet(style);
546 ui->monthlyElevationPositiveCheckBox->setStyleSheet(style);
547 }
548
populateCelestialNames(QString)549 void AstroCalcDialog::populateCelestialNames(QString)
550 {
551 populateCelestialBodyList();
552 populatePlanetList();
553 }
554
showExtraEphemerisDialog()555 void AstroCalcDialog::showExtraEphemerisDialog()
556 {
557 if (extraEphemerisDialog == Q_NULLPTR)
558 extraEphemerisDialog = new AstroCalcExtraEphemerisDialog();
559
560 extraEphemerisDialog->setVisible(true);
561 }
562
showCustomStepsDialog()563 void AstroCalcDialog::showCustomStepsDialog()
564 {
565 if (customStepsDialog == Q_NULLPTR)
566 customStepsDialog = new AstroCalcCustomStepsDialog();
567
568 customStepsDialog->setVisible(true);
569 }
570
searchWutClear()571 void AstroCalcDialog::searchWutClear()
572 {
573 ui->wutMatchingObjectsLineEdit->clear();
574 }
575
updateAstroCalcData()576 void AstroCalcDialog::updateAstroCalcData()
577 {
578 drawAltVsTimeDiagram();
579 populateCelestialBodyList();
580 populatePlanetList();
581 }
582
saveAltVsTimeSunFlag(bool state)583 void AstroCalcDialog::saveAltVsTimeSunFlag(bool state)
584 {
585 if (plotAltVsTimeSun!=state)
586 {
587 plotAltVsTimeSun = state;
588 conf->setValue("astrocalc/altvstime_sun", plotAltVsTimeSun);
589
590 drawAltVsTimeDiagram();
591 }
592 }
593
saveAltVsTimeMoonFlag(bool state)594 void AstroCalcDialog::saveAltVsTimeMoonFlag(bool state)
595 {
596 if (plotAltVsTimeMoon!=state)
597 {
598 plotAltVsTimeMoon = state;
599 conf->setValue("astrocalc/altvstime_moon", plotAltVsTimeMoon);
600
601 drawAltVsTimeDiagram();
602 }
603 }
604
saveAltVsTimePositiveFlag(bool state)605 void AstroCalcDialog::saveAltVsTimePositiveFlag(bool state)
606 {
607 if (plotAltVsTimePositive!=state)
608 {
609 plotAltVsTimePositive = state;
610 conf->setValue("astrocalc/altvstime_positive_only", plotAltVsTimePositive);
611
612 drawAltVsTimeDiagram();
613 }
614 }
615
saveAltVsTimePositiveLimit(int limit)616 void AstroCalcDialog::saveAltVsTimePositiveLimit(int limit)
617 {
618 if (altVsTimePositiveLimit!=limit)
619 {
620 altVsTimePositiveLimit = limit;
621 conf->setValue("astrocalc/altvstime_positive_limit", altVsTimePositiveLimit);
622 drawAltVsTimeDiagram();
623 }
624 }
625
prepareAziVsTimeAxesAndGraph()626 void AstroCalcDialog::prepareAziVsTimeAxesAndGraph()
627 {
628 QString xAxisStr = q_("Local Time");
629 QString yAxisStr = QString("%1, %2").arg(q_("Azimuth"), QChar(0x00B0));
630
631 QColor axisColor(Qt::white);
632 QPen axisPen(axisColor, 1);
633
634 ui->aziVsTimePlot->clearGraphs();
635
636 // main data: Azimuth vs. Time graph
637 ui->aziVsTimePlot->addGraph();
638 ui->aziVsTimePlot->setBackground(QBrush(QColor(86, 87, 90)));
639 ui->aziVsTimePlot->graph(0)->setPen(QPen(Qt::red, 1));
640 ui->aziVsTimePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
641 ui->aziVsTimePlot->graph(0)->rescaleAxes(true);
642
643 // additional data: Current Time Diagram
644 ui->aziVsTimePlot->addGraph();
645 ui->aziVsTimePlot->graph(1)->setPen(QPen(Qt::yellow, 1));
646 ui->aziVsTimePlot->graph(1)->setLineStyle(QCPGraph::lsLine);
647 ui->aziVsTimePlot->graph(1)->setName("[Now]");
648
649 ui->aziVsTimePlot->xAxis->setLabel(xAxisStr);
650 ui->aziVsTimePlot->yAxis->setLabel(yAxisStr);
651
652 ui->aziVsTimePlot->xAxis->setRange(43200, 129600); // 24 hours since 12h00m (range in seconds)
653 ui->aziVsTimePlot->xAxis->setScaleType(QCPAxis::stLinear);
654 ui->aziVsTimePlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
655 ui->aziVsTimePlot->xAxis->setLabelColor(axisColor);
656 ui->aziVsTimePlot->xAxis->setTickLabelColor(axisColor);
657 ui->aziVsTimePlot->xAxis->setBasePen(axisPen);
658 ui->aziVsTimePlot->xAxis->setTickPen(axisPen);
659 ui->aziVsTimePlot->xAxis->setSubTickPen(axisPen);
660 ui->aziVsTimePlot->xAxis->setDateTimeFormat("H:mm");
661 ui->aziVsTimePlot->xAxis->setDateTimeSpec(Qt::UTC); // Qt::UTC + core->getUTCOffset() give local time
662 ui->aziVsTimePlot->xAxis->setAutoTickStep(false);
663 ui->aziVsTimePlot->xAxis->setTickStep(7200); // step is 2 hours (in seconds)
664 ui->aziVsTimePlot->xAxis->setAutoSubTicks(false);
665 ui->aziVsTimePlot->xAxis->setSubTickCount(7);
666
667 ui->aziVsTimePlot->yAxis->setRange(minYaz, maxYaz);
668 ui->aziVsTimePlot->yAxis->setScaleType(QCPAxis::stLinear);
669 ui->aziVsTimePlot->yAxis->setLabelColor(axisColor);
670 ui->aziVsTimePlot->yAxis->setTickLabelColor(axisColor);
671 ui->aziVsTimePlot->yAxis->setBasePen(axisPen);
672 ui->aziVsTimePlot->yAxis->setTickPen(axisPen);
673 ui->aziVsTimePlot->yAxis->setSubTickPen(axisPen);
674 }
675
drawAziVsTimeDiagram()676 void AstroCalcDialog::drawAziVsTimeDiagram()
677 {
678 // Avoid crash!
679 if (core->getCurrentPlanet()->getEnglishName().contains("->")) // We are on the spaceship!
680 return;
681
682 // special case - plot the graph when tab is visible
683 //..
684 // we got notified about a reason to redraw the plot, but dialog was
685 // not visible. which means we must redraw when becoming visible again!
686 if (!dialog->isVisible() && plotAziVsTime)
687 {
688 graphPlotNeedsRefresh = true;
689 return;
690 }
691
692 if (!plotAziVsTime) return;
693
694 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
695
696 if (!selectedObjects.isEmpty())
697 {
698 bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
699 // X axis - time; Y axis - azimuth
700 QList<double> aX, aY;
701
702 StelObjectP selectedObject = selectedObjects[0];
703 ui->aziVsTimeTitle->setText(selectedObject->getNameI18n());
704 double currentJD = core->getJD();
705 double shift = core->getUTCOffset(currentJD) / 24.0;
706 double noon = static_cast<int>(currentJD + shift);
707 double az, alt, deg;
708 bool sign;
709
710 int step = 180;
711 int limit = 485;
712 bool isSatellite = false;
713
714 #ifdef USE_STATIC_PLUGIN_SATELLITES
715 SatelliteP sat;
716 if (selectedObject->getType() == "Satellite")
717 {
718 // get reference to satellite
719 isSatellite = true;
720 sat = GETSTELMODULE(Satellites)->getById(selectedObject->getInfoMap(core)["catalog"].toString());
721 }
722 #endif
723
724 for (int i = -5; i <= limit; i++) // 24 hours + 15 minutes in both directions
725 {
726 // A new point on the graph every 3 minutes with shift to right 12 hours
727 // to get midnight at the center of diagram (i.e. accuracy is 3 minutes)
728 double ltime = i * step + 43200;
729 aX.append(ltime);
730 double JD = noon + ltime / 86400 - shift - 0.5;
731 core->setJD(JD);
732
733 if (isSatellite)
734 {
735 #ifdef USE_STATIC_PLUGIN_SATELLITES
736 sat->update(0.0);
737 #endif
738 }
739 else
740 core->update(0.0);
741
742 StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
743 double direction = 3.; // N is zero, E is 90 degrees
744 if (useSouthAzimuth)
745 direction = 2.;
746 az = direction*M_PI - az;
747 if (az > M_PI*2)
748 az -= M_PI*2;
749 StelUtils::radToDecDeg(az, sign, deg);
750 aY.append(deg);
751 }
752 core->setJD(currentJD);
753
754 QVector<double> x = aX.toVector(), y = aY.toVector();
755 minYaz = *std::min_element(aY.begin(), aY.end()) - 2.0;
756 maxYaz = *std::max_element(aY.begin(), aY.end()) + 2.0;
757
758 prepareAziVsTimeAxesAndGraph();
759 drawCurrentTimeDiagram();
760
761 QString name = selectedObject->getNameI18n();
762 if (name.isEmpty())
763 {
764 QString otype = selectedObject->getType();
765 if (otype == "Nebula")
766 {
767 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
768 if (name.isEmpty())
769 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
770 }
771
772 if (otype == "Star" || otype=="Pulsar")
773 selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
774 }
775
776 drawTransitTimeDiagram();
777
778 ui->aziVsTimePlot->graph(0)->setData(x, y);
779 ui->aziVsTimePlot->graph(0)->setName(name);
780 ui->aziVsTimePlot->replot();
781 }
782
783 // clean up the data when selection is removed
784 if (!objectMgr->getWasSelected())
785 {
786 ui->aziVsTimePlot->graph(0)->data()->clear(); // main data: Azimuth vs. Time graph
787 ui->aziVsTimePlot->replot();
788 }
789 }
790
mouseOverAziLine(QMouseEvent * event)791 void AstroCalcDialog::mouseOverAziLine(QMouseEvent* event)
792 {
793 double x = ui->aziVsTimePlot->xAxis->pixelToCoord(event->pos().x());
794 double y = ui->aziVsTimePlot->yAxis->pixelToCoord(event->pos().y());
795
796 QCPAbstractPlottable* abstractGraph = ui->aziVsTimePlot->plottableAt(event->pos(), false);
797 QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
798
799 if (ui->aziVsTimePlot->xAxis->range().contains(x) && ui->aziVsTimePlot->yAxis->range().contains(y))
800 {
801 QString info = "";
802 if (graph)
803 {
804 double JD;
805 if (graph->name() == "[Now]")
806 {
807 JD = core->getJD();
808 info = q_("Now about %1").arg(StelUtils::jdToQDateTime(JD + core->getUTCOffset(JD)/24).toString("H:mm"));
809 }
810 else
811 {
812 JD = x / 86400.0 + static_cast<int>(core->getJD()) - 0.5;
813 QString LT = StelUtils::jdToQDateTime(JD - core->getUTCOffset(JD)).toString("H:mm");
814 if (StelApp::getInstance().getFlagShowDecimalDegrees())
815 info = QString("%1<br />%2: %3<br />%4: %5%6").arg(ui->aziVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Azimuth"), QString::number(y, 'f', 2), QChar(0x00B0));
816 else
817 info = QString("%1<br />%2: %3<br />%4: %5").arg(ui->aziVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Azimuth"), StelUtils::decDegToDmsStr(y));
818 }
819 }
820 ui->aziVsTimePlot->setToolTip(info);
821 }
822
823 ui->aziVsTimePlot->update();
824 ui->aziVsTimePlot->replot();
825 }
826
initListCelestialPositions()827 void AstroCalcDialog::initListCelestialPositions()
828 {
829 ui->celestialPositionsTreeWidget->clear();
830 ui->celestialPositionsTreeWidget->setColumnCount(CColumnCount);
831 setCelestialPositionsHeaderNames();
832 ui->celestialPositionsTreeWidget->header()->setSectionsMovable(false);
833 ui->celestialPositionsTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
834 }
835
setCelestialPositionsHeaderNames()836 void AstroCalcDialog::setCelestialPositionsHeaderNames()
837 {
838 Q_ASSERT(ui->celestialCategoryComboBox);
839 QComboBox* category = ui->celestialCategoryComboBox;
840 int celType = category->itemData(category->currentIndex()).toInt();
841
842 bool horizon = ui->horizontalCoordinatesCheckBox->isChecked();
843
844 positionsHeader.clear();
845 // TRANSLATORS: name of object
846 positionsHeader << q_("Name");
847 if (horizon)
848 {
849 // TRANSLATORS: azimuth
850 positionsHeader << q_("Azimuth");
851 // TRANSLATORS: altitude
852 positionsHeader << q_("Altitude");
853 }
854 else
855 {
856 // TRANSLATORS: right ascension
857 positionsHeader << q_("RA (J2000)");
858 // TRANSLATORS: declination
859 positionsHeader << q_("Dec (J2000)");
860 }
861 if (celType == 12 || celType == 102 || celType == 111) // check for dark nebulae
862 {
863 // TRANSLATORS: opacity
864 positionsHeader << q_("Opac.");
865 }
866 else
867 {
868 // TRANSLATORS: magnitude
869 positionsHeader << q_("Mag.");
870 }
871 // TRANSLATORS: angular size, arc-minutes
872 positionsHeader << QString("%1, '").arg(q_("A.S."));
873 if (celType == 170)
874 {
875 // TRANSLATORS: separation, arc-seconds
876 positionsHeader << QString("%1, \"").arg(q_("Sep."));
877 }
878 else if (celType == 171 || celType == 173 || celType == 174)
879 {
880 // TRANSLATORS: period, days
881 positionsHeader << QString("%1, %2").arg(q_("Per."), qc_("d", "days"));
882 }
883 else if (celType >= 200)
884 {
885 // TRANSLATORS: distance, AU
886 positionsHeader << QString("%1, %2").arg(q_("Dist."), qc_("AU", "distance, astronomical unit"));
887 }
888 else if (celType == 172)
889 {
890 // TRANSLATORS: proper motion, arc-second per year
891 positionsHeader << QString("%1, %2").arg(q_("P.M."), qc_("\"/yr", "arc-second per year"));
892 }
893 else
894 {
895 // TRANSLATORS: surface brightness
896 positionsHeader << q_("S.B.");
897 }
898 // TRANSLATORS: time of transit
899 positionsHeader << qc_("Transit", "celestial event; passage across a meridian");
900 // TRANSLATORS: elevation
901 positionsHeader << q_("Elev.");
902 // TRANSLATORS: elongation
903 positionsHeader << q_("Elong.");
904 // TRANSLATORS: type of object
905 positionsHeader << q_("Type");
906
907 ui->celestialPositionsTreeWidget->setHeaderLabels(positionsHeader);
908 adjustCelestialPositionsColumns();
909 }
910
adjustCelestialPositionsColumns()911 void AstroCalcDialog::adjustCelestialPositionsColumns()
912 {
913 // adjust the column width
914 for (int i = 0; i < CColumnCount; ++i)
915 {
916 ui->celestialPositionsTreeWidget->resizeColumnToContents(i);
917 }
918 }
919
onChangedEphemerisPosition()920 void AstroCalcDialog::onChangedEphemerisPosition()
921 {
922 DisplayedPositionIndex =ui->ephemerisTreeWidget->currentItem()->data(EphemerisRA, Qt::UserRole).toInt();
923 }
924
computeMaxElevation(StelObjectP obj)925 double AstroCalcDialog::computeMaxElevation(StelObjectP obj)
926 {
927 // NOTE: Without refraction!
928 double ra, dec, elevation;
929 const double lat = static_cast<double>(core->getCurrentLocation().latitude);
930 StelUtils::rectToSphe(&ra, &dec, obj->getEquinoxEquatorialPos(core));
931 dec *= M_180_PI;
932 if (lat>=0.)
933 {
934 // star between zenith and southern horizon, or between N pole and zenith
935 elevation = (dec<=lat) ? (90. - lat + dec) : (90. + lat - dec);
936 }
937 else
938 {
939 // star between zenith and north horizon, or between S pole and zenith
940 elevation = (dec>=lat) ? (90. + lat - dec) : (90. - lat + dec);
941 }
942 return elevation * M_PI_180;
943 }
944
populateCelestialCategoryList()945 void AstroCalcDialog::populateCelestialCategoryList()
946 {
947 Q_ASSERT(ui->celestialCategoryComboBox);
948
949 QComboBox* category = ui->celestialCategoryComboBox;
950
951 category->blockSignals(true);
952 int index = category->currentIndex();
953 QVariant selectedCategoryId = category->itemData(index);
954 category->clear();
955
956 QMap<QString,QString> map = objectMgr->objectModulesMap();
957 QMapIterator<QString,QString> it(map);
958 while(it.hasNext())
959 {
960 it.next();
961 QString key = it.key();
962 if (key.startsWith("NebulaMgr") && key.contains(":"))
963 category->addItem(q_(it.value()), key.remove("NebulaMgr:"));
964
965 if (key.startsWith("StarMgr") && key.contains(":"))
966 {
967 int kn = key.remove("StarMgr:").toInt();
968 if (kn>1 && kn<=6) // Original IDs: 2, 3, 4, 5, 6
969 category->addItem(q_(it.value()), QString::number(kn + 168)); // AstroCalc IDs: 170, 171, 172, 173, 174
970 }
971 }
972 category->addItem(q_("Deep-sky objects"), "169");
973 category->addItem(q_("Solar system objects"), "200");
974 category->addItem(q_("Solar system objects: comets"), "201");
975 category->addItem(q_("Solar system objects: minor bodies"), "202");
976 category->addItem(q_("Solar system objects: planets"), "203");
977
978 index = category->findData(selectedCategoryId, Qt::UserRole, Qt::MatchCaseSensitive);
979 if (index < 0) // read config data
980 index = category->findData(conf->value("astrocalc/celestial_category", "200").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
981
982 if (index < 0) // Unknown yet? Default step: Solar system objects
983 index = category->findData("200", Qt::UserRole, Qt::MatchCaseSensitive);
984
985 category->setCurrentIndex(index);
986 category->model()->sort(0);
987 category->blockSignals(false);
988 }
989
saveCelestialPositionsMagnitudeLimit(double mag)990 void AstroCalcDialog::saveCelestialPositionsMagnitudeLimit(double mag)
991 {
992 conf->setValue("astrocalc/celestial_magnitude_limit", QString::number(mag, 'f', 2));
993 // Refresh the celestial bodies positions table
994 currentCelestialPositions();
995 }
996
saveCelestialPositionsHorizontalCoordinatesFlag(bool b)997 void AstroCalcDialog::saveCelestialPositionsHorizontalCoordinatesFlag(bool b)
998 {
999 conf->setValue("astrocalc/flag_horizontal_coordinates", b);
1000 // Refresh the celestial bodies positions table
1001 currentCelestialPositions();
1002 }
1003
saveCelestialPositionsCategory(int index)1004 void AstroCalcDialog::saveCelestialPositionsCategory(int index)
1005 {
1006 Q_ASSERT(ui->celestialCategoryComboBox);
1007 QComboBox* category = ui->celestialCategoryComboBox;
1008 conf->setValue("astrocalc/celestial_category", category->itemData(index).toInt());
1009 // Refresh the celestial bodies positions table
1010 currentCelestialPositions();
1011 }
1012
fillCelestialPositionTable(QString objectName,QString RA,QString Dec,double magnitude,QString angularSize,QString angularSizeToolTip,QString extraData,QString extraDataToolTip,QString transitTime,QString maxElevation,QString sElongation,QString objectType)1013 void AstroCalcDialog::fillCelestialPositionTable(QString objectName, QString RA, QString Dec, double magnitude,
1014 QString angularSize, QString angularSizeToolTip, QString extraData,
1015 QString extraDataToolTip, QString transitTime, QString maxElevation,
1016 QString sElongation, QString objectType)
1017 {
1018 ACCelPosTreeWidgetItem* treeItem = new ACCelPosTreeWidgetItem(ui->celestialPositionsTreeWidget);
1019 treeItem->setText(CColumnName, objectName);
1020 treeItem->setText(CColumnRA, RA);
1021 treeItem->setTextAlignment(CColumnRA, Qt::AlignRight);
1022 treeItem->setText(CColumnDec, Dec);
1023 treeItem->setTextAlignment(CColumnDec, Qt::AlignRight);
1024 if (magnitude>90.)
1025 treeItem->setText(CColumnMagnitude, dash);
1026 else
1027 treeItem->setText(CColumnMagnitude, QString::number(magnitude, 'f', 2));
1028 treeItem->setTextAlignment(CColumnMagnitude, Qt::AlignRight);
1029 treeItem->setText(CColumnAngularSize, angularSize);
1030 treeItem->setTextAlignment(CColumnAngularSize, Qt::AlignRight);
1031 treeItem->setToolTip(CColumnAngularSize, angularSizeToolTip);
1032 treeItem->setText(CColumnExtra, extraData);
1033 treeItem->setTextAlignment(CColumnExtra, Qt::AlignRight);
1034 treeItem->setToolTip(CColumnExtra, extraDataToolTip);
1035 treeItem->setText(CColumnTransit, transitTime);
1036 treeItem->setTextAlignment(CColumnTransit, Qt::AlignRight);
1037 treeItem->setText(CColumnMaxElevation, maxElevation);
1038 treeItem->setTextAlignment(CColumnMaxElevation, Qt::AlignRight);
1039 treeItem->setToolTip(CColumnMaxElevation, q_("Elevation of object at moment of upper culmination"));
1040 treeItem->setText(CColumnElongation, sElongation);
1041 treeItem->setTextAlignment(CColumnElongation, Qt::AlignRight);
1042 treeItem->setToolTip(CColumnElongation, q_("Angular distance from the Sun at the moment of computation of position"));
1043 treeItem->setText(CColumnType, objectType);
1044 }
1045
currentCelestialPositions()1046 void AstroCalcDialog::currentCelestialPositions()
1047 {
1048 QPair<QString, QString> coordStrings;
1049
1050 initListCelestialPositions();
1051 ui->celestialPositionsTreeWidget->showColumn(CColumnAngularSize);
1052
1053 const double mag = ui->celestialMagnitudeDoubleSpinBox->value();
1054 const bool horizon = ui->horizontalCoordinatesCheckBox->isChecked();
1055 const bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
1056 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1057
1058 const double JD = core->getJD();
1059 const double utcShift = core->getUTCOffset(core->getJD()) / 24.; // Fix DST shift...
1060 ui->celestialPositionsTimeLabel->setText(q_("Positions on %1").arg(QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD))));
1061 Vec4d rts;
1062 Vec3d observerHelioPos;
1063 double angularDistance;
1064 PlanetP sun = solarSystem->getSun();
1065
1066 Q_ASSERT(ui->celestialCategoryComboBox);
1067 QComboBox* category = ui->celestialCategoryComboBox;
1068 QString celType = category->itemData(category->currentIndex()).toString();
1069 const int celTypeId = celType.toInt();
1070
1071 if (celTypeId < 170)
1072 {
1073 QString mu;
1074 if (dsoMgr->getFlagSurfaceBrightnessShortNotationUsage())
1075 {
1076 mu = QString("<sup>m</sup>/%1'").arg(QChar(0x2B1C));
1077 if (dsoMgr->getFlagSurfaceBrightnessArcsecUsage())
1078 mu = QString("<sup>m</sup>/%1\"").arg(QChar(0x2B1C));
1079 }
1080 else
1081 {
1082 mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-min"));
1083 if (dsoMgr->getFlagSurfaceBrightnessArcsecUsage())
1084 mu = QString("%1/%2<sup>2</sup>").arg(qc_("mag", "magnitude"), q_("arc-sec"));
1085 }
1086 double magOp;
1087 bool passByBrightness;
1088 QString dsoName;
1089 QString asToolTip = QString("%1, %2").arg(q_("Average angular size"), q_("arc-min"));
1090 // Deep-sky objects
1091 QList<NebulaP> celestialObjects;
1092 if (celTypeId==169)
1093 celestialObjects = dsoMgr->getAllDeepSkyObjects().toList();
1094 else
1095 celestialObjects = dsoMgr->getDeepSkyObjectsByType(celType);
1096 for (const auto& obj : qAsConst(celestialObjects))
1097 {
1098 if (celTypeId == 12 || celTypeId == 102 || celTypeId == 111) // opacity cannot be extincted
1099 magOp = static_cast<double>(obj->getVMagnitude(core));
1100 else
1101 magOp = static_cast<double>(obj->getVMagnitudeWithExtinction(core));
1102
1103 if (celTypeId==35 || (celTypeId==169 && obj->getDSOType()==Nebula::NebDn)) // Regions of the sky
1104 {
1105 passByBrightness = true;
1106 magOp = 99.;
1107 }
1108 else
1109 passByBrightness = (magOp <= mag);
1110
1111 if (obj->objectInDisplayedCatalog() && obj->objectInAllowedSizeRangeLimits() && passByBrightness && obj->isAboveRealHorizon(core))
1112 {
1113 coordStrings = getStringCoordinates(horizon ? obj->getAltAzPosAuto(core) : obj->getJ2000EquatorialPos(core), horizon, useSouthAzimuth, withDecimalDegree);
1114
1115 QString celObjName = obj->getNameI18n();
1116 QString celObjId = obj->getDSODesignation();
1117 QString elongStr;
1118 if (celObjId.isEmpty())
1119 dsoName = celObjName;
1120 else if (celObjName.isEmpty())
1121 dsoName = celObjId;
1122 else
1123 dsoName = QString("%1 (%2)").arg(celObjId, celObjName);
1124
1125 QString extra = QString::number(static_cast<double>(obj->getSurfaceBrightnessWithExtinction(core)), 'f', 2);
1126 if (extra.toFloat() > 90.f)
1127 extra = dash;
1128
1129 // Convert to arc-minutes the average angular size of deep-sky object
1130 QString angularSize = QString::number(obj->getAngularSize(core) * 120., 'f', 3);
1131 if (angularSize.toFloat() < 0.01f)
1132 angularSize = dash;
1133
1134 QString sTransit = dash;
1135 QString sMaxElevation = dash;
1136 rts = obj->getRTSTime(core);
1137 if (rts[1]>=0.)
1138 {
1139 sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(rts[1]+utcShift), true);
1140 if (withDecimalDegree)
1141 sMaxElevation = StelUtils::radToDecDegStr(computeMaxElevation(qSharedPointerCast<StelObject>(obj)), 5, false, true);
1142 else
1143 sMaxElevation = StelUtils::radToDmsPStr(computeMaxElevation(qSharedPointerCast<StelObject>(obj)), 2);
1144 }
1145
1146 angularDistance = obj->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core));
1147 if (withDecimalDegree)
1148 elongStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
1149 else
1150 elongStr = StelUtils::radToDmsStr(angularDistance, true);
1151
1152 fillCelestialPositionTable(dsoName, coordStrings.first, coordStrings.second, magOp, angularSize, asToolTip, extra, mu, sTransit, sMaxElevation, elongStr, q_(obj->getTypeString()));
1153 }
1154 }
1155 }
1156 else if (celTypeId >= 200 && celTypeId <= 203)
1157 {
1158 QString distanceInfo = q_("Planetocentric distance");
1159 if (core->getUseTopocentricCoordinates())
1160 distanceInfo = q_("Topocentric distance");
1161 QString distanceUM = qc_("AU", "distance, astronomical unit");
1162 QString sToolTip = QString("%1, %2").arg(distanceInfo, distanceUM);
1163 QString asToolTip = QString("%1, %2").arg(q_("Angular size (with rings, if any)"), q_("arc-min"));
1164 Vec3d pos;
1165 bool passByType;
1166
1167 QList<PlanetP> planets;
1168 switch (celTypeId)
1169 {
1170 case 200:
1171 case 203:
1172 planets = solarSystem->getAllPlanets();
1173 break;
1174 case 201:
1175 case 202:
1176 planets = solarSystem->getAllMinorBodies();
1177 break;
1178 }
1179
1180 for (const auto& planet : qAsConst(planets))
1181 {
1182 passByType = false;
1183
1184 switch (celTypeId)
1185 {
1186 case 200:
1187 if (planet->getPlanetType() != Planet::isUNDEFINED)
1188 passByType = true;
1189 break;
1190 case 201:
1191 if (planet->getPlanetType() == Planet::isComet)
1192 passByType = true;
1193 break;
1194 case 202:
1195 {
1196 Planet::PlanetType ptype = planet->getPlanetType();
1197 if (ptype == Planet::isAsteroid || ptype == Planet::isCubewano || ptype == Planet::isDwarfPlanet || ptype == Planet::isOCO || ptype == Planet::isPlutino || ptype == Planet::isSDO || ptype == Planet::isSednoid || ptype==Planet::isInterstellar)
1198 passByType = true;
1199 break;
1200 }
1201 case 203:
1202 if (planet->getPlanetType() == Planet::isPlanet)
1203 passByType = true;
1204 break;
1205 }
1206
1207 if (!planet->hasValidPositionalData(JD, Planet::PositionQuality::OrbitPlotting))
1208 passByType = false;
1209
1210 if (passByType && planet != core->getCurrentPlanet() && static_cast<double>(planet->getVMagnitudeWithExtinction(core)) <= mag && planet->isAboveRealHorizon(core))
1211 {
1212 pos = planet->getJ2000EquatorialPos(core);
1213
1214 if (horizon)
1215 coordStrings = getStringCoordinates(planet->getAltAzPosAuto(core), horizon, useSouthAzimuth, withDecimalDegree);
1216 else
1217 coordStrings = getStringCoordinates(pos, horizon, useSouthAzimuth, withDecimalDegree);
1218
1219 QString extra = QString::number(pos.length(), 'f', 5); // A.U.
1220
1221 // Convert to arc-seconds the angular size of Solar system object (with rings, if any)
1222 QString angularSize = QString::number(planet->getAngularSize(core) * 120., 'f', 4);
1223 if (angularSize.toFloat() < 1e-4f || planet->getPlanetType() == Planet::isComet)
1224 angularSize = dash;
1225
1226 QString sTransit = dash;
1227 QString sMaxElevation = dash;
1228 QString elongStr;
1229 rts = planet->getRTSTime(core);
1230 if (rts[1]>=0.)
1231 {
1232 sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(rts[1]+utcShift), true);
1233 if (withDecimalDegree)
1234 sMaxElevation = StelUtils::radToDecDegStr(computeMaxElevation(qSharedPointerCast<StelObject>(planet)), 5, false, true);
1235 else
1236 sMaxElevation = StelUtils::radToDmsPStr(computeMaxElevation(qSharedPointerCast<StelObject>(planet)), 2);
1237 }
1238
1239 if (planet!=sun)
1240 {
1241 angularDistance = planet->getElongation(core->getObserverHeliocentricEclipticPos());
1242 if (withDecimalDegree)
1243 elongStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
1244 else
1245 elongStr = StelUtils::radToDmsStr(angularDistance, true);
1246 }
1247 else
1248 elongStr = dash;
1249
1250 fillCelestialPositionTable(planet->getNameI18n(), coordStrings.first, coordStrings.second, planet->getVMagnitudeWithExtinction(core), angularSize, asToolTip, extra, sToolTip, sTransit, sMaxElevation, elongStr, q_(planet->getPlanetTypeString()));
1251 }
1252 }
1253 }
1254 else
1255 {
1256 // stars
1257 QString sType = q_("star");
1258 QString commonName, sToolTip = "";
1259 QList<StelACStarData> celestialObjects;
1260 if (celTypeId == 170)
1261 {
1262 // double stars
1263 celestialObjects = starMgr->getHipparcosDoubleStars();
1264 sType = q_("double star");
1265 }
1266 else if (celTypeId == 171 || celTypeId == 173 || celTypeId == 174)
1267 {
1268 // variable stars
1269 switch (celTypeId)
1270 {
1271 case 171:
1272 celestialObjects = starMgr->getHipparcosVariableStars();
1273 break;
1274 case 173:
1275 celestialObjects = starMgr->getHipparcosAlgolTypeStars();
1276 break;
1277 case 174:
1278 celestialObjects = starMgr->getHipparcosClassicalCepheidsTypeStars();
1279 break;
1280 }
1281 sType = q_("variable star");
1282 }
1283 else
1284 {
1285 // stars with high proper motion
1286 celestialObjects = starMgr->getHipparcosHighPMStars();
1287 sType = q_("star with high proper motion");
1288 }
1289
1290 for (const auto& star : qAsConst(celestialObjects))
1291 {
1292 StelObjectP obj = star.firstKey();
1293 QString extra, elongStr;
1294 if (static_cast<double>(obj->getVMagnitudeWithExtinction(core)) <= mag && obj->isAboveRealHorizon(core))
1295 {
1296 if (horizon)
1297 coordStrings = getStringCoordinates(obj->getAltAzPosAuto(core), horizon, useSouthAzimuth, withDecimalDegree);
1298 else
1299 coordStrings = getStringCoordinates(obj->getJ2000EquatorialPos(core), horizon, useSouthAzimuth, withDecimalDegree);
1300
1301 if (celTypeId == 170) // double stars
1302 {
1303 double wdsSep = static_cast<double>(star.value(obj));
1304 extra = QString::number(wdsSep, 'f', 3); // arc-seconds
1305 sToolTip = StelUtils::decDegToDmsStr(wdsSep / 3600.);
1306 }
1307 else if (celTypeId == 171 || celTypeId == 173 || celTypeId == 174) // variable stars
1308 {
1309 if (star.value(obj) > 0.f)
1310 extra = QString::number(star.value(obj), 'f', 5); // days
1311 else
1312 extra = dash;
1313 }
1314 else // stars with high proper motion
1315 extra = QString::number(star.value(obj), 'f', 5); // "/yr
1316
1317 QString sTransit = dash;
1318 QString sMaxElevation = dash;
1319 rts = obj->getRTSTime(core);
1320 if (rts[1]>=0.)
1321 {
1322 sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(rts[1]+utcShift), true);
1323 if (withDecimalDegree)
1324 sMaxElevation = StelUtils::radToDecDegStr(computeMaxElevation(obj), 5, false, true);
1325 else
1326 sMaxElevation = StelUtils::radToDmsPStr(computeMaxElevation(obj), 2);
1327 }
1328
1329 angularDistance = obj->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core));
1330 if (withDecimalDegree)
1331 elongStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
1332 else
1333 elongStr = StelUtils::radToDmsStr(angularDistance, true);
1334
1335 commonName = obj->getNameI18n();
1336 if (commonName.isEmpty())
1337 commonName = obj->getID();
1338
1339 fillCelestialPositionTable(commonName, coordStrings.first, coordStrings.second, obj->getVMagnitudeWithExtinction(core), dash, "", extra, sToolTip, sTransit, sMaxElevation, elongStr, sType);
1340 }
1341 }
1342 ui->celestialPositionsTreeWidget->hideColumn(CColumnAngularSize);
1343 }
1344
1345 adjustCelestialPositionsColumns();
1346 // sort-by-name
1347 ui->celestialPositionsTreeWidget->sortItems(CColumnName, Qt::AscendingOrder);
1348 }
1349
getStringCoordinates(const Vec3d coord,const bool horizontal,const bool southAzimuth,const bool decimalDegrees)1350 QPair<QString, QString> AstroCalcDialog::getStringCoordinates(const Vec3d coord, const bool horizontal, const bool southAzimuth, const bool decimalDegrees)
1351 {
1352 double lng, lat;
1353 QString lngStr, latStr;
1354 StelUtils::rectToSphe(&lng, &lat, coord);
1355 if (horizontal)
1356 {
1357 // Azimuth is counted in reverse sense. N is zero, E is 90 degrees
1358 lng = (southAzimuth ? 2. : 3.) * M_PI - lng;
1359 if (lng > M_PI * 2)
1360 lng -= M_PI * 2;
1361 if (decimalDegrees)
1362 {
1363 lngStr = StelUtils::radToDecDegStr(lng, 5, false, true);
1364 latStr = StelUtils::radToDecDegStr(lat, 5);
1365 }
1366 else
1367 {
1368 lngStr = StelUtils::radToDmsStr(lng, true);
1369 latStr = StelUtils::radToDmsStr(lat, true);
1370 }
1371 }
1372 else
1373 {
1374 if (decimalDegrees)
1375 {
1376 lngStr = StelUtils::radToDecDegStr(lng, 5, false, true);
1377 latStr = StelUtils::radToDecDegStr(lat, 5);
1378 }
1379 else
1380 {
1381 lngStr = StelUtils::radToHmsStr(lng);
1382 latStr = StelUtils::radToDmsStr(lat, true);
1383 }
1384 }
1385
1386 return QPair<QString, QString> (lngStr, latStr);
1387 }
1388
saveCelestialPositions()1389 void AstroCalcDialog::saveCelestialPositions()
1390 {
1391 QString filter = q_("Microsoft Excel Open XML Spreadsheet");
1392 filter.append(" (*.xlsx);;");
1393 filter.append(q_("CSV (Comma delimited)"));
1394 filter.append(" (*.csv)");
1395 QString defaultFilter("(*.xlsx)");
1396 QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
1397 q_("Save celestial positions of objects as..."),
1398 QDir::homePath() + "/positions.xlsx",
1399 filter,
1400 &defaultFilter);
1401
1402 if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
1403 saveTableAsCSV(filePath, ui->celestialPositionsTreeWidget, positionsHeader);
1404 else
1405 {
1406 int count = ui->celestialPositionsTreeWidget->topLevelItemCount();
1407 int columns = positionsHeader.size();
1408 int *width = new int[static_cast<unsigned int>(columns)];
1409 QString sData;
1410
1411 QXlsx::Document xlsx;
1412 xlsx.setDocumentProperty("title", q_("Celestial positions of objects"));
1413 xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
1414 xlsx.addSheet(ui->celestialCategoryComboBox->currentData(Qt::DisplayRole).toString(), AbstractSheet::ST_WorkSheet);
1415
1416 QXlsx::Format header;
1417 header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1418 header.setPatternBackgroundColor(Qt::yellow);
1419 header.setBorderStyle(QXlsx::Format::BorderThin);
1420 header.setBorderColor(Qt::black);
1421 header.setFontBold(true);
1422 for (int i = 0; i < columns; i++)
1423 {
1424 // Row 1: Names of columns
1425 sData = positionsHeader.at(i).trimmed();
1426 xlsx.write(1, i + 1, sData, header);
1427 width[i] = sData.size();
1428 }
1429
1430 QXlsx::Format data;
1431 data.setHorizontalAlignment(QXlsx::Format::AlignRight);
1432 for (int i = 0; i < count; i++)
1433 {
1434 for (int j = 0; j < columns; j++)
1435 {
1436 // Row 2 and next: the data
1437 sData = ui->celestialPositionsTreeWidget->topLevelItem(i)->text(j).trimmed();
1438 xlsx.write(i + 2, j + 1, sData, data);
1439 int w = sData.size();
1440 if (w > width[j])
1441 {
1442 width[j] = w;
1443 }
1444 }
1445 }
1446
1447 for (int i = 0; i < columns; i++)
1448 {
1449 xlsx.setColumnWidth(i+1, width[i]+2);
1450 }
1451
1452 delete[] width;
1453
1454 // Add the date and time info for celestial positions
1455 xlsx.write(count + 2, 1, ui->celestialPositionsTimeLabel->text());
1456 QXlsx::CellRange range = CellRange(count+2, 1, count+2, columns);
1457 QXlsx::Format extraData;
1458 extraData.setBorderStyle(QXlsx::Format::BorderThin);
1459 extraData.setBorderColor(Qt::black);
1460 extraData.setPatternBackgroundColor(Qt::yellow);
1461 extraData.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1462 xlsx.mergeCells(range, extraData);
1463
1464 xlsx.saveAs(filePath);
1465 }
1466 }
1467
selectCurrentCelestialPosition(const QModelIndex & modelIndex)1468 void AstroCalcDialog::selectCurrentCelestialPosition(const QModelIndex& modelIndex)
1469 {
1470 // Find the object
1471 QString nameI18n = modelIndex.sibling(modelIndex.row(), CColumnName).data().toString();
1472 bool found = (objectMgr->findAndSelectI18n(nameI18n) || objectMgr->findAndSelect(nameI18n));
1473
1474 if (!found)
1475 {
1476 QStringList list = nameI18n.split("(");
1477 if (list.count() > 0 && nameI18n.lastIndexOf("(") != 0 && nameI18n.lastIndexOf("/") < 0)
1478 nameI18n = list.at(0).trimmed();
1479
1480 found = (objectMgr->findAndSelectI18n(nameI18n) || objectMgr->findAndSelect(nameI18n));
1481 }
1482
1483 if (found)
1484 {
1485 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1486 if (!newSelected.empty())
1487 {
1488 mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
1489 mvMgr->setFlagTracking(true);
1490 }
1491 }
1492 }
1493
selectCurrentEphemeride(const QModelIndex & modelIndex)1494 void AstroCalcDialog::selectCurrentEphemeride(const QModelIndex& modelIndex)
1495 {
1496 // Find the object
1497 const QString name = modelIndex.sibling(modelIndex.row(), EphemerisCOName).data(Qt::UserRole).toString();
1498 const double JD = modelIndex.sibling(modelIndex.row(), EphemerisDate).data(Qt::UserRole).toDouble();
1499
1500 if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
1501 {
1502 core->setJD(JD);
1503 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
1504 if (!newSelected.empty())
1505 {
1506 // Can't point to home planet
1507 if (newSelected[0]->getEnglishName() != core->getCurrentLocation().planetName)
1508 {
1509 mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
1510 mvMgr->setFlagTracking(true);
1511 }
1512 else
1513 {
1514 GETSTELMODULE(StelObjectMgr)->unSelect();
1515 }
1516 }
1517 }
1518 }
1519
setEphemerisHeaderNames()1520 void AstroCalcDialog::setEphemerisHeaderNames()
1521 {
1522 const bool horizontalCoords = ui->ephemerisHorizontalCoordinatesCheckBox->isChecked();
1523
1524 ephemerisHeader.clear();
1525 ephemerisHeader << q_("Name");
1526 ephemerisHeader << q_("Date and Time");
1527 if (horizontalCoords)
1528 {
1529 // TRANSLATORS: azimuth
1530 ephemerisHeader << q_("Azimuth");
1531 // TRANSLATORS: altitude
1532 ephemerisHeader << q_("Altitude");
1533 }
1534 else
1535 {
1536 // TRANSLATORS: right ascension
1537 ephemerisHeader << q_("RA (J2000)");
1538 // TRANSLATORS: declination
1539 ephemerisHeader << q_("Dec (J2000)");
1540 }
1541 // TRANSLATORS: magnitude
1542 ephemerisHeader << q_("Mag.");
1543 // TRANSLATORS: phase
1544 ephemerisHeader << q_("Phase");
1545 // TRANSLATORS: distance, AU
1546 ephemerisHeader << QString("%1, %2").arg(q_("Dist."), qc_("AU", "distance, astronomical unit"));
1547 // TRANSLATORS: elongation
1548 ephemerisHeader << q_("Elong.");
1549 ui->ephemerisTreeWidget->setHeaderLabels(ephemerisHeader);
1550
1551 // adjust the column width
1552 for (int i = 0; i < EphemerisCount; ++i)
1553 {
1554 ui->ephemerisTreeWidget->resizeColumnToContents(i);
1555 }
1556 }
1557
initListEphemeris()1558 void AstroCalcDialog::initListEphemeris()
1559 {
1560 ui->ephemerisTreeWidget->clear();
1561 ui->ephemerisTreeWidget->setColumnCount(EphemerisCount);
1562 setEphemerisHeaderNames();
1563 ui->ephemerisTreeWidget->header()->setSectionsMovable(false);
1564 ui->ephemerisTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
1565 }
1566
reGenerateEphemeris()1567 void AstroCalcDialog::reGenerateEphemeris()
1568 {
1569 reGenerateEphemeris(true);
1570 }
1571
reGenerateEphemeris(bool withSelection)1572 void AstroCalcDialog::reGenerateEphemeris(bool withSelection)
1573 {
1574 if (EphemerisList.size() > 0)
1575 {
1576 int row = ui->ephemerisTreeWidget->currentIndex().row();
1577 generateEphemeris(); // Update list of ephemeris
1578 if (row>0 && withSelection)
1579 {
1580 ui->ephemerisTreeWidget->setCurrentItem(ui->ephemerisTreeWidget->topLevelItem(row), 0, QItemSelectionModel::Select | QItemSelectionModel::Rows);
1581 ui->ephemerisTreeWidget->setFocus();
1582 }
1583 }
1584 else
1585 initListEphemeris(); // Just update headers
1586 }
1587
generateEphemeris()1588 void AstroCalcDialog::generateEphemeris()
1589 {
1590 const QString currentPlanet = ui->celestialBodyComboBox->currentData(Qt::UserRole).toString();
1591 const QString secondaryPlanet = ui->secondaryCelestialBodyComboBox->currentData(Qt::UserRole).toString();
1592 const QString distanceInfo = (core->getUseTopocentricCoordinates() ? q_("Topocentric distance") : q_("Planetocentric distance"));
1593 const QString distanceUM = qc_("AU", "distance, astronomical unit");
1594 QString englishName, nameI18n, elongStr = "", phaseStr = "";
1595 const bool useHorizontalCoords = ui->ephemerisHorizontalCoordinatesCheckBox->isChecked();
1596 const bool useSouthAzimuth = StelApp::getInstance().getFlagSouthAzimuthUsage();
1597 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1598
1599 DisplayedPositionIndex = -1; // deselect an ephemeris marker
1600 initListEphemeris();
1601
1602 if (currentPlanet.isEmpty()) // avoid crash
1603 return;
1604
1605 int idxRow = 0, colorIndex = 0;
1606 double solarDay = 1.0, siderealDay = 1.0, siderealYear = 365.256363004; // days
1607 const PlanetP& cplanet = core->getCurrentPlanet();
1608 const PlanetP& sun = solarSystem->getSun();
1609 if (!cplanet->getEnglishName().contains("observer", Qt::CaseInsensitive))
1610 {
1611 if (cplanet==solarSystem->getEarth())
1612 solarDay = 1.0; // Special case: OK, it's Earth, let's use standard duration of the solar day
1613 else
1614 solarDay = cplanet->getMeanSolarDay();
1615 siderealDay = cplanet->getSiderealDay();
1616 siderealYear = cplanet->getSiderealPeriod();
1617 }
1618 const QMap<int, double>timeStepMap = {
1619 { 0, getCustomTimeStep() }, // custom time step
1620 { 1, 10. * StelCore::JD_MINUTE },
1621 { 2, 30. * StelCore::JD_MINUTE },
1622 { 3, StelCore::JD_HOUR },
1623 { 4, 6. * StelCore::JD_HOUR },
1624 { 5, 12. * StelCore::JD_HOUR },
1625 { 6, solarDay },
1626 { 7, 5. * solarDay },
1627 { 8, 10. * solarDay },
1628 { 9, 15. * solarDay },
1629 {10, 30. * solarDay },
1630 {11, 60. * solarDay },
1631 {12, StelCore::JD_DAY },
1632 {13, 5. * StelCore::JD_DAY },
1633 {14, 10. * StelCore::JD_DAY },
1634 {15, 15. * StelCore::JD_DAY },
1635 {16, 30. * StelCore::JD_DAY },
1636 {17, 60. * StelCore::JD_DAY },
1637 {18, siderealDay },
1638 {19, 5. * siderealDay },
1639 {20, 10. * siderealDay },
1640 {21, 15. * siderealDay },
1641 {22, 30. * siderealDay },
1642 {23, 60. * siderealDay },
1643 {24, 100. * solarDay },
1644 {25, 100. * siderealDay },
1645 {26, 100. * StelCore::JD_DAY },
1646 {27, siderealYear*solarDay },
1647 {28, 365.25*solarDay }, // 1 Julian year
1648 {29, 365.2568983*solarDay }, // 1 Gaussian year
1649 {30, 29.530588853*solarDay }, // 1 synodic month
1650 {31, 27.212220817*solarDay }, // 1 draconic month
1651 {32, 27.321582241*solarDay }, // 1 mean tropical month
1652 {33, 27.554549878*solarDay }, // 1 anomalistic month
1653 {34, 365.259636*solarDay }, // 1 anomalistic year
1654 {35, 6585.321314219*solarDay }, // 1 saros (223 synodic months)
1655 {36, 500. * siderealDay },
1656 {37, 500. * solarDay },
1657 {38, StelCore::JD_MINUTE }
1658 };
1659 double currentStep = timeStepMap.value(ui->ephemerisStepComboBox->currentData().toInt(), solarDay);
1660
1661 const double currentJD = core->getJD(); // save current JD
1662 double firstJD = StelUtils::qDateTimeToJd(ui->dateFromDateTimeEdit->dateTime());
1663 firstJD -= core->getUTCOffset(firstJD) / 24.;
1664 double secondJD = StelUtils::qDateTimeToJd(ui->dateToDateTimeEdit->dateTime());
1665 secondJD -= core->getUTCOffset(secondJD) / 24.;
1666 const int elements = static_cast<int>((secondJD - firstJD) / currentStep);
1667 EphemerisList.clear();
1668 const bool allNakedEyePlanets = (ui->allNakedEyePlanetsCheckBox->isChecked() && cplanet==solarSystem->getEarth());
1669
1670 QList<PlanetP> celestialObjects;
1671 Q_ASSERT(celestialObjects.isEmpty());
1672
1673 int n = 1;
1674 if (allNakedEyePlanets)
1675 {
1676 const QStringList planets = { "Mercury", "Venus", "Mars", "Jupiter", "Saturn"};
1677 for (auto planet: planets)
1678 celestialObjects.append(solarSystem->searchByEnglishName(planet));
1679 n = planets.count();
1680 }
1681 else if (secondaryPlanet!="none")
1682 {
1683 celestialObjects.append(solarSystem->searchByEnglishName(currentPlanet));
1684 celestialObjects.append(solarSystem->searchByEnglishName(secondaryPlanet));
1685 n = 2;
1686 }
1687 else
1688 celestialObjects.append(solarSystem->searchByEnglishName(currentPlanet));
1689
1690 EphemerisList.reserve(elements*n);
1691 propMgr->setStelPropertyValue("SolarSystem.ephemerisDataLimit", n);
1692
1693 for (auto obj: celestialObjects)
1694 {
1695 englishName = obj->getEnglishName();
1696 nameI18n = obj->getNameI18n();
1697
1698 if (allNakedEyePlanets && cplanet==solarSystem->getEarth())
1699 {
1700 if (englishName.contains("Mercury", Qt::CaseInsensitive))
1701 colorIndex = 2;
1702 else if (englishName.contains("Venus", Qt::CaseInsensitive))
1703 colorIndex = 3;
1704 else if (englishName.contains("Mars", Qt::CaseInsensitive))
1705 colorIndex = 4;
1706 else if (englishName.contains("Jupiter", Qt::CaseInsensitive))
1707 colorIndex = 5;
1708 else if (englishName.contains("Saturn", Qt::CaseInsensitive))
1709 colorIndex = 6;
1710 else
1711 colorIndex = 0;
1712 }
1713 else if (secondaryPlanet!="none")
1714 {
1715 colorIndex = (secondaryPlanet==englishName ? 1 : 0);
1716 }
1717
1718 if (obj == solarSystem->getSun())
1719 {
1720 phaseStr = dash;
1721 elongStr = dash;
1722 }
1723
1724 for (int i = 0; i <= elements; i++)
1725 {
1726 Vec3d pos, sunPos;
1727 double JD = firstJD + i * currentStep;
1728 core->setJD(JD);
1729 core->update(0); // force update to get new coordinates
1730
1731 if (!obj->hasValidPositionalData(JD, Planet::PositionQuality::OrbitPlotting))
1732 continue;
1733
1734 if (useHorizontalCoords)
1735 {
1736 pos = obj->getAltAzPosAuto(core);
1737 sunPos = sun->getAltAzPosAuto(core);
1738 }
1739 else
1740 {
1741 pos = obj->getJ2000EquatorialPos(core);
1742 sunPos = sun->getJ2000EquatorialPos(core);
1743 }
1744 QPair<QString, QString> coordStrings = getStringCoordinates(pos, useHorizontalCoords, useSouthAzimuth, withDecimalDegree);
1745
1746 Ephemeris item;
1747 item.coord = pos;
1748 item.sunCoord = sunPos;
1749 item.colorIndex = colorIndex;
1750 item.objDate = JD;
1751 item.magnitude = obj->getVMagnitudeWithExtinction(core);
1752 item.isComet = obj->getPlanetType()==Planet::isComet;
1753 EphemerisList.append(item);
1754
1755 Vec3d observerHelioPos = core->getObserverHeliocentricEclipticPos();
1756 if (phaseStr != dash)
1757 phaseStr = QString("%1%").arg(QString::number(obj->getPhase(observerHelioPos) * 100, 'f', 2));
1758
1759 if (elongStr != dash)
1760 {
1761 if (withDecimalDegree)
1762 elongStr = StelUtils::radToDecDegStr(obj->getElongation(observerHelioPos), 5, false, true);
1763 else
1764 elongStr = StelUtils::radToDmsStr(obj->getElongation(observerHelioPos), true);
1765 }
1766
1767 ACEphemTreeWidgetItem* treeItem = new ACEphemTreeWidgetItem(ui->ephemerisTreeWidget);
1768 treeItem->setText(EphemerisCOName, nameI18n);
1769 treeItem->setData(EphemerisCOName, Qt::UserRole, englishName);
1770 treeItem->setText(EphemerisDate, QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD))); // local date and time
1771 treeItem->setData(EphemerisDate, Qt::UserRole, JD);
1772 treeItem->setText(EphemerisRA, coordStrings.first);
1773 treeItem->setData(EphemerisRA, Qt::UserRole, idxRow);
1774 treeItem->setTextAlignment(EphemerisRA, Qt::AlignRight);
1775 treeItem->setText(EphemerisDec, coordStrings.second);
1776 treeItem->setTextAlignment(EphemerisDec, Qt::AlignRight);
1777 treeItem->setText(EphemerisMagnitude, QString::number(obj->getVMagnitudeWithExtinction(core), 'f', 2));
1778 treeItem->setTextAlignment(EphemerisMagnitude, Qt::AlignRight);
1779 treeItem->setText(EphemerisPhase, phaseStr);
1780 treeItem->setTextAlignment(EphemerisPhase, Qt::AlignRight);
1781 treeItem->setText(EphemerisDistance, QString::number(obj->getJ2000EquatorialPos(core).length(), 'f', 6));
1782 treeItem->setTextAlignment(EphemerisDistance, Qt::AlignRight);
1783 treeItem->setToolTip(EphemerisDistance, QString("%1, %2").arg(distanceInfo, distanceUM));
1784 treeItem->setText(EphemerisElongation, elongStr);
1785 treeItem->setTextAlignment(EphemerisElongation, Qt::AlignRight);
1786
1787 idxRow++;
1788 }
1789 }
1790 core->setJD(currentJD); // restore time
1791
1792 // adjust the column width
1793 for (int i = 0; i < EphemerisCount; ++i)
1794 {
1795 ui->ephemerisTreeWidget->resizeColumnToContents(i);
1796 }
1797
1798 // sort-by-date
1799 ui->ephemerisTreeWidget->sortItems(EphemerisDate, Qt::AscendingOrder);
1800
1801 emit solarSystem->requestEphemerisVisualization();
1802 }
1803
getCustomTimeStep()1804 double AstroCalcDialog::getCustomTimeStep()
1805 {
1806 double solarDay = 1.0, siderealDay = 1.0, siderealYear = 365.256363004; // days
1807 const PlanetP& cplanet = core->getCurrentPlanet();
1808 if (!cplanet->getEnglishName().contains("observer", Qt::CaseInsensitive))
1809 {
1810 if (cplanet==solarSystem->getEarth())
1811 solarDay = 1.0; // Special case: OK, it's Earth, let's use standard duration of the solar day
1812 else
1813 solarDay = cplanet->getMeanSolarDay();
1814 siderealDay = cplanet->getSiderealDay();
1815 siderealYear = cplanet->getSiderealPeriod();
1816 }
1817 const double timeStep = conf->value("astrocalc/custom_time_step", 1.0).toDouble();
1818 // NOTE: Sync units with AstroCalcCustomStepsDialog::populateUnitMeasurementsList()!
1819 const int customTimeStepKey=conf->value("astrocalc/custom_time_step_unit", 3).toInt();
1820 const QMap<int, double>customTimeStepMap={
1821 { 1, StelCore::JD_MINUTE}, // minutes
1822 { 2, StelCore::JD_HOUR}, // hours
1823 { 3, solarDay}, // solar days
1824 { 4, siderealDay}, // sidereal days
1825 { 5, StelCore::JD_DAY}, // Julian days
1826 { 6, 29.530588853*solarDay}, // synodic months
1827 { 7, 27.212220817*solarDay}, // draconic months
1828 { 8, 27.321582241*solarDay}, // mean tropical months
1829 { 9, 27.554549878*solarDay}, // anomalistic months
1830 {10, siderealYear}, // sidereal years
1831 {11, 365.25*solarDay}, // Julian years
1832 {12, 365.2568983*solarDay}, // Gaussian years
1833 {13, 365.259636*solarDay}, // Anomalistic years
1834 {14, 6585.321314219*solarDay}}; // 1 saros (223 synodic months)
1835 return timeStep*customTimeStepMap.value(customTimeStepKey);
1836 }
1837
saveEphemeris()1838 void AstroCalcDialog::saveEphemeris()
1839 {
1840 QString filter = q_("Microsoft Excel Open XML Spreadsheet");
1841 filter.append(" (*.xlsx);;");
1842 filter.append(q_("CSV (Comma delimited)"));
1843 filter.append(" (*.csv)");
1844 QString defaultFilter("(*.xlsx)");
1845 QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
1846 q_("Save calculated ephemeris as..."),
1847 QDir::homePath() + "/ephemeris.xlsx",
1848 filter,
1849 &defaultFilter);
1850
1851 if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
1852 saveTableAsCSV(filePath, ui->ephemerisTreeWidget, ephemerisHeader);
1853 else
1854 {
1855 int count = ui->ephemerisTreeWidget->topLevelItemCount();
1856 int columns = ephemerisHeader.size();
1857 int *width = new int[static_cast<unsigned int>(columns)];
1858 QString sData;
1859
1860 QXlsx::Document xlsx;
1861 xlsx.setDocumentProperty("title", q_("Ephemeris"));
1862 xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
1863 xlsx.addSheet(ui->celestialBodyComboBox->currentData(Qt::DisplayRole).toString(), AbstractSheet::ST_WorkSheet);
1864
1865 QXlsx::Format header;
1866 header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
1867 header.setPatternBackgroundColor(Qt::yellow);
1868 header.setBorderStyle(QXlsx::Format::BorderThin);
1869 header.setBorderColor(Qt::black);
1870 header.setFontBold(true);
1871 for (int i = 0; i < columns; i++)
1872 {
1873 // Row 1: Names of columns
1874 sData = ephemerisHeader.at(i).trimmed();
1875 xlsx.write(1, i + 1, sData, header);
1876 width[i] = sData.size();
1877 }
1878
1879 QXlsx::Format data;
1880 data.setHorizontalAlignment(QXlsx::Format::AlignRight);
1881 for (int i = 0; i < count; i++)
1882 {
1883 for (int j = 0; j < columns; j++)
1884 {
1885 // Row 2 and next: the data
1886 sData = ui->ephemerisTreeWidget->topLevelItem(i)->text(j).trimmed();
1887 xlsx.write(i + 2, j + 1, sData, data);
1888 int w = sData.size();
1889 if (w > width[j])
1890 {
1891 width[j] = w;
1892 }
1893 }
1894 }
1895
1896 for (int i = 0; i < columns; i++)
1897 {
1898 xlsx.setColumnWidth(i+1, width[i]+2);
1899 }
1900
1901 delete[] width;
1902 xlsx.saveAs(filePath);
1903 }
1904 }
1905
cleanupEphemeris()1906 void AstroCalcDialog::cleanupEphemeris()
1907 {
1908 EphemerisList.clear();
1909 ui->ephemerisTreeWidget->clear();
1910 }
1911
setTransitHeaderNames()1912 void AstroCalcDialog::setTransitHeaderNames()
1913 {
1914 transitHeader.clear();
1915 transitHeader << q_("Name");
1916 transitHeader << q_("Date and Time");
1917 // TRANSLATORS: altitude
1918 transitHeader << q_("Altitude");
1919 // TRANSLATORS: magnitude
1920 transitHeader << q_("Mag.");
1921 transitHeader << q_("Solar Elongation");
1922 transitHeader << q_("Lunar Elongation");
1923 ui->transitTreeWidget->setHeaderLabels(transitHeader);
1924
1925 // adjust the column width
1926 for (int i = 0; i < TransitCount; ++i)
1927 {
1928 ui->transitTreeWidget->resizeColumnToContents(i);
1929 }
1930 }
1931
initListTransit()1932 void AstroCalcDialog::initListTransit()
1933 {
1934 ui->transitTreeWidget->clear();
1935 ui->transitTreeWidget->setColumnCount(TransitCount);
1936 setTransitHeaderNames();
1937 ui->transitTreeWidget->header()->setSectionsMovable(false);
1938 ui->transitTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
1939 }
1940
generateTransits()1941 void AstroCalcDialog::generateTransits()
1942 {
1943 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
1944 if (!selectedObjects.isEmpty())
1945 {
1946 QString name, englishName;
1947 StelObjectP selectedObject = selectedObjects[0];
1948 name = ui->transitCelestialBodyNameLabel->text();
1949 selectedObject->getEnglishName().isEmpty() ? englishName = name : englishName = selectedObject->getEnglishName();
1950 //const bool isPlanet = (selectedObject->getType() == "Planet");
1951
1952 if (!name.isEmpty()) // OK, let's calculate!
1953 {
1954 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
1955
1956 initListTransit();
1957
1958 double currentStep = 1.0;
1959 const PlanetP& planet = core->getCurrentPlanet();
1960 const PlanetP sun = solarSystem->getSun();
1961 const PlanetP moon = solarSystem->getMoon();
1962 const PlanetP earth = solarSystem->getEarth();
1963 if (!planet->getEnglishName().contains("observer", Qt::CaseInsensitive))
1964 {
1965 if (planet==earth)
1966 currentStep = 1.0; // Special case: OK, it's Earth, let's use standard duration of the solar day
1967 else
1968 currentStep = planet->getMeanSolarDay();
1969 }
1970
1971 const double currentJD = core->getJD(); // save current JD
1972 double startJD = StelUtils::qDateTimeToJd(QDateTime(ui->transitFromDateEdit->date()));
1973 double stopJD = StelUtils::qDateTimeToJd(QDateTime(ui->transitToDateEdit->date()));
1974 startJD = startJD - core->getUTCOffset(startJD) / 24.;
1975 stopJD = stopJD - core->getUTCOffset(stopJD) / 24.;
1976 int elements = static_cast<int>((stopJD - startJD) / currentStep);
1977 double JD, /*UTCshift,*/ az, alt;
1978 float magnitude;
1979 QString altStr, magStr, elongSStr = dash, elongLStr =dash;
1980 for (int i = 0; i <= elements; i++)
1981 {
1982 JD = startJD + i * currentStep;
1983 core->setJD(JD);
1984 core->update(0); // force update to get new coordinates
1985 // UTCshift = core->getUTCOffset(JD) / 24.; // Fix DST shift...
1986 Vec4d rts = selectedObject->getRTSTime(core);
1987 JD = rts[1]; // static_cast<int>(JD) + 0.5 + rts[1]/24. - UTCshift;
1988 core->setJD(JD);
1989 core->update(0); // force update to get new coordinates
1990
1991 StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
1992 if (withDecimalDegree)
1993 {
1994 altStr = StelUtils::radToDecDegStr(alt, 5, false, true);
1995 elongSStr = (selectedObject==sun) ? dash : StelUtils::radToDecDegStr(selectedObject->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core)), 5, false, true);
1996 elongLStr = (selectedObject==moon && planet==earth) ? dash : StelUtils::radToDecDegStr(selectedObject->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core)), 5, false, true);
1997 }
1998 else
1999 {
2000 altStr = StelUtils::radToDmsStr(alt, true);
2001 elongSStr = (selectedObject==sun) ? dash : StelUtils::radToDmsStr(selectedObject->getJ2000EquatorialPos(core).angle(sun->getJ2000EquatorialPos(core)), true);
2002 elongLStr = (selectedObject==moon && planet==earth) ? dash : StelUtils::radToDmsStr(selectedObject->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core)), true);
2003 }
2004 magnitude = selectedObject->getVMagnitudeWithExtinction(core);
2005 magStr = (magnitude > 50.f || selectedObject->getEnglishName().contains("marker", Qt::CaseInsensitive)? dash : QString::number(magnitude, 'f', 2));
2006
2007 ACTransitTreeWidgetItem* treeItem = new ACTransitTreeWidgetItem(ui->transitTreeWidget);
2008 treeItem->setText(TransitCOName, name);
2009 treeItem->setData(TransitCOName, Qt::UserRole, englishName);
2010 treeItem->setText(TransitDate, QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD))); // local date and time
2011 treeItem->setData(TransitDate, Qt::UserRole, JD);
2012 treeItem->setText(TransitAltitude, altStr);
2013 treeItem->setTextAlignment(TransitAltitude, Qt::AlignRight);
2014 treeItem->setText(TransitMagnitude, magStr);
2015 treeItem->setTextAlignment(TransitMagnitude, Qt::AlignRight);
2016 treeItem->setText(TransitElongation, elongSStr);
2017 treeItem->setTextAlignment(TransitElongation, Qt::AlignRight);
2018 treeItem->setText(TransitAngularDistance, elongLStr);
2019 treeItem->setTextAlignment(TransitAngularDistance, Qt::AlignRight);
2020 }
2021 core->setJD(currentJD);
2022
2023 // adjust the column width
2024 for (int i = 0; i < TransitCount; ++i)
2025 {
2026 ui->transitTreeWidget->resizeColumnToContents(i);
2027 }
2028
2029 // sort-by-date
2030 ui->transitTreeWidget->sortItems(TransitDate, Qt::AscendingOrder);
2031 }
2032 else
2033 cleanupTransits();
2034 }
2035 }
2036
cleanupTransits()2037 void AstroCalcDialog::cleanupTransits()
2038 {
2039 ui->transitTreeWidget->clear();
2040 }
2041
selectCurrentTransit(const QModelIndex & modelIndex)2042 void AstroCalcDialog::selectCurrentTransit(const QModelIndex& modelIndex)
2043 {
2044 // Find the object
2045 QString name = modelIndex.sibling(modelIndex.row(), TransitCOName).data(Qt::UserRole).toString();
2046 double JD = modelIndex.sibling(modelIndex.row(), TransitDate).data(Qt::UserRole).toDouble();
2047
2048 if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
2049 {
2050 core->setJD(JD);
2051 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
2052 if (!newSelected.empty())
2053 {
2054 // Can't point to home planet
2055 if (newSelected[0]->getEnglishName() != core->getCurrentLocation().planetName)
2056 {
2057 mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
2058 mvMgr->setFlagTracking(true);
2059 }
2060 else
2061 {
2062 GETSTELMODULE(StelObjectMgr)->unSelect();
2063 }
2064 }
2065 }
2066 }
2067
setTransitCelestialBodyName()2068 void AstroCalcDialog::setTransitCelestialBodyName()
2069 {
2070 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
2071 QString name;
2072 if (!selectedObjects.isEmpty())
2073 {
2074 StelObjectP selectedObject = selectedObjects[0];
2075 name = selectedObject->getNameI18n();
2076 if (name.isEmpty())
2077 {
2078 QString otype = selectedObject->getType();
2079 if (otype == "Nebula")
2080 {
2081 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
2082 if (name.isEmpty())
2083 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
2084 }
2085 if (otype == "Star" || otype=="Pulsar")
2086 name = selectedObject->getID();
2087 }
2088 if (selectedObject->getType()=="Satellite")
2089 name = QString();
2090 }
2091 ui->transitCelestialBodyNameLabel->setText(name);
2092 }
2093
saveTransits()2094 void AstroCalcDialog::saveTransits()
2095 {
2096 QString filter = q_("Microsoft Excel Open XML Spreadsheet");
2097 filter.append(" (*.xlsx);;");
2098 filter.append(q_("CSV (Comma delimited)"));
2099 filter.append(" (*.csv)");
2100 QString defaultFilter("(*.xlsx)");
2101 QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
2102 q_("Save calculated transits as..."),
2103 QDir::homePath() + "/transits.xlsx",
2104 filter,
2105 &defaultFilter);
2106
2107 if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
2108 saveTableAsCSV(filePath, ui->transitTreeWidget, ephemerisHeader);
2109 else
2110 {
2111 int count = ui->transitTreeWidget->topLevelItemCount();
2112 int columns = transitHeader.size();
2113 int *width = new int[static_cast<unsigned int>(columns)];
2114 QString sData;
2115
2116 QXlsx::Document xlsx;
2117 xlsx.setDocumentProperty("title", q_("Transits"));
2118 xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
2119 xlsx.addSheet(ui->transitCelestialBodyNameLabel->text(), AbstractSheet::ST_WorkSheet);
2120
2121 QXlsx::Format header;
2122 header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
2123 header.setPatternBackgroundColor(Qt::yellow);
2124 header.setBorderStyle(QXlsx::Format::BorderThin);
2125 header.setBorderColor(Qt::black);
2126 header.setFontBold(true);
2127 for (int i = 0; i < columns; i++)
2128 {
2129 // Row 1: Names of columns
2130 sData = transitHeader.at(i).trimmed();
2131 xlsx.write(1, i + 1, sData, header);
2132 width[i] = sData.size();
2133 }
2134
2135 QXlsx::Format data;
2136 data.setHorizontalAlignment(QXlsx::Format::AlignRight);
2137 for (int i = 0; i < count; i++)
2138 {
2139 for (int j = 0; j < columns; j++)
2140 {
2141 // Row 2 and next: the data
2142 sData = ui->transitTreeWidget->topLevelItem(i)->text(j).trimmed();
2143 xlsx.write(i + 2, j + 1, sData, data);
2144 int w = sData.size();
2145 if (w > width[j])
2146 {
2147 width[j] = w;
2148 }
2149 }
2150 }
2151
2152 for (int i = 0; i < columns; i++)
2153 {
2154 xlsx.setColumnWidth(i+1, width[i]+2);
2155 }
2156
2157 delete[] width;
2158 xlsx.saveAs(filePath);
2159 }
2160 }
2161
populateCelestialBodyList()2162 void AstroCalcDialog::populateCelestialBodyList()
2163 {
2164 Q_ASSERT(ui->celestialBodyComboBox);
2165 Q_ASSERT(ui->secondaryCelestialBodyComboBox);
2166 Q_ASSERT(ui->graphsCelestialBodyComboBox);
2167 Q_ASSERT(ui->firstCelestialBodyComboBox);
2168 Q_ASSERT(ui->secondCelestialBodyComboBox);
2169
2170 QComboBox* planets = ui->celestialBodyComboBox;
2171 QComboBox* planets2 = ui->secondaryCelestialBodyComboBox;
2172 QComboBox* graphsp = ui->graphsCelestialBodyComboBox;
2173 QComboBox* firstCB = ui->firstCelestialBodyComboBox;
2174 QComboBox* secondCB = ui->secondCelestialBodyComboBox;
2175
2176 QList<PlanetP> ss = solarSystem->getAllPlanets();
2177
2178 // Save the current selection to be restored later
2179 planets->blockSignals(true);
2180 int indexP = planets->currentIndex();
2181 QVariant selectedPlanetId = planets->itemData(indexP);
2182 planets->clear();
2183
2184 planets2->blockSignals(true);
2185 int indexP2 = planets2->currentIndex();
2186 QVariant selectedPlanet2Id = planets2->itemData(indexP2);
2187 planets2->clear();
2188
2189 graphsp->blockSignals(true);
2190 int indexG = graphsp->currentIndex();
2191 QVariant selectedGraphsPId = graphsp->itemData(indexG);
2192 graphsp->clear();
2193
2194 firstCB->blockSignals(true);
2195 int indexFCB = firstCB->currentIndex();
2196 QVariant selectedFirstCelestialBodyId = firstCB->itemData(indexFCB);
2197 firstCB->clear();
2198
2199 secondCB->blockSignals(true);
2200 int indexSCB = secondCB->currentIndex();
2201 QVariant selectedSecondCelestialBodyId = secondCB->itemData(indexSCB);
2202 secondCB->clear();
2203
2204 // For each planet, display the localized name and store the original as user
2205 // data. Unfortunately, there's no other way to do this than with a cycle.
2206 for (const auto& p : ss)
2207 {
2208 if (!p->getEnglishName().contains("Observer", Qt::CaseInsensitive))
2209 {
2210 if (p->getEnglishName() != core->getCurrentPlanet()->getEnglishName())
2211 {
2212 // Let's exclude moons from list of celestial body for ephemeris tool (except the moons of current planet)
2213 if (p->getPlanetType()==Planet::isMoon && p->getParent()==core->getCurrentPlanet())
2214 {
2215 planets->addItem(p->getNameI18n(), p->getEnglishName());
2216 planets2->addItem(p->getNameI18n(), p->getEnglishName());
2217 }
2218 if (p->getPlanetType()!=Planet::isMoon)
2219 {
2220 planets->addItem(p->getNameI18n(), p->getEnglishName());
2221 planets2->addItem(p->getNameI18n(), p->getEnglishName());
2222 }
2223 graphsp->addItem(p->getNameI18n(), p->getEnglishName());
2224 }
2225 firstCB->addItem(p->getNameI18n(), p->getEnglishName());
2226 secondCB->addItem(p->getNameI18n(), p->getEnglishName());
2227 }
2228 }
2229 planets2->addItem(dash, "none");
2230 // Restore the selection
2231 indexP = planets->findData(selectedPlanetId, Qt::UserRole, Qt::MatchCaseSensitive);
2232 if (indexP < 0)
2233 {
2234 indexP = planets->findData(conf->value("astrocalc/ephemeris_celestial_body", "Moon").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2235 if (indexP<0)
2236 indexP = 0;
2237 }
2238 planets->setCurrentIndex(indexP);
2239 planets->model()->sort(0);
2240
2241 // Restore the selection
2242 indexP2 = planets2->findData(selectedPlanet2Id, Qt::UserRole, Qt::MatchCaseSensitive);
2243 if (indexP2 < 0)
2244 {
2245 indexP2 = planets2->findData(conf->value("astrocalc/ephemeris_second_celestial_body", "none").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2246 if (indexP2<0)
2247 indexP2 = 0;
2248 }
2249 planets2->setCurrentIndex(indexP2);
2250 planets2->model()->sort(0);
2251
2252 indexG = graphsp->findData(selectedGraphsPId, Qt::UserRole, Qt::MatchCaseSensitive);
2253 if (indexG < 0)
2254 {
2255 indexG = graphsp->findData(conf->value("astrocalc/graphs_celestial_body", "Moon").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2256 if (indexG<0)
2257 indexG = 0;
2258 }
2259 graphsp->setCurrentIndex(indexG);
2260 graphsp->model()->sort(0);
2261
2262 indexFCB = firstCB->findData(selectedFirstCelestialBodyId, Qt::UserRole, Qt::MatchCaseSensitive);
2263 if (indexFCB < 0)
2264 {
2265 indexFCB = firstCB->findData(conf->value("astrocalc/first_celestial_body", "Sun").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2266 if (indexFCB<0)
2267 indexFCB = 0;
2268 }
2269 firstCB->setCurrentIndex(indexFCB);
2270 firstCB->model()->sort(0);
2271
2272 indexSCB = secondCB->findData(selectedSecondCelestialBodyId, Qt::UserRole, Qt::MatchCaseSensitive);
2273 if (indexSCB < 0)
2274 {
2275 indexSCB = secondCB->findData(conf->value("astrocalc/second_celestial_body", "Earth").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2276 if (indexSCB<0)
2277 indexSCB = 0;
2278 }
2279 secondCB->setCurrentIndex(indexSCB);
2280 secondCB->model()->sort(0);
2281
2282 planets->blockSignals(false);
2283 planets2->blockSignals(false);
2284 graphsp->blockSignals(false);
2285 firstCB->blockSignals(false);
2286 secondCB->blockSignals(false);
2287 }
2288
saveEphemerisCelestialBody(int index)2289 void AstroCalcDialog::saveEphemerisCelestialBody(int index)
2290 {
2291 Q_ASSERT(ui->celestialBodyComboBox);
2292 QComboBox* planets = ui->celestialBodyComboBox;
2293 conf->setValue("astrocalc/ephemeris_celestial_body", planets->itemData(index).toString());
2294 }
2295
saveEphemerisSecondaryCelestialBody(int index)2296 void AstroCalcDialog::saveEphemerisSecondaryCelestialBody(int index)
2297 {
2298 Q_ASSERT(ui->secondaryCelestialBodyComboBox);
2299 QComboBox* planets = ui->secondaryCelestialBodyComboBox;
2300 conf->setValue("astrocalc/ephemeris_second_celestial_body", planets->itemData(index).toString());
2301 }
2302
saveGraphsCelestialBody(int index)2303 void AstroCalcDialog::saveGraphsCelestialBody(int index)
2304 {
2305 Q_ASSERT(ui->graphsCelestialBodyComboBox);
2306 QComboBox* planets = ui->graphsCelestialBodyComboBox;
2307 conf->setValue("astrocalc/graphs_celestial_body", planets->itemData(index).toString());
2308 }
2309
saveGraphsFirstId(int index)2310 void AstroCalcDialog::saveGraphsFirstId(int index)
2311 {
2312 Q_ASSERT(ui->graphsFirstComboBox);
2313 conf->setValue("astrocalc/graphs_first_id", ui->graphsFirstComboBox->itemData(index).toInt());
2314 }
2315
saveGraphsSecondId(int index)2316 void AstroCalcDialog::saveGraphsSecondId(int index)
2317 {
2318 Q_ASSERT(ui->graphsSecondComboBox);
2319 conf->setValue("astrocalc/graphs_second_id", ui->graphsSecondComboBox->itemData(index).toInt());
2320 }
2321
updateGraphsDuration(int duration)2322 void AstroCalcDialog::updateGraphsDuration(int duration)
2323 {
2324 if (graphsDuration!=duration)
2325 {
2326 graphsDuration = duration;
2327 conf->setValue("astrocalc/graphs_duration", duration);
2328 }
2329 }
2330
populateEphemerisTimeStepsList()2331 void AstroCalcDialog::populateEphemerisTimeStepsList()
2332 {
2333 typedef QPair<QString, QString> itemPairs;
2334 const QList<itemPairs> items = {
2335 {q_("1 minute"), "38"}, {q_("10 minutes"), "1"}, {q_("30 minutes"), "2"}, {q_("1 hour"), "3"}, {q_("6 hours"), "4"}, {q_("12 hours"), "5"},
2336 {q_("1 solar day"), "6"}, {q_("5 solar days"), "7"}, {q_("10 solar days"), "8"}, {q_("15 solar days"), "9"}, {q_("30 solar days"), "10"},
2337 {q_("60 solar days"), "11"}, {q_("100 solar days"), "24"},{q_("500 solar days"), "37"},{q_("1 sidereal day"), "18"},{q_("5 sidereal days"), "19"},
2338 {q_("10 sidereal days"), "20"},{q_("15 sidereal days"), "21"},{q_("30 sidereal days"), "22"},{q_("60 sidereal days"), "23"},{q_("100 sidereal days"), "25"},
2339 {q_("500 sidereal days"), "36"},{q_("1 sidereal year"), "27"},{q_("1 Julian day"), "12"},{q_("5 Julian days"), "13"},{q_("10 Julian days"), "14"},
2340 {q_("15 Julian days"), "15"},{q_("30 Julian days"), "16"},{q_("60 Julian days"), "17"},{q_("100 Julian days"), "26"},{q_("1 Julian year"), "28"},
2341 {q_("1 Gaussian year"), "29"},{q_("1 synodic month"), "30"},{q_("1 draconic month"), "31"},{q_("1 mean tropical month"), "32"},
2342 {q_("1 anomalistic month"), "33"},{q_("1 anomalistic year"), "34"},{q_("1 saros"), "35"},{q_("custom interval"), "0"}
2343 };
2344 Q_ASSERT(ui->ephemerisStepComboBox);
2345 QComboBox* steps = ui->ephemerisStepComboBox;
2346 steps->blockSignals(true);
2347 steps->clear();
2348 int index = steps->currentIndex();
2349 QVariant selectedStepId = steps->itemData(index);
2350 for (const auto& f : items)
2351 {
2352 steps->addItem(f.first, f.second);
2353 }
2354
2355 index = steps->findData(selectedStepId, Qt::UserRole, Qt::MatchCaseSensitive);
2356 if (index < 0)
2357 {
2358 // default step: one day
2359 index = steps->findData(conf->value("astrocalc/ephemeris_time_step", "6").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2360 }
2361 steps->setCurrentIndex(index);
2362 steps->blockSignals(false);
2363 enableCustomEphemerisTimeStepButton();
2364 }
2365
saveEphemerisTimeStep(int index)2366 void AstroCalcDialog::saveEphemerisTimeStep(int index)
2367 {
2368 Q_ASSERT(ui->ephemerisStepComboBox);
2369 QComboBox* steps = ui->ephemerisStepComboBox;
2370 conf->setValue("astrocalc/ephemeris_time_step", steps->itemData(index).toInt());
2371 enableCustomEphemerisTimeStepButton();
2372 }
2373
initEphemerisFlagNakedEyePlanets(void)2374 void AstroCalcDialog::initEphemerisFlagNakedEyePlanets(void)
2375 {
2376 bool nep = conf->value("astrocalc/ephemeris_nakedeye_planets", "false").toBool();
2377 if (core->getCurrentPlanet()==solarSystem->getEarth())
2378 {
2379 ui->celestialBodyComboBox->setEnabled(!nep);
2380 ui->secondaryCelestialBodyComboBox->setEnabled(!nep);
2381 ui->allNakedEyePlanetsCheckBox->setChecked(nep);
2382 ui->allNakedEyePlanetsCheckBox->setEnabled(true);
2383 }
2384 else
2385 {
2386 ui->celestialBodyComboBox->setEnabled(true);
2387 ui->secondaryCelestialBodyComboBox->setEnabled(true);
2388 ui->allNakedEyePlanetsCheckBox->setChecked(false);
2389 ui->allNakedEyePlanetsCheckBox->setEnabled(false);
2390 }
2391 }
2392
saveEphemerisFlagNakedEyePlanets(bool flag)2393 void AstroCalcDialog::saveEphemerisFlagNakedEyePlanets(bool flag)
2394 {
2395 ui->celestialBodyComboBox->setEnabled(!flag);
2396 ui->secondaryCelestialBodyComboBox->setEnabled(!flag);
2397 conf->setValue("astrocalc/ephemeris_nakedeye_planets", flag);
2398 reGenerateEphemeris(false);
2399 }
2400
enableCustomEphemerisTimeStepButton()2401 void AstroCalcDialog::enableCustomEphemerisTimeStepButton()
2402 {
2403 Q_ASSERT(ui->ephemerisStepComboBox);
2404 if (ui->ephemerisStepComboBox->currentData(Qt::UserRole).toInt()==0)
2405 ui->pushButtonCustomStepsDialog->setEnabled(true);
2406 else
2407 ui->pushButtonCustomStepsDialog->setEnabled(false);
2408 }
2409
populatePlanetList()2410 void AstroCalcDialog::populatePlanetList()
2411 {
2412 Q_ASSERT(ui->object1ComboBox); // object 1 is always major planet
2413
2414 QComboBox* planetList = ui->object1ComboBox;
2415 QList<PlanetP> planets = solarSystem->getAllPlanets();
2416 const StelTranslator& trans = localeMgr->getSkyTranslator();
2417 QString cpName = core->getCurrentPlanet()->getEnglishName();
2418
2419 // Save the current selection to be restored later
2420 planetList->blockSignals(true);
2421 int index = planetList->currentIndex();
2422 QVariant selectedPlanetId = planetList->itemData(index);
2423 planetList->clear();
2424 // For each planet, display the localized name and store the original as user
2425 // data. Unfortunately, there's no other way to do this than with a cycle.
2426 for (const auto& planet : planets)
2427 {
2428 // major planets and the Sun
2429 if ((planet->getPlanetType() == Planet::isPlanet || planet->getPlanetType() == Planet::isStar) && planet->getEnglishName() != cpName)
2430 planetList->addItem(trans.qtranslate(planet->getNameI18n()), planet->getEnglishName());
2431
2432 // moons of the current planet
2433 if (planet->getPlanetType() == Planet::isMoon && planet->getEnglishName() != cpName && planet->getParent() == core->getCurrentPlanet())
2434 planetList->addItem(trans.qtranslate(planet->getNameI18n()), planet->getEnglishName());
2435 }
2436 // special case: selected dwarf and minor planets
2437 planets.clear();
2438 planets.append(solarSystem->searchByEnglishName("Pluto"));
2439 planets.append(solarSystem->searchByEnglishName("Ceres"));
2440 planets.append(solarSystem->searchByEnglishName("Pallas"));
2441 planets.append(solarSystem->searchByEnglishName("Juno"));
2442 planets.append(solarSystem->searchByEnglishName("Vesta"));
2443 for (const auto& planet : planets)
2444 {
2445 if (!planet.isNull() && planet->getEnglishName()!=cpName)
2446 planetList->addItem(trans.qtranslate(planet->getNameI18n()), planet->getEnglishName());
2447 }
2448 // Restore the selection
2449 index = planetList->findData(selectedPlanetId, Qt::UserRole, Qt::MatchCaseSensitive);
2450 if (index < 0)
2451 {
2452 index = planetList->findData(conf->value("astrocalc/phenomena_celestial_body", "Venus").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2453 if (index<0)
2454 index = 0;
2455 }
2456 planetList->setCurrentIndex(index);
2457 planetList->model()->sort(0);
2458 planetList->blockSignals(false);
2459 }
2460
savePhenomenaCelestialBody(int index)2461 void AstroCalcDialog::savePhenomenaCelestialBody(int index)
2462 {
2463 Q_ASSERT(ui->object1ComboBox);
2464 QComboBox* planets = ui->object1ComboBox;
2465 conf->setValue("astrocalc/phenomena_celestial_body", planets->itemData(index).toString());
2466 }
2467
populateGroupCelestialBodyList()2468 void AstroCalcDialog::populateGroupCelestialBodyList()
2469 {
2470 Q_ASSERT(ui->object2ComboBox);
2471
2472 QComboBox* groups = ui->object2ComboBox;
2473 groups->blockSignals(true);
2474 int index = groups->currentIndex();
2475 QVariant selectedGroupId = groups->itemData(index);
2476
2477 QString brLimit = QString::number(brightLimit, 'f', 1);
2478 const QMap<QString, int>itemsMap={
2479 {q_("Latest selected object"), PHCLatestSelectedObject}, {q_("Solar system"), PHCSolarSystem}, {q_("Planets"), PHCPlanets}, {q_("Asteroids"), PHCAsteroids},
2480 {q_("Plutinos"), PHCPlutinos}, {q_("Comets"), PHCComets}, {q_("Dwarf planets"), PHCDwarfPlanets},{q_("Cubewanos"), PHCCubewanos},
2481 {q_("Scattered disc objects"), PHCScatteredDiscObjects},{q_("Oort cloud objects"), PHCOortCloudObjects},{q_("Sednoids"), PHCSednoids},
2482 {q_("Bright stars (<%1 mag)").arg(QString::number(brightLimit - 5.0, 'f', 1)), PHCBrightStars},{q_("Bright double stars (<%1 mag)").arg(QString::number(brightLimit - 5.0, 'f', 1)), PHCBrightDoubleStars},
2483 {q_("Bright variable stars (<%1 mag)").arg(QString::number(brightLimit - 5.0, 'f', 1)), PHCBrightVariableStars},{q_("Bright star clusters (<%1 mag)").arg(brLimit), PHCBrightStarClusters},
2484 {q_("Planetary nebulae (<%1 mag)").arg(brLimit), PHCPlanetaryNebulae},{q_("Bright nebulae (<%1 mag)").arg(brLimit), PHCBrightNebulae},{q_("Dark nebulae"), PHCDarkNebulae},
2485 {q_("Bright galaxies (<%1 mag)").arg(brLimit), PHCBrightGalaxies},{q_("Symbiotic stars"), PHCSymbioticStars},{q_("Emission-line stars"), PHCEmissionLineStars},
2486 {q_("Interstellar objects"), PHCInterstellarObjects},{q_("Planets and Sun"), PHCPlanetsSun},{q_("Sun, planets and moons of observer location"), PHCSunPlanetsMoons},
2487 {q_("Bright Solar system objects (<%1 mag)").arg(QString::number(brightLimit + 2.0, 'f', 1)), PHCBrightSolarSystemObjects},
2488 {q_("Solar system objects: minor bodies"), PHCSolarSystemMinorBodies},{q_("Moons of first body"), PHCMoonsFirstBody},
2489 {q_("Bright carbon stars"), PHCBrightCarbonStars},{q_("Bright barium stars"), PHCBrightBariumStars}
2490 };
2491 QMapIterator<QString, int> i(itemsMap);
2492 groups->clear();
2493 while (i.hasNext())
2494 {
2495 i.next();
2496 groups->addItem(i.key(), QString::number(i.value()));
2497 }
2498
2499 index = groups->findData(selectedGroupId, Qt::UserRole, Qt::MatchCaseSensitive);
2500 if (index < 0)
2501 index = groups->findData(conf->value("astrocalc/phenomena_celestial_group", "1").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
2502 groups->setCurrentIndex(index);
2503 groups->model()->sort(0);
2504 groups->blockSignals(false);
2505 }
2506
savePhenomenaCelestialGroup(int index)2507 void AstroCalcDialog::savePhenomenaCelestialGroup(int index)
2508 {
2509 Q_ASSERT(ui->object2ComboBox);
2510 QComboBox* group = ui->object2ComboBox;
2511 conf->setValue("astrocalc/phenomena_celestial_group", group->itemData(index).toInt());
2512 }
2513
cleanupPhenomena()2514 void AstroCalcDialog::cleanupPhenomena()
2515 {
2516 ui->phenomenaTreeWidget->clear();
2517 adjustPhenomenaColumns();
2518 }
2519
savePhenomenaOppositionFlag(bool b)2520 void AstroCalcDialog::savePhenomenaOppositionFlag(bool b)
2521 {
2522 conf->setValue("astrocalc/flag_phenomena_opposition", b);
2523 }
2524
savePhenomenaPerihelionAphelionFlag(bool b)2525 void AstroCalcDialog::savePhenomenaPerihelionAphelionFlag(bool b)
2526 {
2527 conf->setValue("astrocalc/flag_phenomena_perihelion", b);
2528 }
2529
savePhenomenaAngularSeparation()2530 void AstroCalcDialog::savePhenomenaAngularSeparation()
2531 {
2532 conf->setValue("astrocalc/phenomena_angular_separation", QString::number(ui->allowedSeparationSpinBox->valueDegrees(), 'f', 5));
2533 }
2534
drawAltVsTimeDiagram()2535 void AstroCalcDialog::drawAltVsTimeDiagram()
2536 {
2537 // Avoid crash!
2538 if (core->getCurrentPlanet()->getEnglishName().contains("->")) // We are on the spaceship!
2539 return;
2540
2541 // special case - plot the graph when tab is visible
2542 //..
2543 // we got notified about a reason to redraw the plot, but dialog was
2544 // not visible. which means we must redraw when becoming visible again!
2545 if (!dialog->isVisible() && plotAltVsTime)
2546 {
2547 graphPlotNeedsRefresh = true;
2548 return;
2549 }
2550
2551 if (!plotAltVsTime) return;
2552
2553 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
2554
2555 if (!selectedObjects.isEmpty())
2556 {
2557 // X axis - time; Y axis - altitude
2558 QList<double> aX, aY, sX, sY, sYn, sYa, sYc, mX, mY;
2559 QVector<double> xs, ys, ysn, ysa, ysc, xm, ym;
2560
2561 StelObjectP selectedObject = selectedObjects[0];
2562 ui->altVsTimeTitle->setText(selectedObject->getNameI18n());
2563 const bool onEarth = core->getCurrentPlanet()==solarSystem->getEarth();
2564
2565 const double currentJD = core->getJD();
2566 const double shift = core->getUTCOffset(currentJD) / 24.0;
2567 const double noon = static_cast<int>(currentJD + shift);
2568 double az, alt, deg, ltime, JD;
2569 bool sign;
2570
2571 double xMaxY = -100.;
2572 int step = 180;
2573 int limit = 485;
2574 #ifdef USE_STATIC_PLUGIN_SATELLITES
2575 bool isSatellite = false;
2576
2577 SatelliteP sat;
2578 if (selectedObject->getType() == "Satellite")
2579 {
2580 // get reference to satellite
2581 isSatellite = true;
2582 sat = GETSTELMODULE(Satellites)->getById(selectedObject->getInfoMap(core)["catalog"].toString());
2583 }
2584 #endif
2585
2586 for (int i = -5; i <= limit; i++) // 24 hours + 15 minutes in both directions
2587 {
2588 // A new point on the graph every 3 minutes with shift to right 12 hours
2589 // to get midnight at the center of diagram (i.e. accuracy is 3 minutes)
2590 ltime = i * step + 43200;
2591 aX.append(ltime);
2592 JD = noon + ltime / 86400 - shift - 0.5;
2593 core->setJD(JD);
2594
2595 #ifdef USE_STATIC_PLUGIN_SATELLITES
2596 if (isSatellite)
2597 {
2598 // update data for that single satellite only
2599 sat->update(0.0);
2600 }
2601 else
2602 #endif
2603 core->update(0.0);
2604
2605 StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
2606 StelUtils::radToDecDeg(alt, sign, deg);
2607 if (!sign) deg *= -1;
2608 aY.append(deg);
2609 if (deg > xMaxY)
2610 {
2611 xMaxY = deg;
2612 transitX = ltime;
2613 }
2614 }
2615
2616 if (plotAltVsTimeSun)
2617 {
2618 PlanetP sun = solarSystem->getSun();
2619 for (int i = -1; i <= 25; i++)
2620 {
2621 ltime = i * 3600 + 43200;
2622 sX.append(ltime);
2623 JD = noon + ltime / 86400 - shift - 0.5;
2624 core->setJD(JD);
2625 core->update(0.0);
2626 StelUtils::rectToSphe(&az, &alt, sun->getAltAzPosAuto(core));
2627 StelUtils::radToDecDeg(alt, sign, deg);
2628 if (!sign) deg *= -1;
2629 sY.append(deg);
2630 sYc.append(deg + 6);
2631 sYn.append(deg + 12);
2632 sYa.append(deg + 18);
2633 }
2634 }
2635
2636 if (plotAltVsTimeMoon && onEarth)
2637 {
2638 PlanetP moon = solarSystem->getMoon();
2639 for (int i = -1; i <= 25; i++)
2640 {
2641 ltime = i * 3600 + 43200;
2642 mX.append(ltime);
2643 JD = noon + ltime / 86400 - shift - 0.5;
2644 core->setJD(JD);
2645 core->update(0.0);
2646 StelUtils::rectToSphe(&az, &alt, moon->getAltAzPosAuto(core));
2647 StelUtils::radToDecDeg(alt, sign, deg);
2648 if (!sign) deg *= -1;
2649 mY.append(deg);
2650 }
2651 }
2652
2653 core->setJD(currentJD);
2654
2655 QVector<double> x = aX.toVector(), y = aY.toVector();
2656 minY = *std::min_element(aY.begin(), aY.end()) - 2.0;
2657 maxY = *std::max_element(aY.begin(), aY.end()) + 2.0;
2658
2659 // additional data: Sun + Twilight
2660 if (plotAltVsTimeSun)
2661 {
2662 xs = sX.toVector();
2663 ys = sY.toVector();
2664 ysc = sYc.toVector();
2665 ysn = sYn.toVector();
2666 ysa = sYa.toVector();
2667 double minYs = *std::min_element(sY.begin(), sY.end());
2668 double maxYs = *std::max_element(sY.begin(), sY.end());
2669
2670 if (minY >= minYs - 2.0) minY = minYs - 2.0;
2671 if (maxY <= maxYs + 20.0) maxY = maxYs + 20.0;
2672 }
2673
2674 // additional data: Moon
2675 if (plotAltVsTimeMoon && onEarth)
2676 {
2677 xm = mX.toVector();
2678 ym = mY.toVector();
2679 double minYm = *std::min_element(mY.begin(), mY.end());
2680 double maxYm = *std::max_element(mY.begin(), mY.end());
2681
2682 if (minY >= minYm - 2.0) minY = minYm - 2.0;
2683 if (maxY <= maxYm + 2.0) maxY = maxYm + 2.0;
2684 }
2685
2686 if (plotAltVsTimePositive && minY<altVsTimePositiveLimit)
2687 minY = altVsTimePositiveLimit;
2688
2689 prepareAxesAndGraph();
2690 drawCurrentTimeDiagram();
2691
2692 QString name = selectedObject->getNameI18n();
2693 if (name.isEmpty())
2694 {
2695 QString otype = selectedObject->getType();
2696 if (otype == "Nebula")
2697 {
2698 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
2699 if (name.isEmpty())
2700 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
2701 }
2702
2703 if (otype == "Star" || otype=="Pulsar")
2704 selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
2705 }
2706
2707 drawTransitTimeDiagram();
2708
2709 ui->altVsTimePlot->graph(0)->setData(x, y);
2710 ui->altVsTimePlot->graph(0)->setName(name);
2711 if (plotAltVsTimeSun)
2712 {
2713 ui->altVsTimePlot->graph(3)->setData(xs, ys);
2714 ui->altVsTimePlot->graph(4)->setData(xs, ysc);
2715 ui->altVsTimePlot->graph(5)->setData(xs, ysn);
2716 ui->altVsTimePlot->graph(6)->setData(xs, ysa);
2717 }
2718 if (plotAltVsTimeMoon && onEarth)
2719 ui->altVsTimePlot->graph(7)->setData(xm, ym);
2720
2721 ui->altVsTimePlot->replot();
2722 }
2723
2724 // clean up the data when selection is removed
2725 if (!objectMgr->getWasSelected())
2726 {
2727 ui->altVsTimePlot->graph(0)->data()->clear(); // main data: Altitude vs. Time graph
2728 ui->altVsTimePlot->graph(2)->data()->clear(); // additional data: Transit Time Diagram
2729 ui->altVsTimePlot->graph(3)->data()->clear(); // additional data: Sun
2730 ui->altVsTimePlot->graph(4)->data()->clear(); // additional data: Civil Twilight
2731 ui->altVsTimePlot->graph(5)->data()->clear(); // additional data: Nautical Twilight
2732 ui->altVsTimePlot->graph(6)->data()->clear(); // additional data: Astronomical Twilight
2733 ui->altVsTimePlot->graph(7)->data()->clear(); // additional data: Moon
2734 ui->altVsTimePlot->replot();
2735 }
2736 }
2737
2738 // Added vertical line indicating "now"
drawCurrentTimeDiagram()2739 void AstroCalcDialog::drawCurrentTimeDiagram()
2740 {
2741 // special case - plot the graph when tab is visible
2742 // and only if dialog is visible at all
2743 if (!dialog->isVisible() || (!plotAltVsTime && !plotAziVsTime)) return;
2744
2745 const double currentJD = core->getJD();
2746 const double UTCOffset = core->getUTCOffset(currentJD);
2747 double now = ((currentJD + 0.5 - static_cast<int>(currentJD)) * 86400.0) + UTCOffset * 3600.0;
2748 if (now > 129600) now -= 86400;
2749 if (now < 43200) now += 86400;
2750
2751 QList<double> ax, ay;
2752 ax.append(now);
2753 ax.append(now);
2754 ay.append(-180.);
2755 ay.append(360.);
2756 QVector<double> x = ax.toVector(), y = ay.toVector();
2757 if (plotAltVsTime)
2758 {
2759 ui->altVsTimePlot->graph(1)->setData(x, y);
2760 ui->altVsTimePlot->replot();
2761 }
2762 if (plotAziVsTime)
2763 {
2764 ui->aziVsTimePlot->graph(1)->setData(x, y);
2765 ui->aziVsTimePlot->replot();
2766 }
2767
2768 // detect roll over graph day limits.
2769 // if so, update the graph
2770 int graphJD = static_cast<int>(currentJD + UTCOffset / 24.);
2771 if (oldGraphJD != graphJD || graphPlotNeedsRefresh)
2772 {
2773 oldGraphJD = graphJD;
2774 graphPlotNeedsRefresh = false;
2775 emit graphDayChanged();
2776 }
2777 }
2778
2779 // Added vertical line indicating time of transit
drawTransitTimeDiagram()2780 void AstroCalcDialog::drawTransitTimeDiagram()
2781 {
2782 // special case - plot the graph when tab is visible
2783 if (!plotAltVsTime)
2784 return;
2785
2786 QList<double> ax, ay;
2787 ax.append(transitX);
2788 ax.append(transitX);
2789 ay.append(minY);
2790 ay.append(maxY);
2791 QVector<double> x = ax.toVector(), y = ay.toVector();
2792 ui->altVsTimePlot->graph(2)->setData(x, y);
2793 ui->altVsTimePlot->replot();
2794 }
2795
prepareAxesAndGraph()2796 void AstroCalcDialog::prepareAxesAndGraph()
2797 {
2798 QString xAxisStr = q_("Local Time");
2799 QString yAxisStr = QString("%1, %2").arg(q_("Altitude"), QChar(0x00B0));
2800
2801 QColor axisColor(Qt::white);
2802 QPen axisPen(axisColor, 1);
2803
2804 ui->altVsTimePlot->clearGraphs();
2805
2806 // main data: Altitude vs. Time graph
2807 ui->altVsTimePlot->addGraph();
2808 ui->altVsTimePlot->setBackground(QBrush(QColor(86, 87, 90)));
2809 ui->altVsTimePlot->graph(0)->setPen(QPen(Qt::red, 1));
2810 ui->altVsTimePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
2811 ui->altVsTimePlot->graph(0)->rescaleAxes(true);
2812
2813 // additional data: Current Time Diagram
2814 ui->altVsTimePlot->addGraph();
2815 ui->altVsTimePlot->graph(1)->setPen(QPen(Qt::yellow, 1));
2816 ui->altVsTimePlot->graph(1)->setLineStyle(QCPGraph::lsLine);
2817 ui->altVsTimePlot->graph(1)->setName("[Now]");
2818
2819 // additional data: Transit Time Diagram
2820 ui->altVsTimePlot->addGraph();
2821 ui->altVsTimePlot->graph(2)->setPen(QPen(Qt::cyan, 1));
2822 ui->altVsTimePlot->graph(2)->setLineStyle(QCPGraph::lsLine);
2823 ui->altVsTimePlot->graph(2)->setName("[Transit]");
2824
2825 // additional data: Sun Elevation
2826 ui->altVsTimePlot->addGraph();
2827 ui->altVsTimePlot->graph(3)->setPen(QPen(Qt::darkBlue, 1));
2828 ui->altVsTimePlot->graph(3)->setLineStyle(QCPGraph::lsLine);
2829 ui->altVsTimePlot->graph(3)->setName("[Sun]");
2830 // additional data: Civil Twilight
2831 QPen pen;
2832 pen.setStyle(Qt::DotLine);
2833 pen.setWidth(1);
2834 pen.setColor(Qt::blue);
2835 ui->altVsTimePlot->addGraph();
2836 ui->altVsTimePlot->graph(4)->setPen(pen);
2837 ui->altVsTimePlot->graph(4)->setName("[Civil Twilight]");
2838 // additional data: Nautical Twilight
2839 ui->altVsTimePlot->addGraph();
2840 ui->altVsTimePlot->graph(5)->setPen(pen);
2841 ui->altVsTimePlot->graph(5)->setName("[Nautical Twilight]");
2842 // additional data: Astronomical Twilight
2843 ui->altVsTimePlot->addGraph();
2844 ui->altVsTimePlot->graph(6)->setPen(pen);
2845 ui->altVsTimePlot->graph(6)->setName("[Astronomical Twilight]");
2846
2847 // additional data: Moon Elevation
2848 pen.setStyle(Qt::DashLine);
2849 pen.setColor(Qt::darkBlue);
2850 ui->altVsTimePlot->addGraph();
2851 ui->altVsTimePlot->graph(7)->setPen(pen);
2852 ui->altVsTimePlot->graph(7)->setName("[Moon]");
2853
2854 ui->altVsTimePlot->xAxis->setLabel(xAxisStr);
2855 ui->altVsTimePlot->yAxis->setLabel(yAxisStr);
2856
2857 ui->altVsTimePlot->xAxis->setRange(43200, 129600); // 24 hours since 12h00m (range in seconds)
2858 ui->altVsTimePlot->xAxis->setScaleType(QCPAxis::stLinear);
2859 ui->altVsTimePlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
2860 ui->altVsTimePlot->xAxis->setLabelColor(axisColor);
2861 ui->altVsTimePlot->xAxis->setTickLabelColor(axisColor);
2862 ui->altVsTimePlot->xAxis->setBasePen(axisPen);
2863 ui->altVsTimePlot->xAxis->setTickPen(axisPen);
2864 ui->altVsTimePlot->xAxis->setSubTickPen(axisPen);
2865 ui->altVsTimePlot->xAxis->setDateTimeFormat("H:mm");
2866 ui->altVsTimePlot->xAxis->setDateTimeSpec(Qt::UTC); // Qt::UTC + core->getUTCOffset() give local time
2867 ui->altVsTimePlot->xAxis->setAutoTickStep(false);
2868 ui->altVsTimePlot->xAxis->setTickStep(7200); // step is 2 hours (in seconds)
2869 ui->altVsTimePlot->xAxis->setAutoSubTicks(false);
2870 ui->altVsTimePlot->xAxis->setSubTickCount(7);
2871
2872 ui->altVsTimePlot->yAxis->setRange(minY, maxY);
2873 ui->altVsTimePlot->yAxis->setScaleType(QCPAxis::stLinear);
2874 ui->altVsTimePlot->yAxis->setLabelColor(axisColor);
2875 ui->altVsTimePlot->yAxis->setTickLabelColor(axisColor);
2876 ui->altVsTimePlot->yAxis->setBasePen(axisPen);
2877 ui->altVsTimePlot->yAxis->setTickPen(axisPen);
2878 ui->altVsTimePlot->yAxis->setSubTickPen(axisPen);
2879 }
2880
drawXVsTimeGraphs()2881 void AstroCalcDialog::drawXVsTimeGraphs()
2882 {
2883 PlanetP ssObj = solarSystem->searchByEnglishName(ui->graphsCelestialBodyComboBox->currentData().toString());
2884 // added special case - the tool is not applicable on non-Earth locations
2885 if (!ssObj.isNull() && core->getCurrentPlanet()==solarSystem->getEarth())
2886 {
2887 // X axis - time; Y axis - altitude
2888 QList<double> aX, aY, bY;
2889
2890 const double currentJD = core->getJD();
2891 int year, month, day;
2892 double startJD, JD, ltime, /*UTCshift,*/ width = 1.0;
2893 StelUtils::getDateFromJulianDay(currentJD, &year, &month, &day);
2894 StelUtils::getJDFromDate(&startJD, year, 1, 1, 0, 0, 0);
2895
2896 int dYear = static_cast<int>(core->getCurrentPlanet()->getSiderealPeriod()*graphsDuration) + 3;
2897 int firstGraph = ui->graphsFirstComboBox->currentData().toInt();
2898 int secondGraph = ui->graphsSecondComboBox->currentData().toInt();
2899
2900 for (int i = -2; i <= dYear; i++)
2901 {
2902 JD = startJD + i;
2903
2904 if (firstGraph==GraphTransitAltitudeVsTime || secondGraph==GraphTransitAltitudeVsTime)
2905 {
2906 core->setJD(JD);
2907 //UTCshift = core->getUTCOffset(JD) / 24.; // Fix DST shift...
2908 Vec4d rts = ssObj->getRTSTime(core);
2909 //JD += (rts[1]/24. - UTCshift); // FIXME: New logic has JD, not hours, here.
2910 JD = rts[1]; // Maybe that's all?
2911 }
2912
2913 ltime = (JD - startJD) * StelCore::ONE_OVER_JD_SECOND;
2914 aX.append(ltime);
2915
2916 core->setJD(JD);
2917
2918 aY.append(computeGraphValue(ssObj, firstGraph));
2919 bY.append(computeGraphValue(ssObj, secondGraph));
2920
2921 core->update(0.0);
2922 }
2923 core->setJD(currentJD);
2924
2925 QVector<double> x = aX.toVector(), ya = aY.toVector(), yb = bY.toVector();
2926
2927 double minYa = *std::min_element(aY.begin(), aY.end());
2928 double maxYa = *std::max_element(aY.begin(), aY.end());
2929
2930 width = (maxYa - minYa) / 50.0;
2931 minY1 = minYa - width;
2932 maxY1 = maxYa + width;
2933
2934 minYa = *std::min_element(bY.begin(), bY.end());
2935 maxYa = *std::max_element(bY.begin(), bY.end());
2936
2937 width = (maxYa - minYa) / 50.0;
2938 minY2 = minYa - width;
2939 maxY2 = maxYa + width;
2940
2941 prepareXVsTimeAxesAndGraph();
2942
2943 ui->graphsPlot->clearGraphs();
2944
2945 ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis);
2946 ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
2947 ui->graphsPlot->graph(0)->setPen(QPen(Qt::green, 1));
2948 ui->graphsPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
2949 ui->graphsPlot->graph(0)->rescaleAxes(true);
2950 ui->graphsPlot->graph(0)->setData(x, ya);
2951 ui->graphsPlot->graph(0)->setName("[0]");
2952
2953 ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis2);
2954 ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
2955 ui->graphsPlot->graph(1)->setPen(QPen(Qt::yellow, 1));
2956 ui->graphsPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
2957 ui->graphsPlot->graph(1)->rescaleAxes(true);
2958 ui->graphsPlot->graph(1)->setData(x, yb);
2959 ui->graphsPlot->graph(1)->setName("[1]");
2960
2961 if (graphsDuration>1)
2962 {
2963 int JDshift = static_cast<int>(core->getCurrentPlanet()->getSiderealPeriod());
2964 QList<double> axj, ayj;
2965 for (int i = 0; i < graphsDuration; i++)
2966 {
2967 JD = startJD + i*JDshift;
2968 ltime = (JD - startJD) * StelCore::ONE_OVER_JD_SECOND;
2969 axj.append(ltime);
2970 axj.append(ltime);
2971 ayj.append(minY1);
2972 ayj.append(maxY1);
2973 QVector<double> xj = axj.toVector(), yj = ayj.toVector();
2974 int j = 2 + i;
2975 ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis);
2976 ui->graphsPlot->graph(j)->setPen(QPen(Qt::red, 1, Qt::DashLine));
2977 ui->graphsPlot->graph(j)->setLineStyle(QCPGraph::lsLine);
2978 ui->graphsPlot->graph(j)->setData(xj, yj);
2979 ui->graphsPlot->graph(j)->setName(QString("[%1]").arg(j));
2980 axj.clear();
2981 ayj.clear();
2982 }
2983 }
2984
2985 ui->graphsPlot->replot();
2986 }
2987 else
2988 {
2989 prepareXVsTimeAxesAndGraph();
2990 ui->graphsPlot->clearGraphs();
2991 ui->graphsPlot->replot();
2992 }
2993 }
2994
updateXVsTimeGraphs()2995 void AstroCalcDialog::updateXVsTimeGraphs()
2996 {
2997 // Do all that only if the dialog is visible!
2998 if (!dialog->isVisible())
2999 return;
3000
3001 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
3002 if (!selectedObjects.isEmpty())
3003 {
3004 QComboBox* celestialBody = ui->graphsCelestialBodyComboBox;
3005 celestialBody->blockSignals(true);
3006 int index = celestialBody->findData(selectedObjects[0]->getEnglishName(), Qt::UserRole, Qt::MatchCaseSensitive);
3007 if (index>=0)
3008 celestialBody->setCurrentIndex(index);
3009 celestialBody->blockSignals(false);
3010 }
3011
3012 if (ui->tabWidgetGraphs->currentIndex()==3)
3013 drawXVsTimeGraphs();
3014 }
3015
mouseOverGraphs(QMouseEvent * event)3016 void AstroCalcDialog::mouseOverGraphs(QMouseEvent* event)
3017 {
3018 double x = ui->graphsPlot->xAxis->pixelToCoord(event->pos().x());
3019 double y = ui->graphsPlot->yAxis->pixelToCoord(event->pos().y());
3020 double y2 = ui->graphsPlot->yAxis2->pixelToCoord(event->pos().y());
3021
3022 QCPAbstractPlottable* abstractGraph = ui->graphsPlot->plottableAt(event->pos(), false);
3023 QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
3024
3025 int year, month, day;
3026 double startJD, ltime;
3027 StelUtils::getDateFromJulianDay(core->getJD(), &year, &month, &day);
3028 StelUtils::getJDFromDate(&startJD, year, 1, 1, 0, 0, 0);
3029
3030 if (x > ui->graphsPlot->xAxis->range().lower && x < ui->graphsPlot->xAxis->range().upper
3031 && y > ui->graphsPlot->yAxis->range().lower && y < ui->graphsPlot->yAxis->range().upper)
3032 {
3033 QString info = "";
3034 if (graph)
3035 {
3036 ltime = (x / StelCore::ONE_OVER_JD_SECOND) + startJD;
3037
3038 if (graph->name() == "[0]")
3039 info = QString("%1<br />%2: %3").arg(StelUtils::julianDayToISO8601String(ltime).replace("T", " "), ui->graphsPlot->yAxis->label() , QString::number(y, 'f', 2));
3040
3041 if (graph->name() == "[1]")
3042 info = QString("%1<br />%2: %3").arg(StelUtils::julianDayToISO8601String(ltime).replace("T", " "), ui->graphsPlot->yAxis2->label() , QString::number(y2, 'f', 2));
3043 }
3044 ui->graphsPlot->setToolTip(info);
3045 }
3046
3047 ui->graphsPlot->update();
3048 ui->graphsPlot->replot();
3049 }
3050
computeGraphValue(const PlanetP & ssObj,const int graphType)3051 double AstroCalcDialog::computeGraphValue(const PlanetP &ssObj, const int graphType)
3052 {
3053 double value = 0.;
3054 switch (graphType)
3055 {
3056 case GraphMagnitudeVsTime:
3057 value = ssObj->getVMagnitude(core);
3058 break;
3059 case GraphPhaseVsTime:
3060 value = ssObj->getPhase(core->getObserverHeliocentricEclipticPos()) * 100.;
3061 break;
3062 case GraphDistanceVsTime:
3063 value = ssObj->getJ2000EquatorialPos(core).length();
3064 break;
3065 case GraphElongationVsTime:
3066 value = ssObj->getElongation(core->getObserverHeliocentricEclipticPos()) * 180. / M_PI;
3067 break;
3068 case GraphAngularSizeVsTime:
3069 {
3070 value = ssObj->getAngularSize(core) * 360. / M_PI;
3071 if (value < 1.)
3072 value *= 60.;
3073 break;
3074 }
3075 case GraphPhaseAngleVsTime:
3076 value = ssObj->getPhaseAngle(core->getObserverHeliocentricEclipticPos()) * 180. / M_PI;
3077 break;
3078 case GraphHDistanceVsTime:
3079 value = ssObj->getHeliocentricEclipticPos().length();
3080 break;
3081 case GraphTransitAltitudeVsTime:
3082 {
3083 double az, alt;
3084 bool sign;
3085 StelUtils::rectToSphe(&az, &alt, ssObj->getAltAzPosAuto(core));
3086 StelUtils::radToDecDeg(alt, sign, value); // convert to degrees
3087 if (!sign)
3088 value *= -1;
3089 break;
3090 }
3091 case GraphRightAscensionVsTime:
3092 {
3093 double dec_equ, ra_equ;
3094 StelUtils::rectToSphe(&ra_equ, &dec_equ, ssObj->getEquinoxEquatorialPos(core));
3095 ra_equ = 2.*M_PI-ra_equ;
3096 value = ra_equ*12./M_PI;
3097 if (value>24.)
3098 value -= 24.;
3099 break;
3100 }
3101 case GraphDeclinationVsTime:
3102 {
3103 double dec_equ, ra_equ;
3104 bool sign;
3105 StelUtils::rectToSphe(&ra_equ, &dec_equ, ssObj->getEquinoxEquatorialPos(core));
3106 StelUtils::radToDecDeg(dec_equ, sign, value); // convert to degrees
3107 if (!sign)
3108 value *= -1;
3109 break;
3110 }
3111 }
3112 return value;
3113 }
3114
populateFunctionsList()3115 void AstroCalcDialog::populateFunctionsList()
3116 {
3117 Q_ASSERT(ui->graphsFirstComboBox);
3118 Q_ASSERT(ui->graphsSecondComboBox);
3119
3120 typedef QPair<QString, GraphsTypes> graph;
3121 const QList<graph> functions = {
3122 { q_("Magnitude vs. Time"), GraphMagnitudeVsTime},
3123 { q_("Phase vs. Time"), GraphPhaseVsTime},
3124 { q_("Distance vs. Time"), GraphDistanceVsTime},
3125 { q_("Elongation vs. Time"), GraphElongationVsTime},
3126 { q_("Angular size vs. Time"), GraphAngularSizeVsTime},
3127 { q_("Phase angle vs. Time"), GraphPhaseAngleVsTime},
3128 // TRANSLATORS: The phrase "Heliocentric distance" may be long in some languages and you can short it to use in the drop-down list.
3129 { q_("Heliocentric distance vs. Time"), GraphHDistanceVsTime},
3130 // TRANSLATORS: The phrase "Transit altitude" may be long in some languages and you can short it to use in the drop-down list.
3131 { q_("Transit altitude vs. Time"), GraphTransitAltitudeVsTime},
3132 // TRANSLATORS: The phrase "Right ascension" may be long in some languages and you can short it to use in the drop-down list.
3133 { q_("Right ascension vs. Time"), GraphRightAscensionVsTime},
3134 { q_("Declination vs. Time"), GraphDeclinationVsTime}};
3135
3136 QComboBox* first = ui->graphsFirstComboBox;
3137 QComboBox* second = ui->graphsSecondComboBox;
3138 first->blockSignals(true);
3139 second->blockSignals(true);
3140
3141 int indexF = first->currentIndex();
3142 QVariant selectedFirstId = first->itemData(indexF);
3143 int indexS = second->currentIndex();
3144 QVariant selectedSecondId = second->itemData(indexS);
3145
3146 first->clear();
3147 second->clear();
3148
3149 for (const auto& f : functions)
3150 {
3151 first->addItem(f.first, f.second);
3152 second->addItem(f.first, f.second);
3153 }
3154
3155 indexF = first->findData(selectedFirstId, Qt::UserRole, Qt::MatchCaseSensitive);
3156 if (indexF < 0)
3157 indexF = first->findData(conf->value("astrocalc/graphs_first_id", GraphMagnitudeVsTime).toInt(), Qt::UserRole, Qt::MatchCaseSensitive);
3158 first->setCurrentIndex(indexF);
3159 first->model()->sort(0);
3160
3161 indexS = second->findData(selectedSecondId, Qt::UserRole, Qt::MatchCaseSensitive);
3162 if (indexS < 0)
3163 indexS = second->findData(conf->value("astrocalc/graphs_second_id", GraphPhaseVsTime).toInt(), Qt::UserRole, Qt::MatchCaseSensitive);
3164 second->setCurrentIndex(indexS);
3165 second->model()->sort(0);
3166
3167 first->blockSignals(false);
3168 second->blockSignals(false);
3169 }
3170
prepareXVsTimeAxesAndGraph()3171 void AstroCalcDialog::prepareXVsTimeAxesAndGraph()
3172 {
3173 QString distMU = qc_("AU", "distance, astronomical unit");
3174 QString asMU = QString("'");
3175
3176 PlanetP ssObj = solarSystem->searchByEnglishName(ui->graphsCelestialBodyComboBox->currentData().toString());
3177 if (!ssObj.isNull())
3178 {
3179 if (ssObj->getJ2000EquatorialPos(core).length() < 0.1)
3180 {
3181 // TRANSLATORS: Mega-meter (SI symbol: Mm; Mega-meter is a unit of length in the metric system,
3182 // equal to one million meters)
3183 distMU = q_("Mm");
3184 }
3185 if ((ssObj->getAngularSize(core) * 360. / M_PI) < 1.) asMU = QString("\"");
3186 }
3187
3188 bool direction1 = false;
3189 bool direction2 = false;
3190
3191 switch (ui->graphsFirstComboBox->currentData().toInt())
3192 {
3193 case GraphMagnitudeVsTime:
3194 yAxis1Legend = q_("Magnitude");
3195 if (minY1 < -1000.) minY1 = 0.0;
3196 if (maxY1 > 1000.) maxY1 = 6.0;
3197 direction1 = true;
3198 break;
3199 case GraphPhaseVsTime:
3200 yAxis1Legend = QString("%1, %").arg(q_("Phase"));
3201 if (minY1 < -1000.) minY1 = 0.0;
3202 if (maxY1 > 1000.) maxY1 = 100.0;
3203 break;
3204 case GraphDistanceVsTime:
3205 yAxis1Legend = QString("%1, %2").arg(q_("Distance"), distMU);
3206 if (minY1 < -1000.) minY1 = 0.0;
3207 if (maxY1 > 1000.) maxY1 = 50.0;
3208 break;
3209 case GraphElongationVsTime:
3210 yAxis1Legend = QString("%1, %2").arg(q_("Elongation"), QChar(0x00B0));
3211 if (minY1 < -1000.) minY1 = 0.0;
3212 if (maxY1 > 1000.) maxY1 = 180.0;
3213 break;
3214 case GraphAngularSizeVsTime:
3215 yAxis1Legend = QString("%1, %2").arg(q_("Angular size"), asMU);
3216 if (minY1 < -1000.) minY1 = 0.0;
3217 if (maxY1 > 1000.) maxY1 = 30.0;
3218 break;
3219 case GraphPhaseAngleVsTime:
3220 yAxis1Legend = QString("%1, %2").arg(q_("Phase angle"), QChar(0x00B0));
3221 if (minY1 < -1000.) minY1 = 0.0;
3222 if (maxY1 > 1000.) maxY1 = 180.0;
3223 break;
3224 case GraphHDistanceVsTime:
3225 // TRANSLATORS: The phrase "Heliocentric distance" may be long in some languages and you can abbreviate it.
3226 yAxis1Legend = QString("%1, %2").arg(qc_("Heliocentric distance","axis name"), distMU);
3227 if (minY1 < -1000.) minY1 = 0.0;
3228 if (maxY1 > 1000.) maxY1 = 50.0;
3229 break;
3230 case GraphTransitAltitudeVsTime:
3231 // TRANSLATORS: The phrase "Transit altitude" may be long in some languages and you can abbreviate it.
3232 yAxis1Legend = QString("%1, %2").arg(qc_("Transit altitude","axis name"), QChar(0x00B0));
3233 if (minY1 < -1000.) minY1 = 0.0;
3234 if (maxY1 > 1000.) maxY1 = 90.0;
3235 break;
3236 case GraphRightAscensionVsTime:
3237 // TRANSLATORS: The phrase "Right ascension" may be long in some languages and you can abbreviate it.
3238 yAxis1Legend = QString("%1, %2").arg(qc_("Right ascension","axis name"), qc_("h","time"));
3239 if (minY1 < -1000.) minY1 = 0.0;
3240 if (maxY1 > 1000.) maxY1 = 24.0;
3241 break;
3242 case GraphDeclinationVsTime:
3243 yAxis1Legend = QString("%1, %2").arg(q_("Declination"), QChar(0x00B0));
3244 if (minY1 < -1000.) minY1 = -90.0;
3245 if (maxY1 > 1000.) maxY1 = 90.0;
3246 break;
3247 }
3248
3249 switch (ui->graphsSecondComboBox->currentData().toInt())
3250 {
3251 case GraphMagnitudeVsTime:
3252 yAxis2Legend = q_("Magnitude");
3253 if (minY2 < -1000.) minY2 = 0.0;
3254 if (maxY2 > 1000.) maxY2 = 6.0;
3255 direction2 = true;
3256 break;
3257 case GraphPhaseVsTime:
3258 yAxis2Legend = QString("%1, %").arg(q_("Phase"));
3259 if (minY2 < -1000.) minY2 = 0.0;
3260 if (maxY2 > 1000.) maxY2 = 100.0;
3261 break;
3262 case GraphDistanceVsTime:
3263 yAxis2Legend = QString("%1, %2").arg(q_("Distance"), distMU);
3264 if (minY2 < -1000.) minY2 = 0.0;
3265 if (maxY2 > 1000.) maxY2 = 50.0;
3266 break;
3267 case GraphElongationVsTime:
3268 yAxis2Legend = QString("%1, %2").arg(q_("Elongation"), QChar(0x00B0));
3269 if (minY2 < -1000.) minY2 = 0.0;
3270 if (maxY2 > 1000.) maxY2 = 180.0;
3271 break;
3272 case GraphAngularSizeVsTime:
3273 yAxis2Legend = QString("%1, %2").arg(q_("Angular size"), asMU);
3274 if (minY2 < -1000.) minY2 = 0.0;
3275 if (maxY2 > 1000.) maxY2 = 30.0;
3276 break;
3277 case GraphPhaseAngleVsTime:
3278 yAxis2Legend = QString("%1, %2").arg(q_("Phase angle"), QChar(0x00B0));
3279 if (minY2 < -1000.) minY2 = 0.0;
3280 if (maxY2 > 1000.) maxY2 = 180.0;
3281 break;
3282 case GraphHDistanceVsTime:
3283 // TRANSLATORS: The phrase "Heliocentric distance" may be long in some languages and you can short it.
3284 yAxis2Legend = QString("%1, %2").arg(q_("Heliocentric distance"), distMU);
3285 if (minY2 < -1000.) minY2 = 0.0;
3286 if (maxY2 > 1000.) maxY2 = 50.0;
3287 break;
3288 case GraphTransitAltitudeVsTime:
3289 // TRANSLATORS: The phrase "Transit altitude" may be long in some languages and you can short it.
3290 yAxis2Legend = QString("%1, %2").arg(q_("Transit altitude"), QChar(0x00B0));
3291 if (minY2 < -1000.) minY2 = 0.0;
3292 if (maxY2 > 1000.) maxY2 = 90.0;
3293 break;
3294 case GraphRightAscensionVsTime:
3295 // TRANSLATORS: The phrase "Right ascension" may be long in some languages and you can short it.
3296 yAxis2Legend = QString("%1, %2").arg(qc_("Right ascension","axis name"), qc_("h","time"));
3297 if (minY2 < -1000.) minY2 = 0.0;
3298 if (maxY2 > 1000.) maxY2 = 24.0;
3299 break;
3300 case GraphDeclinationVsTime:
3301 yAxis2Legend = QString("%1, %2").arg(q_("Declination"), QChar(0x00B0));
3302 if (minY2 < -1000.) minY2 = -90.0;
3303 if (maxY2 > 1000.) maxY2 = 90.0;
3304 break;
3305 }
3306
3307 QColor axisColor(Qt::white);
3308 QPen axisPen(axisColor, 1);
3309 QColor axisColorL(Qt::green);
3310 QPen axisPenL(axisColorL, 1);
3311 QColor axisColorR(Qt::yellow);
3312 QPen axisPenR(axisColorR, 1);
3313
3314 ui->graphsPlot->setLocale(QLocale(localeMgr->getAppLanguage()));
3315 ui->graphsPlot->yAxis->setLabel(yAxis1Legend);
3316 ui->graphsPlot->yAxis2->setLabel(yAxis2Legend);
3317
3318 int dYear = (static_cast<int>(solarSystem->getEarth()->getSiderealPeriod()*graphsDuration) + 1) * 86400;
3319 ui->graphsPlot->xAxis->setRange(0, dYear);
3320 ui->graphsPlot->xAxis->setScaleType(QCPAxis::stLinear);
3321 ui->graphsPlot->xAxis->setTickLabelType(QCPAxis::ltDateTime);
3322 ui->graphsPlot->xAxis->setLabelColor(axisColor);
3323 ui->graphsPlot->xAxis->setTickLabelColor(axisColor);
3324 ui->graphsPlot->xAxis->setBasePen(axisPen);
3325 ui->graphsPlot->xAxis->setTickPen(axisPen);
3326 ui->graphsPlot->xAxis->setSubTickPen(axisPen);
3327 ui->graphsPlot->xAxis->setDateTimeFormat("d\nMMM");
3328 ui->graphsPlot->xAxis->setDateTimeSpec(Qt::UTC);
3329 ui->graphsPlot->xAxis->setAutoTicks(true);
3330 ui->graphsPlot->xAxis->setAutoTickCount(20);
3331
3332 ui->graphsPlot->yAxis->setRange(minY1, maxY1);
3333 ui->graphsPlot->yAxis->setScaleType(QCPAxis::stLinear);
3334 ui->graphsPlot->yAxis->setLabelColor(axisColorL);
3335 ui->graphsPlot->yAxis->setTickLabelColor(axisColorL);
3336 ui->graphsPlot->yAxis->setBasePen(axisPenL);
3337 ui->graphsPlot->yAxis->setTickPen(axisPenL);
3338 ui->graphsPlot->yAxis->setSubTickPen(axisPenL);
3339 ui->graphsPlot->yAxis->setRangeReversed(direction1);
3340
3341 ui->graphsPlot->yAxis2->setRange(minY2, maxY2);
3342 ui->graphsPlot->yAxis2->setScaleType(QCPAxis::stLinear);
3343 ui->graphsPlot->yAxis2->setLabelColor(axisColorR);
3344 ui->graphsPlot->yAxis2->setTickLabelColor(axisColorR);
3345 ui->graphsPlot->yAxis2->setBasePen(axisPenR);
3346 ui->graphsPlot->yAxis2->setTickPen(axisPenR);
3347 ui->graphsPlot->yAxis2->setSubTickPen(axisPenR);
3348 ui->graphsPlot->yAxis2->setRangeReversed(direction2);
3349 ui->graphsPlot->yAxis2->setVisible(true);
3350
3351 ui->graphsPlot->clearGraphs();
3352 ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis);
3353 ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
3354 ui->graphsPlot->graph(0)->setPen(QPen(Qt::red, 1));
3355 ui->graphsPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
3356 ui->graphsPlot->graph(0)->rescaleAxes(true);
3357
3358 ui->graphsPlot->addGraph(ui->graphsPlot->xAxis, ui->graphsPlot->yAxis2);
3359 ui->graphsPlot->setBackground(QBrush(QColor(86, 87, 90)));
3360 ui->graphsPlot->graph(1)->setPen(QPen(Qt::yellow, 1));
3361 ui->graphsPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
3362 ui->graphsPlot->graph(1)->rescaleAxes(true);
3363 }
3364
prepareMonthlyElevationAxesAndGraph()3365 void AstroCalcDialog::prepareMonthlyElevationAxesAndGraph()
3366 {
3367 QString xAxisStr = q_("Date");
3368 QString yAxisStr = QString("%1, %2").arg(q_("Altitude"), QChar(0x00B0));
3369
3370 QColor axisColor(Qt::white);
3371 QPen axisPen(axisColor, 1);
3372
3373 ui->monthlyElevationGraph->setLocale(QLocale(localeMgr->getAppLanguage()));
3374 ui->monthlyElevationGraph->xAxis->setLabel(xAxisStr);
3375 ui->monthlyElevationGraph->yAxis->setLabel(yAxisStr);
3376
3377 int dYear = (static_cast<int>(solarSystem->getEarth()->getSiderealPeriod()) + 1) * 86400;
3378 ui->monthlyElevationGraph->xAxis->setRange(0, dYear);
3379 ui->monthlyElevationGraph->xAxis->setScaleType(QCPAxis::stLinear);
3380 ui->monthlyElevationGraph->xAxis->setTickLabelType(QCPAxis::ltDateTime);
3381 ui->monthlyElevationGraph->xAxis->setLabelColor(axisColor);
3382 ui->monthlyElevationGraph->xAxis->setTickLabelColor(axisColor);
3383 ui->monthlyElevationGraph->xAxis->setBasePen(axisPen);
3384 ui->monthlyElevationGraph->xAxis->setTickPen(axisPen);
3385 ui->monthlyElevationGraph->xAxis->setSubTickPen(axisPen);
3386 ui->monthlyElevationGraph->xAxis->setDateTimeFormat("d\nMMM");
3387 ui->monthlyElevationGraph->xAxis->setDateTimeSpec(Qt::UTC);
3388 ui->monthlyElevationGraph->xAxis->setAutoTicks(true);
3389 ui->monthlyElevationGraph->xAxis->setAutoTickCount(15);
3390
3391 ui->monthlyElevationGraph->yAxis->setRange(minYme, maxYme);
3392 ui->monthlyElevationGraph->yAxis->setScaleType(QCPAxis::stLinear);
3393 ui->monthlyElevationGraph->yAxis->setLabelColor(axisColor);
3394 ui->monthlyElevationGraph->yAxis->setTickLabelColor(axisColor);
3395 ui->monthlyElevationGraph->yAxis->setBasePen(axisPen);
3396 ui->monthlyElevationGraph->yAxis->setTickPen(axisPen);
3397 ui->monthlyElevationGraph->yAxis->setSubTickPen(axisPen);
3398
3399 ui->monthlyElevationGraph->clearGraphs();
3400 ui->monthlyElevationGraph->addGraph();
3401 ui->monthlyElevationGraph->setBackground(QBrush(QColor(86, 87, 90)));
3402 ui->monthlyElevationGraph->graph(0)->setPen(QPen(Qt::red, 1));
3403 ui->monthlyElevationGraph->graph(0)->setLineStyle(QCPGraph::lsLine);
3404 ui->monthlyElevationGraph->graph(0)->rescaleAxes(true);
3405 }
3406
syncMonthlyElevationTime()3407 void AstroCalcDialog::syncMonthlyElevationTime()
3408 {
3409 ui->monthlyElevationTimeInfo->setText(QString("%1 %2").arg(QString::number(ui->monthlyElevationTime->value()), qc_("h", "time")));
3410 }
3411
updateMonthlyElevationTime()3412 void AstroCalcDialog::updateMonthlyElevationTime()
3413 {
3414 syncMonthlyElevationTime();
3415
3416 conf->setValue("astrocalc/me_time", ui->monthlyElevationTime->value());
3417
3418 drawMonthlyElevationGraph();
3419 }
3420
saveMonthlyElevationPositiveFlag(bool state)3421 void AstroCalcDialog::saveMonthlyElevationPositiveFlag(bool state)
3422 {
3423 if (plotMonthlyElevationPositive!=state)
3424 {
3425 plotMonthlyElevationPositive = state;
3426 conf->setValue("astrocalc/me_positive_only", plotMonthlyElevationPositive);
3427
3428 drawMonthlyElevationGraph();
3429 }
3430 }
3431
saveMonthlyElevationPositiveLimit(int limit)3432 void AstroCalcDialog::saveMonthlyElevationPositiveLimit(int limit)
3433 {
3434 if (monthlyElevationPositiveLimit!=limit)
3435 {
3436 monthlyElevationPositiveLimit = limit;
3437 conf->setValue("astrocalc/me_positive_limit", monthlyElevationPositiveLimit);
3438 drawMonthlyElevationGraph();
3439 }
3440 }
3441
drawMonthlyElevationGraph()3442 void AstroCalcDialog::drawMonthlyElevationGraph()
3443 {
3444 ui->monthlyElevationGraph->setToolTip("");
3445
3446 // Avoid crash!
3447 if (core->getCurrentPlanet()->getEnglishName().contains("->")) // We are on the spaceship!
3448 return;
3449
3450 // special case - plot the graph when tab is visible
3451 //..
3452 // we got notified about a reason to redraw the plot, but dialog was
3453 // not visible. which means we must redraw when becoming visible again!
3454 if (!dialog->isVisible() && plotMonthlyElevation)
3455 {
3456 graphPlotNeedsRefresh = true;
3457 return;
3458 }
3459
3460 if (!plotMonthlyElevation) return;
3461
3462 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
3463 if (!selectedObjects.isEmpty())
3464 {
3465 // X axis - time; Y axis - altitude
3466 QList<double> aX, aY;
3467 StelObjectP selectedObject = selectedObjects[0];
3468 ui->monthlyElevationTitle->setText(selectedObject->getNameI18n());
3469 if (selectedObject->getType() == "Satellite")
3470 {
3471 ui->monthlyElevationGraph->graph(0)->data()->clear();
3472 ui->monthlyElevationGraph->replot();
3473 return;
3474 }
3475
3476 const double currentJD = core->getJD();
3477 int hour = ui->monthlyElevationTime->value();
3478 double az, alt, deg, startJD, JD, ltime;
3479 bool sign;
3480 int year, month, day;
3481 StelUtils::getDateFromJulianDay(currentJD, &year, &month, &day);
3482 StelUtils::getJDFromDate(&startJD, year, 1, 1, hour, 0, 0);
3483 startJD -= core->getUTCOffset(startJD)/24; // Time zone correction
3484 int dYear = static_cast<int>(core->getCurrentPlanet()->getSiderealPeriod()/5.) + 3;
3485 for (int i = -2; i <= dYear; i++)
3486 {
3487 JD = startJD + i*5;
3488 ltime = (JD - startJD) * StelCore::ONE_OVER_JD_SECOND;
3489 aX.append(ltime);
3490 core->setJD(JD);
3491 StelUtils::rectToSphe(&az, &alt, selectedObject->getAltAzPosAuto(core));
3492 StelUtils::radToDecDeg(alt, sign, deg);
3493 if (!sign) deg *= -1;
3494 aY.append(deg);
3495 core->update(0.0);
3496 }
3497 core->setJD(currentJD);
3498
3499 QVector<double> x = aX.toVector(), y = aY.toVector();
3500 minYme = *std::min_element(aY.begin(), aY.end()) - 2.0;
3501 maxYme = *std::max_element(aY.begin(), aY.end()) + 2.0;
3502
3503 if (plotMonthlyElevationPositive && minYme<monthlyElevationPositiveLimit)
3504 minYme = monthlyElevationPositiveLimit;
3505
3506 prepareMonthlyElevationAxesAndGraph();
3507
3508 QString name = selectedObject->getNameI18n();
3509 if (name.isEmpty())
3510 {
3511 QString otype = selectedObject->getType();
3512 if (otype == "Nebula")
3513 {
3514 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
3515 if (name.isEmpty())
3516 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
3517 }
3518 if (otype == "Star" || otype=="Pulsar")
3519 selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
3520 }
3521 ui->monthlyElevationGraph->graph(0)->setData(x, y);
3522 ui->monthlyElevationGraph->graph(0)->setName(name);
3523 ui->monthlyElevationGraph->replot();
3524 ui->monthlyElevationGraph->setToolTip(name);
3525 }
3526
3527 // clean up the data when selection is removed
3528 if (!objectMgr->getWasSelected())
3529 {
3530 ui->monthlyElevationGraph->graph(0)->data()->clear();
3531 ui->monthlyElevationGraph->replot();
3532 }
3533 }
3534
3535 // click inside AltVsTime graph area sets new current time
altTimeClick(QMouseEvent * event)3536 void AstroCalcDialog::altTimeClick(QMouseEvent* event)
3537 {
3538 Qt::MouseButtons buttons = event->buttons();
3539 if (!(buttons & Qt::LeftButton)) return;
3540
3541 double x = ui->altVsTimePlot->xAxis->pixelToCoord(event->pos().x());
3542 double y = ui->altVsTimePlot->yAxis->pixelToCoord(event->pos().y());
3543
3544 if (ui->altVsTimePlot->xAxis->range().contains(x) && ui->altVsTimePlot->yAxis->range().contains(y) )
3545 {
3546 setClickedTime(x);
3547 }
3548 }
3549
3550 // click inside AziVsTime graph area sets new current time
aziTimeClick(QMouseEvent * event)3551 void AstroCalcDialog::aziTimeClick(QMouseEvent* event)
3552 {
3553 Qt::MouseButtons buttons = event->buttons();
3554 if (!(buttons & Qt::LeftButton)) return;
3555
3556 double x = ui->aziVsTimePlot->xAxis->pixelToCoord(event->pos().x());
3557 double y = ui->aziVsTimePlot->yAxis->pixelToCoord(event->pos().y());
3558
3559 if (ui->aziVsTimePlot->xAxis->range().contains(x) && ui->aziVsTimePlot->yAxis->range().contains(y))
3560 {
3561 setClickedTime(x);
3562 }
3563 }
3564
3565
setClickedTime(double posx)3566 void AstroCalcDialog::setClickedTime(double posx)
3567 {
3568 double JD = core->getJD();
3569 double shift = core->getUTCOffset(JD) / 24;
3570 int noonJD = static_cast<int>(JD + shift);
3571 JD = posx / 86400.0 + noonJD - 0.5 - shift;
3572
3573 core->setRealTimeSpeed();
3574 core->setJD(JD);
3575 drawCurrentTimeDiagram();
3576
3577 // if object is tracked, we make our own (smoothed) movement
3578 if (mvMgr->getFlagTracking())
3579 {
3580 StelObjectP obj = objectMgr->getSelectedObject()[0];
3581 mvMgr->moveToObject(obj, 0.4f);
3582 }
3583 }
3584
3585 // When dialog becomes visible: check if there is a
3586 // graph plot to refresh
handleVisibleEnabled()3587 void AstroCalcDialog::handleVisibleEnabled()
3588 {
3589 if (dialog->isVisible())
3590 {
3591 // check which graph needs refresh (only one is set, if any)
3592 if (graphPlotNeedsRefresh)
3593 {
3594 if (plotAltVsTime || plotAziVsTime)
3595 drawCurrentTimeDiagram();
3596 if (plotMonthlyElevation)
3597 drawMonthlyElevationGraph();
3598 if (plotAngularDistanceGraph)
3599 drawAngularDistanceGraph();
3600 }
3601 else
3602 drawCurrentTimeDiagram();
3603 }
3604
3605 graphPlotNeedsRefresh = false;
3606 }
3607
mouseOverLine(QMouseEvent * event)3608 void AstroCalcDialog::mouseOverLine(QMouseEvent* event)
3609 {
3610 const double x = ui->altVsTimePlot->xAxis->pixelToCoord(event->pos().x());
3611 const double y = ui->altVsTimePlot->yAxis->pixelToCoord(event->pos().y());
3612
3613 QCPAbstractPlottable* abstractGraph = ui->altVsTimePlot->plottableAt(event->pos(), false);
3614 QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
3615
3616 if (ui->altVsTimePlot->xAxis->range().contains(x) && ui->altVsTimePlot->yAxis->range().contains(y))
3617 {
3618 QString info = "";
3619 if (graph)
3620 {
3621 double JD;
3622 if (graph->name() == "[Now]")
3623 {
3624 JD = core->getJD();
3625 info = q_("Now about %1").arg(StelUtils::jdToQDateTime(JD + core->getUTCOffset(JD)/24).toString("H:mm"));
3626 }
3627 else if (graph->name() == "[Transit]")
3628 {
3629 JD = transitX / 86400.0 + static_cast<int>(core->getJD()) - 0.5;
3630 info = q_("Passage of meridian at approximately %1").arg(StelUtils::jdToQDateTime(JD - core->getUTCOffset(JD)).toString("H:mm"));
3631 }
3632 else if (graph->name() == "[Sun]")
3633 info = solarSystem->getSun()->getNameI18n();
3634 else if (graph->name() == "[Moon]")
3635 info = solarSystem->getMoon()->getNameI18n();
3636 else if (graph->name() == "[Civil Twilight]")
3637 info = q_("Line of civil twilight");
3638 else if (graph->name() == "[Nautical Twilight]")
3639 info = q_("Line of nautical twilight");
3640 else if (graph->name() == "[Astronomical Twilight]")
3641 info = q_("Line of astronomical twilight");
3642 else
3643 {
3644 JD = x / 86400.0 + static_cast<int>(core->getJD()) - 0.5;
3645 QString LT = StelUtils::jdToQDateTime(JD - core->getUTCOffset(JD)).toString("H:mm");
3646
3647 if (StelApp::getInstance().getFlagShowDecimalDegrees())
3648 info = QString("%1<br />%2: %3<br />%4: %5%6").arg(ui->altVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Altitude"), QString::number(y, 'f', 2), QChar(0x00B0));
3649 else
3650 info = QString("%1<br />%2: %3<br />%4: %5").arg(ui->altVsTimePlot->graph(0)->name(), q_("Local Time"), LT, q_("Altitude"), StelUtils::decDegToDmsStr(y));
3651 }
3652 }
3653 ui->altVsTimePlot->setToolTip(info);
3654 }
3655
3656 ui->altVsTimePlot->update();
3657 ui->altVsTimePlot->replot();
3658 }
3659
setPhenomenaHeaderNames()3660 void AstroCalcDialog::setPhenomenaHeaderNames()
3661 {
3662 phenomenaHeader.clear();
3663 phenomenaHeader << q_("Phenomenon");
3664 phenomenaHeader << q_("Date and Time");
3665 phenomenaHeader << q_("Object 1");
3666 // TRANSLATORS: Magnitude of object 1
3667 phenomenaHeader << q_("Mag. 1");
3668 phenomenaHeader << q_("Object 2");
3669 // TRANSLATORS: Magnitude of object 2
3670 phenomenaHeader << q_("Mag. 2");
3671 phenomenaHeader << q_("Separation");
3672 phenomenaHeader << q_("Elevation");
3673 phenomenaHeader << q_("Solar Elongation");
3674 phenomenaHeader << q_("Lunar Elongation");
3675 ui->phenomenaTreeWidget->setHeaderLabels(phenomenaHeader);
3676 adjustPhenomenaColumns();
3677 }
3678
adjustPhenomenaColumns()3679 void AstroCalcDialog::adjustPhenomenaColumns()
3680 {
3681 // adjust the column width
3682 for (int i = 0; i < PhenomenaCount; ++i)
3683 {
3684 ui->phenomenaTreeWidget->resizeColumnToContents(i);
3685 }
3686 }
3687
initListPhenomena()3688 void AstroCalcDialog::initListPhenomena()
3689 {
3690 ui->phenomenaTreeWidget->clear();
3691 ui->phenomenaTreeWidget->setColumnCount(PhenomenaCount);
3692 setPhenomenaHeaderNames();
3693 ui->phenomenaTreeWidget->header()->setSectionsMovable(false);
3694 ui->phenomenaTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
3695 }
3696
selectCurrentPhenomen(const QModelIndex & modelIndex)3697 void AstroCalcDialog::selectCurrentPhenomen(const QModelIndex& modelIndex)
3698 {
3699 // Find the object
3700 QString name = ui->object1ComboBox->currentData().toString();
3701 if (modelIndex.sibling(modelIndex.row(), PhenomenaType).data().toString().contains(q_("Opposition"), Qt::CaseInsensitive))
3702 name = modelIndex.sibling(modelIndex.row(), PhenomenaObject2).data().toString();
3703 double JD = modelIndex.sibling(modelIndex.row(), PhenomenaDate).data(Qt::UserRole).toDouble();
3704
3705 if (objectMgr->findAndSelectI18n(name) || objectMgr->findAndSelect(name))
3706 {
3707 core->setJD(JD);
3708 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
3709 if (!newSelected.empty())
3710 {
3711 mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
3712 mvMgr->setFlagTracking(true);
3713 }
3714 }
3715 }
3716
calculatePhenomena()3717 void AstroCalcDialog::calculatePhenomena()
3718 {
3719 const QString currentPlanet = ui->object1ComboBox->currentData().toString();
3720 const double separation = ui->allowedSeparationSpinBox->valueDegrees();
3721 const bool opposition = ui->phenomenaOppositionCheckBox->isChecked();
3722 const bool perihelion = ui->phenomenaPerihelionAphelionCheckBox->isChecked();
3723
3724 initListPhenomena();
3725
3726 double startJD = StelUtils::qDateTimeToJd(QDateTime(ui->phenomenFromDateEdit->date()));
3727 double stopJD = StelUtils::qDateTimeToJd(QDateTime(ui->phenomenToDateEdit->date().addDays(1)));
3728 if (stopJD<=startJD) // Stop warming atmosphere!..
3729 return;
3730
3731 QList<PlanetP> objects;
3732 Q_ASSERT(objects.isEmpty());
3733 QList<PlanetP> allObjects = solarSystem->getAllPlanets();
3734
3735 QList<NebulaP> dso;
3736 Q_ASSERT(dso.isEmpty());
3737 QVector<NebulaP> allDSO = dsoMgr->getAllDeepSkyObjects();
3738
3739 QList<StelObjectP> star, doubleStar, variableStar;
3740 Q_ASSERT(star.isEmpty());
3741 Q_ASSERT(doubleStar.isEmpty());
3742 Q_ASSERT(variableStar.isEmpty());
3743 QList<StelObjectP> hipStars = starMgr->getHipparcosStars();
3744 QList<StelObjectP> carbonStars = starMgr->getHipparcosCarbonStars();
3745 QList<StelObjectP> bariumStars = starMgr->getHipparcosBariumStars();
3746 QList<StelACStarData> doubleHipStars = starMgr->getHipparcosDoubleStars();
3747 QList<StelACStarData> variableHipStars = starMgr->getHipparcosVariableStars();
3748
3749 int obj2Type = ui->object2ComboBox->currentData().toInt();
3750 switch (obj2Type)
3751 {
3752 case PHCSolarSystem: // All Solar system objects
3753 for (const auto& object : allObjects)
3754 {
3755 if (object->getPlanetType() != Planet::isUNDEFINED && object->getPlanetType() != Planet::isObserver)
3756 objects.append(object);
3757 }
3758 break;
3759 case PHCPlanets: // Planets
3760 for (const auto& object : allObjects)
3761 {
3762 if (object->getPlanetType() == Planet::isPlanet && object->getEnglishName() != core->getCurrentPlanet()->getEnglishName() && object->getEnglishName() != currentPlanet)
3763 objects.append(object);
3764 }
3765 break;
3766 case PHCAsteroids:
3767 case PHCPlutinos:
3768 case PHCComets:
3769 case PHCDwarfPlanets:
3770 case PHCCubewanos:
3771 case PHCScatteredDiscObjects:
3772 case PHCOortCloudObjects:
3773 case PHCSednoids:
3774 case PHCInterstellarObjects:
3775 {
3776 static const QMap<int, Planet::PlanetType>map = {
3777 {PHCAsteroids, Planet::isAsteroid},
3778 {PHCPlutinos, Planet::isPlutino},
3779 {PHCComets, Planet::isComet},
3780 {PHCDwarfPlanets, Planet::isDwarfPlanet},
3781 {PHCCubewanos, Planet::isCubewano},
3782 {PHCScatteredDiscObjects, Planet::isSDO},
3783 {PHCOortCloudObjects, Planet::isOCO},
3784 {PHCSednoids, Planet::isSednoid},
3785 {PHCInterstellarObjects, Planet::isInterstellar}};
3786 const Planet::PlanetType pType = map.value(obj2Type, Planet::isUNDEFINED);
3787
3788 for (const auto& object : allObjects)
3789 {
3790 if (object->getPlanetType() == pType)
3791 objects.append(object);
3792 }
3793 break;
3794 }
3795 case PHCBrightStars: // Stars
3796 case PHCBrightCarbonStars: // Bright carbon stars
3797 case PHCBrightBariumStars: // Bright barium stars
3798 {
3799 QList<StelObjectP> stars;
3800 if (obj2Type==PHCBrightStars)
3801 stars = hipStars;
3802 else if (obj2Type==PHCBrightBariumStars)
3803 stars = bariumStars;
3804 else
3805 stars = carbonStars;
3806 for (const auto& object : qAsConst(stars))
3807 {
3808 if (object->getVMagnitude(core) < (brightLimit - 5.0))
3809 star.append(object);
3810 }
3811 break;
3812 }
3813 case PHCBrightDoubleStars: // Double stars
3814 for (const auto& object : doubleHipStars)
3815 {
3816 if (object.firstKey()->getVMagnitude(core) < (brightLimit - 5.0))
3817 star.append(object.firstKey());
3818 }
3819 break;
3820 case PHCBrightVariableStars: // Variable stars
3821 for (const auto& object : variableHipStars)
3822 {
3823 if (object.firstKey()->getVMagnitude(core) < (brightLimit - 5.0))
3824 star.append(object.firstKey());
3825 }
3826 break;
3827 case PHCBrightStarClusters: // Star clusters
3828 for (const auto& object : allDSO)
3829 {
3830 if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebCl || object->getDSOType() == Nebula::NebOc || object->getDSOType() == Nebula::NebGc || object->getDSOType() == Nebula::NebSA || object->getDSOType() == Nebula::NebSC || object->getDSOType() == Nebula::NebCn))
3831 dso.append(object);
3832 }
3833 break;
3834 case PHCPlanetaryNebulae: // Planetary nebulae
3835 for (const auto& object : allDSO)
3836 {
3837 if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebPn || object->getDSOType() == Nebula::NebPossPN || object->getDSOType() == Nebula::NebPPN))
3838 dso.append(object);
3839 }
3840 break;
3841 case PHCBrightNebulae: // Bright nebulae
3842 for (const auto& object : allDSO)
3843 {
3844 if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebN || object->getDSOType() == Nebula::NebBn || object->getDSOType() == Nebula::NebEn || object->getDSOType() == Nebula::NebRn || object->getDSOType() == Nebula::NebHII || object->getDSOType() == Nebula::NebISM || object->getDSOType() == Nebula::NebCn || object->getDSOType() == Nebula::NebSNR))
3845 dso.append(object);
3846 }
3847 break;
3848 case PHCDarkNebulae: // Dark nebulae
3849 for (const auto& object : allDSO)
3850 {
3851 if (object->getDSOType() == Nebula::NebDn || object->getDSOType() == Nebula::NebMolCld || object->getDSOType() == Nebula::NebYSO)
3852 dso.append(object);
3853 }
3854 break;
3855 case PHCBrightGalaxies: // Galaxies
3856 for (const auto& object : allDSO)
3857 {
3858 if (object->getVMagnitude(core) < brightLimit && (object->getDSOType() == Nebula::NebGx || object->getDSOType() == Nebula::NebAGx || object->getDSOType() == Nebula::NebRGx || object->getDSOType() == Nebula::NebQSO || object->getDSOType() == Nebula::NebPossQSO || object->getDSOType() == Nebula::NebBLL || object->getDSOType() == Nebula::NebBLA || object->getDSOType() == Nebula::NebIGx))
3859 dso.append(object);
3860 }
3861 break;
3862 case PHCSymbioticStars: // Symbiotic stars
3863 for (const auto& object : allDSO)
3864 {
3865 if (object->getDSOType() == Nebula::NebSymbioticStar)
3866 dso.append(object);
3867 }
3868 break;
3869 case PHCEmissionLineStars: // Emission-line stars
3870 for (const auto& object : allDSO)
3871 {
3872 if (object->getDSOType() == Nebula::NebEmissionLineStar)
3873 dso.append(object);
3874 }
3875 break;
3876 case PHCPlanetsSun: // Planets and Sun
3877 for (const auto& object : allObjects)
3878 {
3879 if ((object->getPlanetType() == Planet::isPlanet || object->getPlanetType() == Planet::isStar) && object->getEnglishName() != core->getCurrentPlanet()->getEnglishName() && object->getEnglishName() != currentPlanet)
3880 objects.append(object);
3881 }
3882 break;
3883 case PHCSunPlanetsMoons: // Sun, planets and moons of observer location
3884 {
3885 PlanetP cp = core->getCurrentPlanet();
3886 for (const auto& object : allObjects)
3887 {
3888 if ((object->getPlanetType() == Planet::isPlanet || object->getPlanetType() == Planet::isStar || (object->getParent()==cp && object->getPlanetType()==Planet::isMoon)) && object->getEnglishName() != cp->getEnglishName() && object->getEnglishName() != currentPlanet)
3889 objects.append(object);
3890 }
3891 break;
3892 }
3893 case PHCBrightSolarSystemObjects: // Bright Solar system objects
3894 for (const auto& object : allObjects)
3895 {
3896 if (object->getVMagnitude(core) < (brightLimit + 2.0f) && object->getPlanetType() != Planet::isUNDEFINED)
3897 objects.append(object);
3898 }
3899 break;
3900 case PHCSolarSystemMinorBodies: // Minor bodies of Solar system
3901 for (const auto& object : allObjects)
3902 {
3903 if (object->getPlanetType() != Planet::isUNDEFINED && object->getPlanetType() != Planet::isPlanet && object->getPlanetType() != Planet::isStar && object->getPlanetType() != Planet::isMoon && object->getPlanetType() != Planet::isComet && object->getPlanetType() != Planet::isArtificial && object->getPlanetType() != Planet::isObserver && !object->getEnglishName().contains("Pluto", Qt::CaseInsensitive))
3904 objects.append(object);
3905 }
3906 break;
3907 case PHCMoonsFirstBody: // Moons of first body
3908 PlanetP firstPplanet = solarSystem->searchByEnglishName(currentPlanet);
3909 for (const auto& object : allObjects)
3910 {
3911 if (object->getParent()==firstPplanet && object->getPlanetType() == Planet::isMoon)
3912 objects.append(object);
3913 }
3914 break;
3915 }
3916
3917 PlanetP planet = solarSystem->searchByEnglishName(currentPlanet);
3918 PlanetP sun = solarSystem->getSun();
3919 if (planet)
3920 {
3921 const double currentJD = core->getJD(); // save current JD
3922 startJD = startJD - core->getUTCOffset(startJD) / 24.;
3923 stopJD = stopJD - core->getUTCOffset(stopJD) / 24.;
3924
3925 if (obj2Type == PHCLatestSelectedObject)
3926 {
3927 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
3928 if (!selectedObjects.isEmpty())
3929 {
3930 StelObjectP selectedObject = selectedObjects[0];
3931 if (selectedObject!=planet && selectedObject->getType() != "Satellite")
3932 {
3933 // conjunction
3934 fillPhenomenaTable(findClosestApproach(planet, selectedObject, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, selectedObject, PhenomenaTypeIndex::Conjunction);
3935 // opposition
3936 if (opposition)
3937 fillPhenomenaTable(findClosestApproach(planet, selectedObject, startJD, stopJD, separation, PhenomenaTypeIndex::Opposition), planet, selectedObject, PhenomenaTypeIndex::Opposition);
3938 }
3939 }
3940 }
3941 else if ((obj2Type >= PHCSolarSystem && obj2Type < PHCBrightStars) || (obj2Type >= PHCInterstellarObjects && obj2Type <= PHCMoonsFirstBody))
3942 {
3943 // Solar system objects
3944 for (auto& obj : objects)
3945 {
3946 // conjunction
3947 StelObjectP mObj = qSharedPointerCast<StelObject>(obj);
3948 fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, obj, PhenomenaTypeIndex::Conjunction);
3949 // opposition
3950 if (opposition)
3951 fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Opposition), planet, obj, PhenomenaTypeIndex::Opposition);
3952 // shadows from moons
3953 if (obj2Type==PHCMoonsFirstBody || obj2Type==PHCSolarSystem || obj2Type==PHCBrightSolarSystemObjects)
3954 fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Shadows), planet, obj, PhenomenaTypeIndex::Shadows);
3955 }
3956 }
3957 else if (obj2Type == PHCBrightStars || obj2Type == PHCBrightDoubleStars || obj2Type == PHCBrightVariableStars || obj2Type == PHCBrightCarbonStars || obj2Type == PHCBrightBariumStars)
3958 {
3959 // Stars
3960 for (auto& obj : star)
3961 {
3962 // conjunction
3963 StelObjectP mObj = qSharedPointerCast<StelObject>(obj);
3964 fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, obj, PhenomenaTypeIndex::Conjunction);
3965 }
3966 }
3967 else
3968 {
3969 // Deep-sky objects
3970 for (auto& obj : dso)
3971 {
3972 // conjunction
3973 StelObjectP mObj = qSharedPointerCast<StelObject>(obj);
3974 fillPhenomenaTable(findClosestApproach(planet, mObj, startJD, stopJD, separation, PhenomenaTypeIndex::Conjunction), planet, obj);
3975 }
3976 }
3977
3978 if (planet!=sun && planet->getPlanetType()!=Planet::isMoon)
3979 {
3980 StelObjectP mObj = qSharedPointerCast<StelObject>(sun);
3981 if (planet->getHeliocentricEclipticPos().length()<core->getCurrentPlanet()->getHeliocentricEclipticPos().length())
3982 {
3983 // greatest elongations for inner planets
3984 fillPhenomenaTable(findGreatestElongationApproach(planet, mObj, startJD, stopJD), planet, sun, PhenomenaTypeIndex::GreatestElongation);
3985 }
3986 // stationary points
3987 fillPhenomenaTable(findStationaryPointApproach(planet, startJD, stopJD), planet, sun, PhenomenaTypeIndex::StationaryPoint);
3988 // perihelion and aphelion points
3989 if (perihelion)
3990 fillPhenomenaTable(findOrbitalPointApproach(planet, startJD, stopJD), planet, sun, PhenomenaTypeIndex::OrbitalPoint);
3991 }
3992
3993 core->setJD(currentJD); // restore time
3994 core->update(0);
3995 }
3996
3997 // adjust the column width
3998 adjustPhenomenaColumns();
3999 // sort-by-date
4000 ui->phenomenaTreeWidget->sortItems(PhenomenaDate, Qt::AscendingOrder);
4001 }
4002
savePhenomena()4003 void AstroCalcDialog::savePhenomena()
4004 {
4005 QString filter = q_("Microsoft Excel Open XML Spreadsheet");
4006 filter.append(" (*.xlsx);;");
4007 filter.append(q_("CSV (Comma delimited)"));
4008 filter.append(" (*.csv)");
4009 QString defaultFilter("(*.xlsx)");
4010 QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
4011 q_("Save calculated phenomena as..."),
4012 QDir::homePath() + "/phenomena.xlsx",
4013 filter,
4014 &defaultFilter);
4015
4016 if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
4017 saveTableAsCSV(filePath, ui->phenomenaTreeWidget, phenomenaHeader);
4018 else
4019 {
4020 int count = ui->phenomenaTreeWidget->topLevelItemCount();
4021 int columns = phenomenaHeader.size();
4022
4023 int *width;
4024 width = new int[columns];
4025 QString sData;
4026 int w;
4027
4028 QXlsx::Document xlsx;
4029 xlsx.setDocumentProperty("title", q_("Phenomena"));
4030 xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
4031 xlsx.addSheet(q_("Phenomena"), AbstractSheet::ST_WorkSheet);
4032
4033 QXlsx::Format header;
4034 header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
4035 header.setPatternBackgroundColor(Qt::yellow);
4036 header.setBorderStyle(QXlsx::Format::BorderThin);
4037 header.setBorderColor(Qt::black);
4038 header.setFontBold(true);
4039 for (int i = 0; i < columns; i++)
4040 {
4041 // Row 1: Names of columns
4042 sData = phenomenaHeader.at(i).trimmed();
4043 xlsx.write(1, i + 1, sData, header);
4044 width[i] = sData.size();
4045 }
4046
4047 QXlsx::Format data;
4048 data.setHorizontalAlignment(QXlsx::Format::AlignRight);
4049 for (int i = 0; i < count; i++)
4050 {
4051 for (int j = 0; j < columns; j++)
4052 {
4053 // Row 2 and next: the data
4054 sData = ui->phenomenaTreeWidget->topLevelItem(i)->text(j).trimmed();
4055 xlsx.write(i + 2, j + 1, sData, data);
4056 w = sData.size();
4057 if (w > width[j])
4058 {
4059 width[j] = w;
4060 }
4061 }
4062 }
4063
4064 for (int i = 0; i < columns; i++)
4065 {
4066 xlsx.setColumnWidth(i+1, width[i]+2);
4067 }
4068
4069 delete[] width;
4070 xlsx.saveAs(filePath);
4071 }
4072 }
4073
fillPhenomenaTableVis(QString phenomenType,double JD,QString firstObjectName,float firstObjectMagnitude,QString secondObjectName,float secondObjectMagnitude,QString separation,QString elevation,QString elongation,QString angularDistance,QString elongTooltip,QString angDistTooltip)4074 void AstroCalcDialog::fillPhenomenaTableVis(QString phenomenType, double JD, QString firstObjectName, float firstObjectMagnitude,
4075 QString secondObjectName, float secondObjectMagnitude, QString separation, QString elevation,
4076 QString elongation, QString angularDistance, QString elongTooltip, QString angDistTooltip)
4077 {
4078 ACPhenTreeWidgetItem* treeItem = new ACPhenTreeWidgetItem(ui->phenomenaTreeWidget);
4079 treeItem->setText(PhenomenaType, phenomenType);
4080 // local date and time
4081 treeItem->setText(PhenomenaDate, QString("%1 %2").arg(localeMgr->getPrintableDateLocal(JD), localeMgr->getPrintableTimeLocal(JD)));
4082 treeItem->setData(PhenomenaDate, Qt::UserRole, JD);
4083 treeItem->setText(PhenomenaObject1, firstObjectName);
4084 treeItem->setText(PhenomenaMagnitude1, (firstObjectMagnitude > 90.f ? dash : QString::number(firstObjectMagnitude, 'f', 2)));
4085 treeItem->setTextAlignment(PhenomenaMagnitude1, Qt::AlignRight);
4086 treeItem->setToolTip(PhenomenaMagnitude1, q_("Magnitude of first object"));
4087 treeItem->setText(PhenomenaObject2, secondObjectName);
4088 treeItem->setText(PhenomenaMagnitude2, (secondObjectMagnitude > 90.f ? dash : QString::number(secondObjectMagnitude, 'f', 2)));
4089 treeItem->setToolTip(PhenomenaMagnitude2, q_("Magnitude of second object"));
4090 treeItem->setTextAlignment(PhenomenaMagnitude2, Qt::AlignRight);
4091 treeItem->setText(PhenomenaSeparation, separation);
4092 treeItem->setTextAlignment(PhenomenaSeparation, Qt::AlignRight);
4093 treeItem->setText(PhenomenaElevation, elevation);
4094 treeItem->setTextAlignment(PhenomenaElevation, Qt::AlignRight);
4095 treeItem->setToolTip(PhenomenaElevation, q_("Elevation of first object at moment of phenomena"));
4096 treeItem->setText(PhenomenaElongation, elongation);
4097 treeItem->setToolTip(PhenomenaElongation, elongTooltip.isEmpty() ? q_("Angular distance from the Sun") : elongTooltip);
4098 treeItem->setTextAlignment(PhenomenaElongation, Qt::AlignRight);
4099 treeItem->setText(PhenomenaAngularDistance, angularDistance);
4100 treeItem->setToolTip(PhenomenaAngularDistance, angDistTooltip.isEmpty() ? q_("Angular distance from the Moon") : angDistTooltip);
4101 treeItem->setTextAlignment(PhenomenaAngularDistance, Qt::AlignRight);
4102 }
4103
fillPhenomenaTable(const QMap<double,double> list,const PlanetP object1,const PlanetP object2,int mode)4104 void AstroCalcDialog::fillPhenomenaTable(const QMap<double, double> list, const PlanetP object1, const PlanetP object2, int mode)
4105 {
4106 QMap<double, double>::ConstIterator it;
4107 PlanetP sun = solarSystem->getSun();
4108 PlanetP moon = solarSystem->getMoon();
4109 PlanetP earth = solarSystem->getEarth();
4110 PlanetP planet = core->getCurrentPlanet();
4111 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
4112 double az, alt;
4113 for (it = list.constBegin(); it != list.constEnd(); ++it)
4114 {
4115 core->setJD(it.key());
4116 core->update(0);
4117
4118 QString phenomenType = q_("Conjunction");
4119 double separation = it.value();
4120 bool occultation = false;
4121 const double s1 = object1->getSpheroidAngularSize(core);
4122 const double s2 = object2->getSpheroidAngularSize(core);
4123 const double d1 = object1->getJ2000EquatorialPos(core).length();
4124 const double d2 = object2->getJ2000EquatorialPos(core).length();
4125 if (mode==PhenomenaTypeIndex::Shadows) // shadows
4126 {
4127 phenomenType = q_("Shadow transit");
4128 separation = object1->getJ2000EquatorialPos(core).angle(object2->getJ2000EquatorialPos(core));
4129 if (d1<d2) // the moon is behind planet, so the moon in shadow
4130 phenomenType = q_("Eclipse");
4131 }
4132 else if (mode==PhenomenaTypeIndex::Opposition) // opposition
4133 {
4134 phenomenType = q_("Opposition");
4135 // Added a special case - lunar eclipse
4136 if (qAbs(separation) <= 0.02 && ((object1 == moon && object2 == sun) || (object1 == sun && object2 == moon)))
4137 phenomenType = q_("Eclipse");
4138
4139 separation = M_PI - separation;
4140 }
4141 else if (mode==PhenomenaTypeIndex::GreatestElongation) // greatest elongations
4142 {
4143 if (separation < 0.0) // we use negative value for eastern elongations!
4144 {
4145 separation *= -1.0;
4146 phenomenType = q_("Greatest eastern elongation");
4147 }
4148 else
4149 phenomenType = q_("Greatest western elongation");
4150 }
4151 else if (mode==PhenomenaTypeIndex::StationaryPoint) // stationary points
4152 {
4153 if (separation < 0.0) // we use negative value for start retrograde motion!
4154 {
4155 // TRANSLATORS: The planet are stand still in the equatorial coordinates
4156 phenomenType = q_("Stationary (begin retrograde motion)");
4157 }
4158 else
4159 {
4160 // TRANSLATORS: The planet are stand still in the equatorial coordinates
4161 phenomenType = q_("Stationary (begin prograde motion)");
4162 }
4163 }
4164 else if (mode==PhenomenaTypeIndex::OrbitalPoint)
4165 {
4166 if (separation < 0.0) // we use negative value for perihelion!
4167 phenomenType = q_("Perihelion");
4168 else
4169 phenomenType = q_("Aphelion");
4170 }
4171 else if (separation < (s2 * M_PI / 180.) || separation < (s1 * M_PI / 180.))
4172 {
4173 if ((d1 < d2 && s1 <= s2) || (d1 > d2 && s1 > s2))
4174 {
4175 // The passage of the celestial body in front of another of greater apparent diameter
4176 phenomenType = qc_("Transit", "passage of the celestial body");
4177 }
4178 else
4179 phenomenType = q_("Occultation");
4180
4181 // Added a special case - solar eclipse
4182 if (qAbs(s1 - s2) <= 0.05 && (object1 == sun || object2 == sun)) // 5% error of difference of sizes
4183 phenomenType = q_("Eclipse");
4184
4185 occultation = true;
4186 }
4187 else if (qAbs(separation) <= 0.0087 && ((object1 == moon && object2 == sun) || (object1 == sun && object2 == moon))) // Added a special case - partial solar eclipse
4188 {
4189 phenomenType = q_("Eclipse");
4190 }
4191 else if (object1 == sun || object2 == sun) // this is may be superior of inferior conjunction for inner planet
4192 {
4193 double dcp = planet->getHeliocentricEclipticPos().length();
4194 double dp = (object1 == sun) ? object2->getHeliocentricEclipticPos().length() :
4195 object1->getHeliocentricEclipticPos().length();
4196 if (dp < dcp) // OK, it's inner planet
4197 {
4198 if (object1 == sun)
4199 phenomenType = d1<d2 ? q_("Superior conjunction") : q_("Inferior conjunction");
4200 else
4201 phenomenType = d2<d1 ? q_("Superior conjunction") : q_("Inferior conjunction");
4202 }
4203 }
4204
4205 QString elongStr = "";
4206 if (((object1 == sun || object2 == sun) && mode==PhenomenaTypeIndex::Conjunction) || (object2 == sun && mode==PhenomenaTypeIndex::Opposition))
4207 elongStr = dash;
4208 else
4209 {
4210 double elongation = object1->getElongation(core->getObserverHeliocentricEclipticPos());
4211 if (mode==PhenomenaTypeIndex::Opposition) // calculate elongation for the second object in this case!
4212 elongation = object2->getElongation(core->getObserverHeliocentricEclipticPos());
4213
4214 if (withDecimalDegree)
4215 elongStr = StelUtils::radToDecDegStr(elongation, 5, false, true);
4216 else
4217 elongStr = StelUtils::radToDmsStr(elongation, true);
4218 }
4219
4220 QString angDistStr = "";
4221 if (planet != earth)
4222 angDistStr = dash;
4223 else
4224 {
4225 if (object1 == moon || object2 == moon)
4226 angDistStr = dash;
4227 else
4228 {
4229 double angularDistance = object1->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4230 if (mode==1) // calculate elongation for the second object in this case!
4231 angularDistance = object2->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4232
4233 if (withDecimalDegree)
4234 angDistStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
4235 else
4236 angDistStr = StelUtils::radToDmsStr(angularDistance, true);
4237 }
4238 }
4239
4240 QString elongationInfo = q_("Angular distance from the Sun");
4241 QString angularDistanceInfo = q_("Angular distance from the Moon");
4242 if (mode==1)
4243 {
4244 elongationInfo = q_("Angular distance from the Sun for second object");
4245 angularDistanceInfo = q_("Angular distance from the Moon for second object");
4246 }
4247
4248 QString separationStr = dash;
4249 float magnitude = object2->getVMagnitude(core);
4250 if (!occultation)
4251 {
4252 if (withDecimalDegree)
4253 separationStr = StelUtils::radToDecDegStr(separation, 5, false, true);
4254 else
4255 separationStr = StelUtils::radToDmsStr(separation, true);
4256 }
4257 else
4258 magnitude = 99.f; // Let's hide obviously wrong data
4259
4260 QString nameObj2 = object2->getNameI18n();
4261 if (mode==PhenomenaTypeIndex::StationaryPoint)
4262 {
4263 nameObj2 = dash;
4264 magnitude = 99.f;
4265 separationStr = dash;
4266 }
4267
4268 QString elevationStr = dash;
4269 StelUtils::rectToSphe(&az, &alt, object1->getAltAzPosAuto(core));
4270 if (withDecimalDegree)
4271 elevationStr = StelUtils::radToDecDegStr(alt, 5, false, true);
4272 else
4273 elevationStr = StelUtils::radToDmsPStr(alt, 2);
4274
4275 fillPhenomenaTableVis(phenomenType, it.key(), object1->getNameI18n(), object1->getVMagnitude(core), nameObj2, magnitude, separationStr, elevationStr, elongStr, angDistStr, elongationInfo, angularDistanceInfo);
4276 }
4277 }
4278
fillPhenomenaTable(const QMap<double,double> list,const PlanetP object1,const NebulaP object2)4279 void AstroCalcDialog::fillPhenomenaTable(const QMap<double, double> list, const PlanetP object1, const NebulaP object2)
4280 {
4281 QMap<double, double>::ConstIterator it;
4282 PlanetP sun = solarSystem->getSun();
4283 PlanetP moon = solarSystem->getMoon();
4284 PlanetP earth = solarSystem->getEarth();
4285 PlanetP planet = core->getCurrentPlanet();
4286 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
4287 double az, alt;
4288 for (it = list.constBegin(); it != list.constEnd(); ++it)
4289 {
4290 core->setJD(it.key());
4291 core->update(0);
4292
4293 QString phenomenType = q_("Conjunction");
4294 double separation = it.value();
4295 bool occultation = false;
4296 if (separation < (object2->getAngularSize(core) * M_PI / 180.) || separation < (object1->getSpheroidAngularSize(core) * M_PI / 180.))
4297 {
4298 phenomenType = q_("Occultation");
4299 occultation = true;
4300 }
4301
4302 QString elongStr = "";
4303 if (object1 == sun)
4304 elongStr = dash;
4305 else
4306 {
4307 if (withDecimalDegree)
4308 elongStr = StelUtils::radToDecDegStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), 5, false, true);
4309 else
4310 elongStr = StelUtils::radToDmsStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), true);
4311 }
4312
4313 QString angDistStr = "";
4314 if (planet != earth)
4315 angDistStr = dash;
4316 else
4317 {
4318 if (object1 == moon)
4319 angDistStr = dash;
4320 else
4321 {
4322 double angularDistance = object1->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4323 if (withDecimalDegree)
4324 angDistStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
4325 else
4326 angDistStr = StelUtils::radToDmsStr(angularDistance, true);
4327 }
4328 }
4329
4330 QString commonName = object2->getNameI18n();
4331 if (commonName.isEmpty())
4332 commonName = object2->getDSODesignation();
4333 if (commonName.isEmpty())
4334 commonName = object2->getDSODesignationWIC();
4335
4336 QString separationStr = dash;
4337 float magnitude = object2->getVMagnitude(core);
4338 if (!occultation)
4339 {
4340 if (withDecimalDegree)
4341 separationStr = StelUtils::radToDecDegStr(separation, 5, false, true);
4342 else
4343 separationStr = StelUtils::radToDmsStr(separation, true);
4344 }
4345 else
4346 magnitude = 99.f; // Let's hide obviously wrong data
4347
4348 QString elevationStr = dash;
4349 StelUtils::rectToSphe(&az, &alt, object1->getAltAzPosAuto(core));
4350 if (withDecimalDegree)
4351 elevationStr = StelUtils::radToDecDegStr(alt, 5, false, true);
4352 else
4353 elevationStr = StelUtils::radToDmsPStr(alt, 2);
4354
4355 fillPhenomenaTableVis(phenomenType, it.key(), object1->getNameI18n(), object1->getVMagnitude(core), commonName, magnitude, separationStr, elevationStr, elongStr, angDistStr);
4356 }
4357 }
4358
fillPhenomenaTable(const QMap<double,double> list,const PlanetP object1,const StelObjectP object2,int mode=0)4359 void AstroCalcDialog::fillPhenomenaTable(const QMap<double, double> list, const PlanetP object1, const StelObjectP object2, int mode = 0)
4360 {
4361 QMap<double, double>::ConstIterator it;
4362 PlanetP sun = solarSystem->getSun();
4363 PlanetP moon = solarSystem->getMoon();
4364 PlanetP earth = solarSystem->getEarth();
4365 PlanetP planet = core->getCurrentPlanet();
4366 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
4367 double az, alt;
4368 for (it = list.constBegin(); it != list.constEnd(); ++it)
4369 {
4370 core->setJD(it.key());
4371 core->update(0);
4372
4373 QString phenomenType = q_("Conjunction");
4374 double separation = it.value();
4375 bool occultation = false;
4376 const double s1 = object1->getSpheroidAngularSize(core);
4377 const double s2 = object2->getAngularSize(core);
4378 const double d1 = object1->getJ2000EquatorialPos(core).length();
4379 const double d2 = object2->getJ2000EquatorialPos(core).length();
4380 if (mode==PhenomenaTypeIndex::Opposition)
4381 {
4382 phenomenType = q_("Opposition");
4383 // Added a special case - lunar eclipse
4384 if (qAbs(separation) <= 0.02 && ((object1 == moon && object2 == sun) || (object1 == sun && object2 == moon)))
4385 phenomenType = q_("Eclipse");
4386
4387 separation = M_PI - separation;
4388 }
4389 else if (separation < (s2 * M_PI / 180.) || separation < (s1 * M_PI / 180.))
4390 {
4391 if ((d1 < d2 && s1 <= s2) || (d1 > d2 && s1 > s2))
4392 {
4393 // The passage of the celestial body in front of another of greater apparent diameter
4394 phenomenType = qc_("Transit", "passage of the celestial body");
4395 }
4396 else
4397 phenomenType = q_("Occultation");
4398
4399 // Added a special case - solar eclipse
4400 if (qAbs(s1 - s2) <= 0.05 && (object1 == sun || object2 == sun)) // 5% error of difference of sizes
4401 phenomenType = q_("Eclipse");
4402
4403 occultation = true;
4404 }
4405 else if (qAbs(separation) <= 0.0087 && ((object1 == moon && object2 == sun) || (object1 == sun && object2 == moon))) // Added a special case - partial solar eclipse
4406 {
4407 phenomenType = q_("Eclipse");
4408 }
4409 else if (object1 == sun || object2 == sun) // this is may be superior of inferior conjunction for inner planet
4410 {
4411 double dcp = (planet->getEquinoxEquatorialPos(core) - sun->getEquinoxEquatorialPos(core)).length();
4412 double dp;
4413 if (object1 == sun)
4414 dp = (object2->getEquinoxEquatorialPos(core) - sun->getEquinoxEquatorialPos(core)).length();
4415 else
4416 dp = (object1->getEquinoxEquatorialPos(core) - sun->getEquinoxEquatorialPos(core)).length();
4417 if (dp < dcp) // OK, it's inner planet
4418 {
4419 if (object1 == sun)
4420 phenomenType = d1<d2 ? q_("Superior conjunction") : q_("Inferior conjunction");
4421 else
4422 phenomenType = d2<d1 ? q_("Superior conjunction") : q_("Inferior conjunction");
4423 }
4424 }
4425
4426 QString elongStr = "";
4427 if (object1 == sun)
4428 elongStr = dash;
4429 else
4430 {
4431 if (withDecimalDegree)
4432 elongStr = StelUtils::radToDecDegStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), 5, false, true);
4433 else
4434 elongStr = StelUtils::radToDmsStr(object1->getElongation(core->getObserverHeliocentricEclipticPos()), true);
4435 }
4436
4437 QString angDistStr = "";
4438 if (planet != earth)
4439 angDistStr = dash;
4440 else
4441 {
4442 if (object1 == moon)
4443 angDistStr = dash;
4444 else
4445 {
4446 double angularDistance = object1->getJ2000EquatorialPos(core).angle(moon->getJ2000EquatorialPos(core));
4447 if (withDecimalDegree)
4448 angDistStr = StelUtils::radToDecDegStr(angularDistance, 5, false, true);
4449 else
4450 angDistStr = StelUtils::radToDmsStr(angularDistance, true);
4451 }
4452 }
4453
4454 QString commonName = object2->getNameI18n();
4455 if (commonName.isEmpty())
4456 commonName = object2->getID();
4457
4458 QString separationStr = dash;
4459 float magnitude = object2->getVMagnitude(core);
4460 if (!occultation)
4461 {
4462 if (withDecimalDegree)
4463 separationStr = StelUtils::radToDecDegStr(separation, 5, false, true);
4464 else
4465 separationStr = StelUtils::radToDmsStr(separation, true);
4466 }
4467 else
4468 magnitude = 99.f; // Let's hide obviously wrong data
4469
4470 QString elevationStr = dash;
4471 StelUtils::rectToSphe(&az, &alt, object1->getAltAzPosAuto(core));
4472 if (withDecimalDegree)
4473 elevationStr = StelUtils::radToDecDegStr(alt, 5, false, true);
4474 else
4475 elevationStr = StelUtils::radToDmsPStr(alt, 2);
4476
4477 fillPhenomenaTableVis(phenomenType, it.key(), object1->getNameI18n(), object1->getVMagnitude(core), commonName, magnitude, separationStr, elevationStr, elongStr, angDistStr);
4478 }
4479 }
4480
findInitialStep(double startJD,double stopJD,QStringList objects)4481 double AstroCalcDialog::findInitialStep(double startJD, double stopJD, QStringList objects)
4482 {
4483 double step = (stopJD - startJD) / 16.0;
4484 double limit = 24.8 * 365.25;
4485 QRegularExpression mp("^[(](\\d+)[)]\\s(.+)$");
4486
4487 if (objects.contains("Moon", Qt::CaseInsensitive))
4488 limit = 0.25;
4489 else if (objects.contains("C/",Qt::CaseInsensitive) || objects.contains("P/",Qt::CaseInsensitive))
4490 limit = 0.5;
4491 else if (objects.contains("Earth",Qt::CaseInsensitive) || objects.contains("Sun", Qt::CaseInsensitive))
4492 limit = 1.;
4493 else if (objects.contains("Venus",Qt::CaseInsensitive) || objects.contains("Mercury", Qt::CaseInsensitive))
4494 limit = 2.5;
4495 else if (objects.contains("Mars",Qt::CaseInsensitive))
4496 limit = 5.;
4497 else if (objects.indexOf(mp)>=0)
4498 limit = 10.;
4499 else if (objects.contains("Jupiter", Qt::CaseInsensitive) || objects.contains("Saturn", Qt::CaseInsensitive))
4500 limit = 15.;
4501 else if (objects.contains("Neptune", Qt::CaseInsensitive) || objects.contains("Uranus", Qt::CaseInsensitive) || objects.contains("Pluto",Qt::CaseInsensitive))
4502 limit = 20.;
4503
4504 if (step > limit)
4505 step = limit;
4506
4507 return step;
4508 }
4509
findClosestApproach(PlanetP & object1,StelObjectP & object2,const double startJD,const double stopJD,const double maxSeparation,const int mode)4510 QMap<double, double> AstroCalcDialog::findClosestApproach(PlanetP& object1, StelObjectP& object2, const double startJD, const double stopJD, const double maxSeparation, const int mode)
4511 {
4512 QMap<double, double> separations;
4513 QPair<double, double> extremum;
4514
4515 if (object2->getType()=="Planet")
4516 {
4517 PlanetP planet = qSharedPointerCast<Planet>(object2);
4518 // We shouldn't compute eclipses and shadow transits not for planets and their moons
4519 if (mode==PhenomenaTypeIndex::Shadows && object2->getEnglishName()!="Sun" && planet->getParent()!=object1)
4520 return separations;
4521
4522 // If we don't have at least partial overlap between planet valid dates and our interval, skip by returning an empty map.
4523 const Vec2d planetValidityLimits=planet->getValidPositionalDataRange(Planet::PositionQuality::Position);
4524 if ( (planetValidityLimits[0] > stopJD) || (planetValidityLimits[1] < startJD) )
4525 return separations;
4526 }
4527
4528 QStringList objects;
4529 Q_ASSERT(objects.isEmpty());
4530 objects.append(object1->getEnglishName());
4531 objects.append(object2->getEnglishName());
4532 double step0 = findInitialStep(startJD, stopJD, objects);
4533 double step = step0;
4534 double jd = startJD;
4535 double prevDist = findDistance(jd, object1, object2, mode);
4536 int prevSgn = 0;
4537 jd += step;
4538 while (jd <= stopJD)
4539 {
4540 double dist = findDistance(jd, object1, object2, mode);
4541 int sgn = StelUtils::sign(dist - prevDist);
4542
4543 double factor = qAbs((dist - prevDist) / dist);
4544 if (factor > 10.)
4545 step = step0 * factor / 10.;
4546 else
4547 step = step0;
4548
4549 if (sgn != prevSgn && prevSgn == -1)
4550 {
4551 if (step > step0)
4552 {
4553 jd -= step;
4554 step = step0;
4555 sgn = prevSgn;
4556 while (jd <= stopJD)
4557 {
4558 dist = findDistance(jd, object1, object2, mode);
4559 sgn = StelUtils::sign(dist - prevDist);
4560 if (sgn != prevSgn)
4561 break;
4562
4563 prevDist = dist;
4564 prevSgn = sgn;
4565 jd += step;
4566 }
4567 }
4568
4569 if (findPrecise(&extremum, object1, object2, jd, step, sgn, mode))
4570 {
4571 double sep = extremum.second * 180. / M_PI;
4572 if (sep < maxSeparation)
4573 separations.insert(extremum.first, extremum.second);
4574 }
4575 }
4576
4577 prevDist = dist;
4578 prevSgn = sgn;
4579 jd += step;
4580 }
4581 return separations;
4582 }
4583
findPrecise(QPair<double,double> * out,PlanetP object1,StelObjectP object2,double JD,double step,int prevSign,int mode)4584 bool AstroCalcDialog::findPrecise(QPair<double, double>* out, PlanetP object1, StelObjectP object2, double JD, double step, int prevSign, int mode)
4585 {
4586 if (out == Q_NULLPTR)
4587 return false;
4588
4589 double prevDist = findDistance(JD, object1, object2, mode);
4590 step = -step / 2.;
4591 prevSign = -prevSign;
4592
4593 while (true)
4594 {
4595 JD += step;
4596 double dist = findDistance(JD, object1, object2, mode);
4597
4598 if (qAbs(step) < 1. / 1440.)
4599 {
4600 out->first = JD - step / 2.0;
4601 out->second = findDistance(JD - step / 2.0, object1, object2, mode);
4602 if (out->second < findDistance(JD - 5.0, object1, object2, mode))
4603 return true;
4604 else
4605 return false;
4606 }
4607 int sgn = StelUtils::sign(dist - prevDist);
4608 if (sgn != prevSign)
4609 {
4610 step = -step / 2.0;
4611 sgn = -sgn;
4612 }
4613 prevDist = dist;
4614 prevSign = sgn;
4615 }
4616 }
4617
findDistance(double JD,PlanetP object1,StelObjectP object2,int mode)4618 double AstroCalcDialog::findDistance(double JD, PlanetP object1, StelObjectP object2, int mode)
4619 {
4620 core->setJD(JD);
4621 core->update(0);
4622 double angle = object1->getJ2000EquatorialPos(core).angle(object2->getJ2000EquatorialPos(core));
4623 if (mode==PhenomenaTypeIndex::Opposition)
4624 angle = M_PI - angle;
4625 else if (mode==PhenomenaTypeIndex::Shadows)
4626 angle = object1->getHeliocentricEclipticPos().angle(qSharedPointerCast<Planet>(object2)->getHeliocentricEclipticPos());
4627 return angle;
4628 }
4629
findGreatestElongationApproach(PlanetP & object1,StelObjectP & object2,double startJD,double stopJD)4630 QMap<double, double> AstroCalcDialog::findGreatestElongationApproach(PlanetP& object1, StelObjectP& object2, double startJD, double stopJD)
4631 {
4632 QMap<double, double> separations;
4633 QPair<double, double> extremum;
4634
4635 QStringList objects;
4636 Q_ASSERT(objects.isEmpty());
4637 objects.append(object1->getEnglishName());
4638 objects.append(object2->getEnglishName());
4639 double step0 = findInitialStep(startJD, stopJD, objects);
4640 double step = step0;
4641 double jd = startJD;
4642 double prevDist = findDistance(jd, object1, object2, PhenomenaTypeIndex::Conjunction);
4643 jd += step;
4644 while (jd <= stopJD)
4645 {
4646 double dist = findDistance(jd, object1, object2, PhenomenaTypeIndex::Conjunction);
4647 double factor = qAbs((dist - prevDist) / dist);
4648 if (factor > 10.)
4649 step = step0 * factor / 10.;
4650 else
4651 step = step0;
4652
4653 if (dist>prevDist)
4654 {
4655 if (step > step0)
4656 {
4657 jd -= step;
4658 step = step0;
4659 while (jd <= stopJD)
4660 {
4661 dist = findDistance(jd, object1, object2, PhenomenaTypeIndex::Conjunction);
4662 if (dist<prevDist)
4663 break;
4664
4665 prevDist = dist;
4666 jd += step;
4667 }
4668 }
4669
4670 if (findPreciseGreatestElongation(&extremum, object1, object2, jd, stopJD, step))
4671 {
4672 separations.insert(extremum.first, extremum.second);
4673 }
4674 }
4675
4676 prevDist = dist;
4677 jd += step;
4678 }
4679 return separations;
4680 }
4681
findPreciseGreatestElongation(QPair<double,double> * out,PlanetP object1,StelObjectP object2,double JD,double stopJD,double step)4682 bool AstroCalcDialog::findPreciseGreatestElongation(QPair<double, double>* out, PlanetP object1, StelObjectP object2, double JD, double stopJD, double step)
4683 {
4684 if (out == Q_NULLPTR)
4685 return false;
4686
4687 double prevDist = findDistance(JD, object1, object2, PhenomenaTypeIndex::Conjunction);
4688 step = -step / 2.;
4689
4690 while (true)
4691 {
4692 JD += step;
4693 double dist = findDistance(JD, object1, object2, PhenomenaTypeIndex::Conjunction);
4694
4695 if (qAbs(step) < 1. / 1440.)
4696 {
4697 out->first = JD - step / 2.0;
4698 out->second = findDistance(JD - step / 2.0, object1, object2, PhenomenaTypeIndex::Conjunction);
4699 if (out->second > findDistance(JD - 5.0, object1, object2, PhenomenaTypeIndex::Conjunction))
4700 {
4701 if (object1->getJ2000EquatorialPos(core).longitude()>object2->getJ2000EquatorialPos(core).longitude())
4702 out->second *= -1.0; // let's use negative value for eastern elongations
4703 return true;
4704 }
4705 else
4706 return false;
4707 }
4708 if (dist<prevDist)
4709 {
4710 step = -step / 2.0;
4711 }
4712 prevDist = dist;
4713
4714 if (JD > stopJD)
4715 return false;
4716 }
4717 }
4718
findStationaryPointApproach(PlanetP & object1,double startJD,double stopJD)4719 QMap<double, double> AstroCalcDialog::findStationaryPointApproach(PlanetP &object1, double startJD, double stopJD)
4720 {
4721 double RA, prevRA, step, step0;
4722 QMap<double, double> separations;
4723 QPair<double, double> extremum;
4724
4725 QStringList objects;
4726 Q_ASSERT(objects.isEmpty());
4727 objects.append(object1->getEnglishName());
4728 step0 = findInitialStep(startJD, stopJD, objects);
4729 step = step0;
4730 double jd = startJD;
4731 prevRA = findRightAscension(jd, object1);
4732 jd += step;
4733 while (jd <= stopJD)
4734 {
4735 RA = findRightAscension(jd, object1);
4736 double factor = qAbs((RA - prevRA) / RA);
4737 if (factor > 10.)
4738 step = step0 * factor / 10.;
4739 else
4740 step = step0;
4741
4742 if (RA>prevRA && qAbs(RA - prevRA)<180.)
4743 {
4744 if (step > step0)
4745 {
4746 jd -= step;
4747 step = step0;
4748 while (jd <= stopJD)
4749 {
4750 RA = findRightAscension(jd, object1);
4751 if (RA<prevRA)
4752 break;
4753
4754 prevRA = RA;
4755 jd += step;
4756 }
4757 }
4758
4759 if (findPreciseStationaryPoint(&extremum, object1, jd, stopJD, step, true))
4760 {
4761 separations.insert(extremum.first, extremum.second);
4762 }
4763 }
4764 prevRA = RA;
4765 jd += step;
4766 }
4767
4768 step0 = findInitialStep(startJD, stopJD, objects);
4769 step = step0;
4770 jd = startJD;
4771 prevRA = findRightAscension(jd, object1);
4772 jd += step;
4773 while (jd <= stopJD)
4774 {
4775 RA = findRightAscension(jd, object1);
4776 double factor = qAbs((RA - prevRA) / RA);
4777 if (factor > 10.)
4778 step = step0 * factor / 10.;
4779 else
4780 step = step0;
4781
4782 if (RA<prevRA && qAbs(RA - prevRA)<180.)
4783 {
4784 if (step > step0)
4785 {
4786 jd -= step;
4787 step = step0;
4788 while (jd <= stopJD)
4789 {
4790 RA = findRightAscension(jd, object1);
4791 if (RA>prevRA)
4792 break;
4793
4794 prevRA = RA;
4795 jd += step;
4796 }
4797 }
4798
4799 if (findPreciseStationaryPoint(&extremum, object1, jd, stopJD, step, false))
4800 {
4801 separations.insert(extremum.first, extremum.second);
4802 }
4803 }
4804 prevRA = RA;
4805 jd += step;
4806 }
4807
4808 return separations;
4809 }
4810
findPreciseStationaryPoint(QPair<double,double> * out,PlanetP object,double JD,double stopJD,double step,bool retrograde)4811 bool AstroCalcDialog::findPreciseStationaryPoint(QPair<double, double> *out, PlanetP object, double JD, double stopJD, double step, bool retrograde)
4812 {
4813 double RA, prevRA;
4814
4815 if (out == Q_NULLPTR)
4816 return false;
4817
4818 prevRA = findRightAscension(JD, object);
4819 step /= -2.;
4820
4821 while (true)
4822 {
4823 JD += step;
4824 RA = findRightAscension(JD, object);
4825
4826 if (qAbs(step) < 1. / 1440.)
4827 {
4828 out->first = JD - step / 2.0;
4829 out->second = findRightAscension(JD - step / 2.0, object);
4830 if (retrograde) // begin retrograde motion
4831 {
4832 if (out->second > findRightAscension(JD - 5.0, object))
4833 {
4834 out->second = -1.0;
4835 return true;
4836 }
4837 else
4838 return false;
4839 }
4840 else
4841 {
4842 if (out->second < findRightAscension(JD - 5.0, object))
4843 {
4844 out->second = 1.0;
4845 return true;
4846 }
4847 else
4848 return false;
4849 }
4850 }
4851 if (retrograde)
4852 {
4853 if (RA<prevRA)
4854 step /= -2.0;
4855 }
4856 else
4857 {
4858 if (RA>prevRA)
4859 step /= -2.0;
4860 }
4861 prevRA = RA;
4862
4863 if (JD > stopJD)
4864 return false;
4865 }
4866 }
4867
4868 // return J2000 RA of a planetary object (without aberration)
findRightAscension(double JD,PlanetP object)4869 double AstroCalcDialog::findRightAscension(double JD, PlanetP object)
4870 {
4871 const double JDE=JD+core->computeDeltaT(JD)/86400.;
4872 const Vec3d obs=core->getCurrentPlanet()->getHeliocentricEclipticPos(JDE);
4873 Vec3d body=object->getHeliocentricEclipticPos(JDE);
4874 const double distanceCorrection=(body-obs).length() * (AU / (SPEED_OF_LIGHT * 86400.));
4875 body=object->getHeliocentricEclipticPos(JDE-distanceCorrection);
4876 Vec3d bodyJ2000=StelCore::matVsop87ToJ2000.multiplyWithoutTranslation(body - obs);
4877 double ra, dec;
4878 StelUtils::rectToSphe(&ra, &dec, bodyJ2000);
4879 return ra*M_180_PI;
4880 }
4881
findOrbitalPointApproach(PlanetP & object1,double startJD,double stopJD)4882 QMap<double, double> AstroCalcDialog::findOrbitalPointApproach(PlanetP &object1, double startJD, double stopJD)
4883 {
4884 QMap<double, double> separations;
4885 QPair<double, double> extremum;
4886
4887 QStringList objects;
4888 Q_ASSERT(objects.isEmpty());
4889 objects.append(object1->getEnglishName());
4890 double step0 = findInitialStep(startJD, stopJD, objects);
4891 double step = step0;
4892 double jd = startJD - step;
4893 double prevDistance = findRightAscension(jd, object1);
4894 jd += step;
4895 double stopJDfx = stopJD + step;
4896 while (jd <= stopJDfx)
4897 {
4898 double distance = findHeliocentricDistance(jd, object1);
4899 double factor = qAbs((distance - prevDistance) / distance);
4900 if (factor > 10.)
4901 step = step0 * factor / 10.;
4902 else
4903 step = step0;
4904
4905 if (distance>prevDistance)
4906 {
4907 if (step > step0)
4908 {
4909 jd -= step;
4910 step = step0;
4911 while (jd <= stopJDfx)
4912 {
4913 distance = findHeliocentricDistance(jd, object1);
4914 if (distance<prevDistance)
4915 break;
4916
4917 prevDistance = distance;
4918 jd += step;
4919 }
4920 }
4921
4922 if (findPreciseOrbitalPoint(&extremum, object1, jd, stopJDfx, step, false))
4923 {
4924 if (extremum.first>startJD && extremum.first<stopJD)
4925 separations.insert(extremum.first, extremum.second);
4926 }
4927 }
4928
4929 prevDistance = distance;
4930 jd += step;
4931 }
4932
4933 step0 = findInitialStep(startJD, stopJD, objects);
4934 step = step0;
4935 jd = startJD - step;
4936 prevDistance = findRightAscension(jd, object1);
4937 jd += step;
4938 while (jd <= stopJDfx)
4939 {
4940 double distance = findHeliocentricDistance(jd, object1);
4941 double factor = qAbs((distance - prevDistance) / distance);
4942 if (factor > 10.)
4943 step = step0 * factor / 10.;
4944 else
4945 step = step0;
4946
4947 if (distance<prevDistance)
4948 {
4949 if (step > step0)
4950 {
4951 jd -= step;
4952 step = step0;
4953 while (jd <= stopJDfx)
4954 {
4955 distance = findHeliocentricDistance(jd, object1);
4956 if (distance>prevDistance)
4957 break;
4958
4959 prevDistance = distance;
4960 jd += step;
4961 }
4962 }
4963
4964 if (findPreciseOrbitalPoint(&extremum, object1, jd, stopJDfx, step, true))
4965 {
4966 if (extremum.first>startJD && extremum.first<stopJD)
4967 separations.insert(extremum.first, extremum.second);
4968 }
4969 }
4970
4971 prevDistance = distance;
4972 jd += step;
4973 }
4974
4975 return separations;
4976 }
4977
findPreciseOrbitalPoint(QPair<double,double> * out,PlanetP object1,double JD,double stopJD,double step,bool minimal)4978 bool AstroCalcDialog::findPreciseOrbitalPoint(QPair<double, double>* out, PlanetP object1, double JD, double stopJD, double step, bool minimal)
4979 {
4980 double dist, prevDist, timeDist = qAbs(stopJD-JD);
4981
4982 if (out == Q_NULLPTR)
4983 return false;
4984
4985 prevDist = findHeliocentricDistance(JD, object1);
4986 step /= -2.;
4987
4988 bool result;
4989 while (true)
4990 {
4991 JD += step;
4992 dist = findHeliocentricDistance(JD, object1);
4993
4994 if (qAbs(step) < 1. / 1440.)
4995 {
4996 out->first = JD - step / 2.0;
4997 out->second = findHeliocentricDistance(JD - step / 2.0, object1);
4998 if (minimal)
4999 {
5000 result = (out->second > findHeliocentricDistance(JD - step / 5.0, object1));
5001 if (result)
5002 out->second *= -1;
5003 }
5004 else
5005 result = (out->second < findHeliocentricDistance(JD - step / 5.0, object1));
5006
5007 return result;
5008 }
5009 if (minimal)
5010 {
5011 if (dist>prevDist)
5012 step /= -2.0;
5013 }
5014 else
5015 {
5016 if (dist<prevDist)
5017 step /= -2.0;
5018 }
5019 prevDist = dist;
5020
5021 if (JD > stopJD || JD < (stopJD - 2*timeDist))
5022 return false;
5023 }
5024 }
5025
changePage(QListWidgetItem * current,QListWidgetItem * previous)5026 void AstroCalcDialog::changePage(QListWidgetItem* current, QListWidgetItem* previous)
5027 {
5028 if (!current)
5029 current = previous;
5030
5031 // reset all flags to make sure only one is set
5032 plotAltVsTime = false;
5033 plotAziVsTime = false;
5034 plotMonthlyElevation = false;
5035 plotAngularDistanceGraph = false;
5036 plotDistanceGraph = false;
5037
5038 ui->stackedWidget->setCurrentIndex(ui->stackListWidget->row(current));
5039
5040 // special case
5041 if (ui->stackListWidget->row(current) == 0)
5042 currentCelestialPositions();
5043
5044 // special case - ephemeris
5045 if (ui->stackListWidget->row(current) == 1)
5046 {
5047 double JD = core->getJD() + core->getUTCOffset(core->getJD()) / 24;
5048 QDateTime currentDT = StelUtils::jdToQDateTime(JD);
5049 ui->dateFromDateTimeEdit->setDateTime(currentDT);
5050 ui->dateToDateTimeEdit->setDateTime(currentDT.addMonths(1));
5051 }
5052
5053 // special case - transits
5054 if (ui->stackListWidget->row(current) == 2)
5055 setTransitCelestialBodyName();
5056
5057 // special case - graphs
5058 if (ui->stackListWidget->row(current) == 4)
5059 {
5060 int idx = ui->tabWidgetGraphs->currentIndex();
5061 if (idx==0) // 'Alt. vs Time' is visible
5062 {
5063 plotAltVsTime = true;
5064 drawAltVsTimeDiagram(); // Is object already selected?
5065 }
5066
5067 if (idx==1) // 'Azi. vs Time' is visible
5068 {
5069 plotAziVsTime = true;
5070 drawAziVsTimeDiagram(); // Is object already selected?
5071 }
5072
5073 if (idx==2) // 'Monthly Elevation' is visible
5074 {
5075 plotMonthlyElevation = true;
5076 drawMonthlyElevationGraph(); // Is object already selected?
5077 }
5078
5079 if (idx==3) // 'Graphs' is visible
5080 updateXVsTimeGraphs();
5081
5082 if(idx==4) // 'Angular distance' is visible
5083 {
5084 plotAngularDistanceGraph = true;
5085 drawAngularDistanceGraph();
5086 }
5087 }
5088
5089 // special case (PCalc)
5090 if (ui->stackListWidget->row(current) == 6)
5091 {
5092 int index = ui->tabWidgetPC->currentIndex();
5093 if (index==0) // First tab: Data
5094 computePlanetaryData();
5095
5096 if (index==1) // Second tab: Graphs
5097 {
5098 plotDistanceGraph = true;
5099 drawDistanceGraph();
5100 }
5101 }
5102 }
5103
changePCTab(int index)5104 void AstroCalcDialog::changePCTab(int index)
5105 {
5106 if (index==0) // First tab: Data
5107 {
5108 plotDistanceGraph = false;
5109 computePlanetaryData();
5110 }
5111 if (index==1) // Second tab: Graphs
5112 {
5113 plotDistanceGraph = true;
5114 drawDistanceGraph();
5115 }
5116 }
5117
changeGraphsTab(int index)5118 void AstroCalcDialog::changeGraphsTab(int index)
5119 {
5120 // reset all flags to make sure only one is set
5121 plotAltVsTime = false;
5122 plotAziVsTime = false;
5123 plotMonthlyElevation = false;
5124 plotAngularDistanceGraph = false;
5125
5126 if (index==0) // Altitude vs. Time
5127 {
5128 plotAltVsTime = true;
5129 drawAltVsTimeDiagram(); // Is object already selected?
5130 }
5131 if (index==1) // Azimuth vs. Time
5132 {
5133 plotAziVsTime = true;
5134 drawAziVsTimeDiagram(); // Is object already selected?
5135 }
5136 if (index==2) // Monthly Elevation
5137 {
5138 plotMonthlyElevation = true;
5139 drawMonthlyElevationGraph(); // Is object already selected?
5140 }
5141 if (index==3) // Graphs
5142 updateXVsTimeGraphs();
5143 if (index==4) // Angular Distance
5144 {
5145 plotAngularDistanceGraph = true;
5146 drawAngularDistanceGraph(); // Is object already selected?
5147 }
5148 }
5149
updateTabBarListWidgetWidth()5150 void AstroCalcDialog::updateTabBarListWidgetWidth()
5151 {
5152 ui->stackListWidget->setWrapping(false);
5153
5154 // Update list item sizes after translation
5155 ui->stackListWidget->adjustSize();
5156
5157 QAbstractItemModel* model = ui->stackListWidget->model();
5158 if (!model)
5159 return;
5160
5161 // stackListWidget->font() does not work properly!
5162 // It has a incorrect fontSize in the first loading, which produces the bug#995107.
5163 QFont font;
5164 font.setPixelSize(14);
5165 font.setWeight(75);
5166 QFontMetrics fontMetrics(font);
5167
5168 int iconSize = ui->stackListWidget->iconSize().width();
5169
5170 int width = 0;
5171 for (int row = 0; row < model->rowCount(); row++)
5172 {
5173 int textWidth = fontMetrics.boundingRect(ui->stackListWidget->item(row)->text()).width();
5174 width += qMax(iconSize, textWidth);
5175 width += 24; // margin - 12px left and 12px right
5176 }
5177
5178 // Hack to force the window to be resized...
5179 ui->stackListWidget->setMinimumWidth(width);
5180 ui->stackListWidget->updateGeometry();
5181 }
5182
updateSolarSystemData()5183 void AstroCalcDialog::updateSolarSystemData()
5184 {
5185 if (dialog)
5186 {
5187 populateCelestialBodyList();
5188 populateGroupCelestialBodyList();
5189 currentCelestialPositions();
5190 calculateWutObjects();
5191 }
5192 }
5193
populateTimeIntervalsList()5194 void AstroCalcDialog::populateTimeIntervalsList()
5195 {
5196 Q_ASSERT(ui->wutComboBox);
5197
5198 QComboBox* wut = ui->wutComboBox;
5199 wut->blockSignals(true);
5200 int index = wut->currentIndex();
5201 QVariant selectedIntervalId = wut->itemData(index);
5202
5203 wut->clear();
5204 wut->addItem(qc_("In the Evening", "Celestial object is observed..."), "0");
5205 wut->addItem(qc_("In the Morning", "Celestial object is observed..."), "1");
5206 wut->addItem(qc_("Around Midnight", "Celestial object is observed..."), "2");
5207 wut->addItem(qc_("In Any Time of the Night", "Celestial object is observed..."), "3");
5208
5209 index = wut->findData(selectedIntervalId, Qt::UserRole, Qt::MatchCaseSensitive);
5210 if (index < 0)
5211 index = wut->findData(conf->value("astrocalc/wut_time_interval", "0").toString(), Qt::UserRole, Qt::MatchCaseSensitive);
5212 wut->setCurrentIndex(index);
5213 wut->model()->sort(0);
5214 wut->blockSignals(false);
5215 }
5216
populateWutGroups()5217 void AstroCalcDialog::populateWutGroups()
5218 {
5219 Q_ASSERT(ui->wutCategoryListWidget);
5220 StelModuleMgr& moduleMgr = StelApp::getInstance().getModuleMgr();
5221
5222 QListWidget* category = ui->wutCategoryListWidget;
5223 category->blockSignals(true);
5224
5225 wutCategories.clear();
5226 wutCategories.insert(q_("Planets"), EWPlanets);
5227 wutCategories.insert(q_("Bright stars"), EWBrightStars);
5228 wutCategories.insert(q_("Bright nebulae"), EWBrightNebulae);
5229 wutCategories.insert(q_("Dark nebulae"), EWDarkNebulae);
5230 wutCategories.insert(q_("Galaxies"), EWGalaxies);
5231 wutCategories.insert(q_("Open star clusters"), EWOpenStarClusters);
5232 wutCategories.insert(q_("Asteroids"), EWAsteroids);
5233 wutCategories.insert(q_("Comets"), EWComets);
5234 wutCategories.insert(q_("Plutinos"), EWPlutinos);
5235 wutCategories.insert(q_("Dwarf planets"), EWDwarfPlanets);
5236 wutCategories.insert(q_("Cubewanos"), EWCubewanos);
5237 wutCategories.insert(q_("Scattered disc objects"), EWScatteredDiscObjects);
5238 wutCategories.insert(q_("Oort cloud objects"), EWOortCloudObjects);
5239 wutCategories.insert(q_("Sednoids"), EWSednoids);
5240 wutCategories.insert(q_("Planetary nebulae"), EWPlanetaryNebulae);
5241 wutCategories.insert(q_("Bright double stars"), EWBrightDoubleStars);
5242 wutCategories.insert(q_("Bright variable stars"), EWBrightVariableStars);
5243 wutCategories.insert(q_("Bright stars with high proper motion"), EWBrightStarsWithHighProperMotion);
5244 wutCategories.insert(q_("Symbiotic stars"), EWSymbioticStars);
5245 wutCategories.insert(q_("Emission-line stars"), EWEmissionLineStars);
5246 wutCategories.insert(q_("Supernova candidates"), EWSupernovaeCandidates);
5247 wutCategories.insert(q_("Supernova remnant candidates"), EWSupernovaeRemnantCandidates);
5248 wutCategories.insert(q_("Supernova remnants"), EWSupernovaeRemnants);
5249 wutCategories.insert(q_("Clusters of galaxies"), EWClustersOfGalaxies);
5250 wutCategories.insert(q_("Interstellar objects"), EWInterstellarObjects);
5251 wutCategories.insert(q_("Globular star clusters"), EWGlobularStarClusters);
5252 wutCategories.insert(q_("Regions of the sky"), EWRegionsOfTheSky);
5253 wutCategories.insert(q_("Active galaxies"), EWActiveGalaxies);
5254 if (moduleMgr.isPluginLoaded("Pulsars"))
5255 {
5256 // Add the category when pulsars is visible
5257 if (propMgr->getProperty("Pulsars.pulsarsVisible")->getValue().toBool())
5258 wutCategories.insert(q_("Pulsars"), EWPulsars);
5259 }
5260 if (moduleMgr.isPluginLoaded("Exoplanets"))
5261 {
5262 // Add the category when exoplanets is visible
5263 if (propMgr->getProperty("Exoplanets.showExoplanets")->getValue().toBool())
5264 wutCategories.insert(q_("Exoplanetary systems"), EWExoplanetarySystems);
5265 }
5266 if (moduleMgr.isPluginLoaded("Novae"))
5267 wutCategories.insert(q_("Bright nova stars"), EWBrightNovaStars);
5268 if (moduleMgr.isPluginLoaded("Supernovae"))
5269 wutCategories.insert(q_("Bright supernova stars"), EWBrightSupernovaStars);
5270 wutCategories.insert(q_("Interacting galaxies"), EWInteractingGalaxies);
5271 wutCategories.insert(q_("Deep-sky objects"), EWDeepSkyObjects);
5272 wutCategories.insert(q_("Messier objects"), EWMessierObjects);
5273 wutCategories.insert(q_("NGC/IC objects"), EWNGCICObjects);
5274 wutCategories.insert(q_("Caldwell objects"), EWCaldwellObjects);
5275 wutCategories.insert(q_("Herschel 400 objects"), EWHerschel400Objects);
5276 wutCategories.insert(q_("Algol-type eclipsing systems"), EWAlgolTypeVariableStars);
5277 wutCategories.insert(q_("The classical cepheids"), EWClassicalCepheidsTypeVariableStars);
5278 wutCategories.insert(q_("Bright carbon stars"), EWCarbonStars);
5279 wutCategories.insert(q_("Bright barium stars"), EWBariumStars);
5280
5281 category->clear();
5282 category->addItems(wutCategories.keys());
5283 category->sortItems(Qt::AscendingOrder);
5284
5285 category->blockSignals(false);
5286 }
5287
saveWutMagnitudeLimit(double mag)5288 void AstroCalcDialog::saveWutMagnitudeLimit(double mag)
5289 {
5290 conf->setValue("astrocalc/wut_magnitude_limit", QString::number(mag, 'f', 2));
5291 calculateWutObjects();
5292 }
5293
saveWutMinAngularSizeLimit()5294 void AstroCalcDialog::saveWutMinAngularSizeLimit()
5295 {
5296 // Convert to angular minutes
5297 conf->setValue("astrocalc/wut_angular_limit_min", QString::number(ui->wutAngularSizeLimitMinSpinBox->valueDegrees()*60.0, 'f', 2));
5298 calculateWutObjects();
5299 }
5300
saveWutMaxAngularSizeLimit()5301 void AstroCalcDialog::saveWutMaxAngularSizeLimit()
5302 {
5303 // Convert to angular minutes
5304 conf->setValue("astrocalc/wut_angular_limit_max", QString::number(ui->wutAngularSizeLimitMaxSpinBox->valueDegrees()*60.0, 'f', 2));
5305 calculateWutObjects();
5306 }
5307
saveWutMinAltitude()5308 void AstroCalcDialog::saveWutMinAltitude()
5309 {
5310 conf->setValue("astrocalc/wut_altitude_min", QString::number(ui->wutAltitudeMinSpinBox->valueDegrees(), 'f', 2));
5311 calculateWutObjects();
5312 }
5313
saveWutAngularSizeFlag(bool state)5314 void AstroCalcDialog::saveWutAngularSizeFlag(bool state)
5315 {
5316 conf->setValue("astrocalc/wut_angular_limit_flag", state);
5317 calculateWutObjects();
5318 }
5319
5320
saveWutTimeInterval(int index)5321 void AstroCalcDialog::saveWutTimeInterval(int index)
5322 {
5323 Q_ASSERT(ui->wutComboBox);
5324 QComboBox* wutTimeInterval = ui->wutComboBox;
5325 conf->setValue("astrocalc/wut_time_interval", wutTimeInterval->itemData(index).toInt());
5326
5327 // Calculate WUT objects!
5328 calculateWutObjects();
5329 }
5330
setWUTHeaderNames(const bool magnitude,const bool separation)5331 void AstroCalcDialog::setWUTHeaderNames(const bool magnitude, const bool separation)
5332 {
5333 wutHeader.clear();
5334 wutHeader << q_("Name");
5335 if (magnitude)
5336 {
5337 // TRANSLATORS: magnitude
5338 wutHeader << q_("Mag.");
5339 }
5340 else
5341 {
5342 // TRANSLATORS: opacity
5343 wutHeader << q_("Opac.");
5344 }
5345 wutHeader << qc_("Rise", "celestial event");
5346 wutHeader << qc_("Transit", "celestial event; passage across a meridian");
5347 // TRANSLATORS: elevation
5348 wutHeader << q_("Elev.");
5349 wutHeader << qc_("Set", "celestial event");
5350 if (separation)
5351 {
5352 // TRANSLATORS: separation
5353 wutHeader << q_("Sep.");
5354 }
5355 else
5356 {
5357 // TRANSLATORS: angular size
5358 wutHeader << q_("Ang. Size");
5359 }
5360 // TRANSLATORS: IAU Constellation
5361 wutHeader << qc_("Const.", "IAU Constellation");
5362 ui->wutMatchingObjectsTreeWidget->setHeaderLabels(wutHeader);
5363
5364 adjustWUTColumns();
5365 }
5366
adjustWUTColumns()5367 void AstroCalcDialog::adjustWUTColumns()
5368 {
5369 // adjust the column width
5370 for (int i = 0; i < WUTCount; ++i)
5371 {
5372 ui->wutMatchingObjectsTreeWidget->resizeColumnToContents(i);
5373 }
5374 }
5375
initListWUT(const bool magnitude,const bool separation)5376 void AstroCalcDialog::initListWUT(const bool magnitude, const bool separation)
5377 {
5378 ui->wutMatchingObjectsTreeWidget->clear();
5379 ui->wutMatchingObjectsTreeWidget->setColumnCount(WUTCount);
5380 setWUTHeaderNames(magnitude, separation);
5381 ui->wutMatchingObjectsTreeWidget->header()->setSectionsMovable(false);
5382 ui->wutMatchingObjectsTreeWidget->header()->setDefaultAlignment(Qt::AlignHCenter);
5383 }
5384
enableAngularLimits(bool enable)5385 void AstroCalcDialog::enableAngularLimits(bool enable)
5386 {
5387 ui->wutAngularSizeLimitCheckBox->setEnabled(enable);
5388 ui->wutAngularSizeLimitMinSpinBox->setEnabled(enable);
5389 ui->wutAngularSizeLimitMaxSpinBox->setEnabled(enable);
5390 if (!enable)
5391 ui->wutMatchingObjectsTreeWidget->hideColumn(WUTAngularSize); // special case!
5392 }
5393
fillWUTTable(QString objectName,QString designation,float magnitude,Vec4d RTSTime,double maxElevation,double angularSize,QString constellation,bool decimalDegrees)5394 void AstroCalcDialog::fillWUTTable(QString objectName, QString designation, float magnitude, Vec4d RTSTime, double maxElevation, double angularSize, QString constellation, bool decimalDegrees)
5395 {
5396 QString sAngularSize = dash;
5397 QString sRise = dash;
5398 QString sTransit = dash;
5399 QString sSet = dash;
5400 QString sMaxElevation = dash;
5401 const double utcShift = core->getUTCOffset(core->getJD()) / 24.; // Fix DST shift...
5402
5403 WUTTreeWidgetItem* treeItem = new WUTTreeWidgetItem(ui->wutMatchingObjectsTreeWidget);
5404 treeItem->setData(WUTObjectName, Qt::DisplayRole, objectName);
5405 treeItem->setData(WUTObjectName, Qt::UserRole, designation);
5406 treeItem->setText(WUTMagnitude, magnitude > 98.f ? dash : QString::number(magnitude, 'f', 2));
5407 treeItem->setTextAlignment(WUTMagnitude, Qt::AlignRight);
5408
5409 sTransit = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(RTSTime[1] + utcShift), true);
5410 if (RTSTime[3]==0.)
5411 {
5412 sRise = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(RTSTime[0] + utcShift), true);
5413 sSet = StelUtils::hoursToHmsStr(StelUtils::getHoursFromJulianDay(RTSTime[2] + utcShift), true);
5414 }
5415
5416 treeItem->setText(WUTRiseTime, sRise);
5417 treeItem->setTextAlignment(WUTRiseTime, Qt::AlignRight);
5418 treeItem->setText(WUTTransitTime, sTransit);
5419 treeItem->setTextAlignment(WUTTransitTime, Qt::AlignRight);
5420
5421 if (decimalDegrees)
5422 sMaxElevation = StelUtils::radToDecDegStr(maxElevation, 5, false, true);
5423 else
5424 sMaxElevation = StelUtils::radToDmsPStr(maxElevation, 2);
5425 treeItem->setText(WUTMaxElevation, sMaxElevation);
5426 treeItem->setTextAlignment(WUTMaxElevation, Qt::AlignRight);
5427
5428 treeItem->setText(WUTSetTime, sSet);
5429 treeItem->setTextAlignment(WUTSetTime, Qt::AlignRight);
5430
5431 double angularSizeRad = angularSize * M_PI / 180.;
5432 if (angularSize>0.0)
5433 {
5434 if (decimalDegrees)
5435 sAngularSize = StelUtils::radToDecDegStr(angularSizeRad, 5, false, true);
5436 else
5437 sAngularSize = StelUtils::radToDmsPStr(angularSizeRad, 2);
5438 }
5439 treeItem->setText(WUTAngularSize, sAngularSize);
5440 treeItem->setTextAlignment(WUTAngularSize, Qt::AlignRight);
5441 treeItem->setText(WUTConstellation, constellation);
5442 treeItem->setTextAlignment(WUTConstellation, Qt::AlignHCenter);
5443 treeItem->setToolTip(WUTConstellation, q_("IAU Constellation"));
5444 }
5445
calculateWutObjects()5446 void AstroCalcDialog::calculateWutObjects()
5447 {
5448 if (ui->wutCategoryListWidget->currentItem())
5449 {
5450 QString categoryName = ui->wutCategoryListWidget->currentItem()->text();
5451 const WUTCategory categoryId = static_cast<WUTCategory> (wutCategories.value(categoryName));
5452
5453 QList<PlanetP> allObjects = solarSystem->getAllPlanets();
5454 QVector<NebulaP> allDSO = dsoMgr->getAllDeepSkyObjects();
5455 QList<StelObjectP> hipStars = starMgr->getHipparcosStars();
5456 QList<StelObjectP> carbonStars = starMgr->getHipparcosCarbonStars();
5457 QList<StelObjectP> bariumStars = starMgr->getHipparcosBariumStars();
5458 QList<StelACStarData> dblHipStars = starMgr->getHipparcosDoubleStars();
5459 QList<StelACStarData> varHipStars = starMgr->getHipparcosVariableStars();
5460 QList<StelACStarData> algolTypeStars = starMgr->getHipparcosAlgolTypeStars();
5461 QList<StelACStarData> classicalCepheidsTypeStars = starMgr->getHipparcosClassicalCepheidsTypeStars();
5462 QList<StelACStarData> hpmHipStars = starMgr->getHipparcosHighPMStars();
5463
5464 const Nebula::TypeGroup& tflags = dsoMgr->getTypeFilters();
5465 const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
5466 const bool angularSizeLimit = ui->wutAngularSizeLimitCheckBox->isChecked();
5467 bool passByType, visible = true;
5468 bool enableAngular = true;
5469 const double angularSizeLimitMin = ui->wutAngularSizeLimitMinSpinBox->valueDegrees();
5470 const double angularSizeLimitMax = ui->wutAngularSizeLimitMaxSpinBox->valueDegrees();
5471 const double altitudeLimitMin = ui->wutAltitudeMinSpinBox->valueDegrees();
5472 const float magLimit = static_cast<float>(ui->wutMagnitudeDoubleSpinBox->value());
5473 const double JD = core->getJD();
5474 double alt;
5475 float mag;
5476 QSet<QString> objectsList;
5477 QString designation, starName, constellation;
5478
5479 ui->wutAngularSizeLimitCheckBox->setText(q_("Limit angular size:"));
5480 ui->wutAngularSizeLimitCheckBox->setToolTip(q_("Set limits for angular size for visible celestial objects"));
5481 ui->wutAngularSizeLimitMinSpinBox->setToolTip(q_("Minimal angular size for visible celestial objects"));
5482 ui->wutAngularSizeLimitMaxSpinBox->setToolTip(q_("Maximum angular size for visible celestial objects"));
5483
5484 // Direct calculate sunrise/sunset (civil twilight)
5485 Vec4d rts = GETSTELMODULE(SolarSystem)->getSun()->getRTSTime(core, -7.);
5486 QList<double> wutJDList;
5487 wutJDList.clear();
5488
5489 QComboBox* wut = ui->wutComboBox;
5490 switch (wut->itemData(wut->currentIndex()).toInt())
5491 {
5492 case 1: // Morning
5493 wutJDList << rts[0];
5494 break;
5495 case 2: // Night
5496 wutJDList << rts[1] + 0.5;
5497 break;
5498 case 3:
5499 wutJDList << rts[0] << rts[1] + 0.5 << rts[2];
5500 break;
5501 default: // Evening
5502 wutJDList << rts[2];
5503 break;
5504 }
5505
5506 initListWUT();
5507 ui->wutMatchingObjectsTreeWidget->showColumn(WUTMagnitude);
5508 ui->wutMatchingObjectsTreeWidget->showColumn(WUTAngularSize);
5509 objectsList.clear();
5510 for (int i = 0; i < wutJDList.count(); i++)
5511 {
5512 core->setJD(wutJDList.at(i));
5513 core->update(0);
5514
5515 switch (categoryId)
5516 {
5517 case EWBrightStars:
5518 case EWCarbonStars:
5519 case EWBariumStars:
5520 {
5521 enableAngular = false;
5522 QList<StelObjectP> stars;
5523 if (categoryId==EWBrightStars)
5524 stars = hipStars;
5525 else if (categoryId==EWBariumStars)
5526 stars = bariumStars;
5527 else
5528 stars = carbonStars;
5529 for (const auto& object : qAsConst(stars))
5530 {
5531 // Filter for angular size is not applicable
5532 mag = object->getVMagnitude(core);
5533 if (mag <= magLimit && object->isAboveRealHorizon(core))
5534 {
5535 designation = object->getEnglishName();
5536 if (designation.isEmpty())
5537 designation = object->getID();
5538
5539 if (!objectsList.contains(designation))
5540 {
5541 starName = object->getNameI18n();
5542 if (starName.isEmpty())
5543 starName = designation;
5544
5545 rts = object->getRTSTime(core, altitudeLimitMin);
5546 alt = computeMaxElevation(object);
5547 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5548
5549 fillWUTTable(starName, designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5550 objectsList.insert(designation);
5551 }
5552 }
5553 }
5554 break;
5555 }
5556 case EWBrightNebulae:
5557 case EWDarkNebulae:
5558 case EWGalaxies:
5559 case EWOpenStarClusters:
5560 case EWPlanetaryNebulae:
5561 case EWSymbioticStars:
5562 case EWEmissionLineStars:
5563 case EWSupernovaeCandidates:
5564 case EWSupernovaeRemnantCandidates:
5565 case EWSupernovaeRemnants:
5566 case EWClustersOfGalaxies:
5567 case EWGlobularStarClusters:
5568 case EWRegionsOfTheSky:
5569 case EWInteractingGalaxies:
5570 case EWDeepSkyObjects:
5571 {
5572 if (categoryId==EWDarkNebulae)
5573 initListWUT(false, false); // special case!
5574 if (categoryId==EWSymbioticStars || categoryId==EWEmissionLineStars || categoryId==EWSupernovaeCandidates)
5575 enableAngular = false;
5576
5577 for (const auto& object : allDSO)
5578 {
5579 passByType = false;
5580 mag = object->getVMagnitude(core);
5581 Nebula::NebulaType ntype = object->getDSOType();
5582 switch (categoryId)
5583 {
5584 case EWBrightNebulae:
5585 if (static_cast<bool>(tflags & Nebula::TypeBrightNebulae) && (ntype == Nebula::NebN || ntype == Nebula::NebBn || ntype == Nebula::NebEn || ntype == Nebula::NebRn || ntype == Nebula::NebHII || ntype == Nebula::NebISM || ntype == Nebula::NebCn || ntype == Nebula::NebSNR) && mag <= magLimit)
5586 passByType = true;
5587 break;
5588 case EWDarkNebulae:
5589 if (static_cast<bool>(tflags & Nebula::TypeDarkNebulae) && (ntype == Nebula::NebDn || ntype == Nebula::NebMolCld || ntype == Nebula::NebYSO))
5590 passByType = true;
5591 break;
5592 case EWGalaxies:
5593 if (static_cast<bool>(tflags & Nebula::TypeGalaxies) && (ntype == Nebula::NebGx) && mag <= magLimit)
5594 passByType = true;
5595 break;
5596 case EWOpenStarClusters:
5597 if (static_cast<bool>(tflags & Nebula::TypeOpenStarClusters) && (ntype == Nebula::NebCl || ntype == Nebula::NebOc || ntype == Nebula::NebSA || ntype == Nebula::NebSC || ntype == Nebula::NebCn) && mag <= magLimit)
5598 passByType = true;
5599 break;
5600 case EWPlanetaryNebulae:
5601 if (static_cast<bool>(tflags & Nebula::TypePlanetaryNebulae) && (ntype == Nebula::NebPn || ntype == Nebula::NebPossPN || ntype == Nebula::NebPPN) && mag <= magLimit)
5602 passByType = true;
5603 break;
5604 case EWSymbioticStars:
5605 if (static_cast<bool>(tflags & Nebula::TypeOther) && (ntype == Nebula::NebSymbioticStar) && mag <= magLimit)
5606 passByType = true;
5607 break;
5608 case EWEmissionLineStars:
5609 if (static_cast<bool>(tflags & Nebula::TypeOther) && (ntype == Nebula::NebEmissionLineStar) && mag <= magLimit)
5610 passByType = true;
5611 break;
5612 case EWSupernovaeCandidates:
5613 {
5614 visible = ((mag <= magLimit) || (mag > 90.f && magLimit >= 19.f));
5615 if (static_cast<bool>(tflags & Nebula::TypeSupernovaRemnants) && (ntype == Nebula::NebSNC) && visible)
5616 passByType = true;
5617 break;
5618 }
5619 case EWSupernovaeRemnantCandidates:
5620 {
5621 visible = ((mag <= magLimit) || (mag > 90.f && magLimit >= 19.f));
5622 if (static_cast<bool>(tflags & Nebula::TypeSupernovaRemnants) && (ntype == Nebula::NebSNRC) && visible)
5623 passByType = true;
5624 break;
5625 }
5626 case EWSupernovaeRemnants:
5627 {
5628 visible = ((mag <= magLimit) || (mag > 90.f && magLimit >= 19.f));
5629 if (static_cast<bool>(tflags & Nebula::TypeSupernovaRemnants) && (ntype == Nebula::NebSNR) && visible)
5630 passByType = true;
5631 break;
5632 }
5633 case EWClustersOfGalaxies:
5634 if (static_cast<bool>(tflags & Nebula::TypeGalaxyClusters) && (ntype == Nebula::NebGxCl) && mag <= magLimit)
5635 passByType = true;
5636 break;
5637 case EWGlobularStarClusters:
5638 if ((static_cast<bool>(tflags & Nebula::TypeGlobularStarClusters) && ntype == Nebula::NebGc) && mag <= magLimit)
5639 passByType = true;
5640 break;
5641 case EWRegionsOfTheSky:
5642 if (static_cast<bool>(tflags & Nebula::TypeOther) && ntype == Nebula::NebRegion)
5643 passByType = true;
5644 break;
5645 case EWInteractingGalaxies:
5646 if (static_cast<bool>(tflags & Nebula::TypeInteractingGalaxies) && (ntype == Nebula::NebIGx) && mag <= magLimit)
5647 passByType = true;
5648 break;
5649 case EWDeepSkyObjects:
5650 if (mag <= magLimit)
5651 passByType = true;
5652 if (ntype == Nebula::NebDn)
5653 mag = 99.f;
5654 break;
5655 default:
5656 break;
5657 }
5658
5659 if (passByType && object->isAboveRealHorizon(core))
5660 {
5661 QString d = object->getDSODesignation();
5662 if (d.isEmpty())
5663 d = object->getDSODesignationWIC();
5664 QString n = object->getNameI18n();
5665
5666 if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
5667 continue;
5668
5669 if (d.isEmpty() && n.isEmpty())
5670 continue;
5671
5672 designation = QString("%1:%2").arg(d, n);
5673 if (!objectsList.contains(designation))
5674 {
5675 rts = object->getRTSTime(core, altitudeLimitMin);
5676 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5677 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5678
5679 if (d.isEmpty())
5680 fillWUTTable(n, n, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5681 else if (n.isEmpty())
5682 fillWUTTable(d, d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5683 else
5684 fillWUTTable(QString("%1 (%2)").arg(d, n), d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5685
5686 objectsList.insert(designation);
5687 }
5688 }
5689 }
5690
5691 if (categoryId==EWRegionsOfTheSky) // special case!
5692 {
5693 ui->wutMatchingObjectsTreeWidget->hideColumn(WUTAngularSize);
5694 ui->wutMatchingObjectsTreeWidget->hideColumn(WUTMagnitude);
5695 }
5696
5697 break;
5698 }
5699 case EWPlanets:
5700 case EWAsteroids:
5701 case EWComets:
5702 case EWPlutinos:
5703 case EWDwarfPlanets:
5704 case EWCubewanos:
5705 case EWScatteredDiscObjects:
5706 case EWOortCloudObjects:
5707 case EWSednoids:
5708 case EWInterstellarObjects:
5709 {
5710 static const QMap<int, Planet::PlanetType>map = {
5711 {EWPlanets, Planet::isPlanet},
5712 {EWAsteroids, Planet::isAsteroid},
5713 {EWComets, Planet::isComet},
5714 {EWPlutinos, Planet::isPlutino},
5715 {EWDwarfPlanets, Planet::isDwarfPlanet},
5716 {EWCubewanos, Planet::isCubewano},
5717 {EWScatteredDiscObjects, Planet::isSDO},
5718 {EWOortCloudObjects, Planet::isOCO},
5719 {EWSednoids, Planet::isSednoid},
5720 {EWInterstellarObjects, Planet::isInterstellar}};
5721 const Planet::PlanetType pType = map.value(categoryId, Planet::isInterstellar);
5722
5723 for (const auto& object : allObjects)
5724 {
5725 mag = object->getVMagnitude(core);
5726 if (object->getPlanetType() == pType && mag <= magLimit && object->isAboveRealHorizon(core))
5727 {
5728 if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
5729 continue;
5730
5731 designation = object->getEnglishName();
5732 if (!objectsList.contains(designation))
5733 {
5734 rts = object->getRTSTime(core, altitudeLimitMin);
5735 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5736 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5737
5738 fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 2.0*object->getAngularSize(core), constellation, withDecimalDegree);
5739 objectsList.insert(designation);
5740 }
5741 }
5742 }
5743
5744 if (pType==Planet::isComet)
5745 ui->wutMatchingObjectsTreeWidget->hideColumn(WUTAngularSize); // special case!
5746
5747 break;
5748 }
5749 case EWBrightDoubleStars:
5750 // Special case for double stars
5751 ui->wutAngularSizeLimitCheckBox->setText(q_("Limit angular separation:"));
5752 ui->wutAngularSizeLimitCheckBox->setToolTip(q_("Set limits for angular separation for visible double stars"));
5753 ui->wutAngularSizeLimitMinSpinBox->setToolTip(q_("Minimal angular separation for visible double stars"));
5754 ui->wutAngularSizeLimitMaxSpinBox->setToolTip(q_("Maximum angular separation for visible double stars"));
5755 if (i==0)
5756 initListWUT(true, true); // special case!
5757
5758 for (const auto& dblStar : dblHipStars)
5759 {
5760 StelObjectP object = dblStar.firstKey();
5761 mag = object->getVMagnitude(core);
5762 if (mag <= magLimit && object->isAboveRealHorizon(core))
5763 {
5764 // convert from arc-seconds to degrees
5765 if ((angularSizeLimit) && (!StelUtils::isWithin(static_cast<double>(dblStar.value(object))/3600.0, angularSizeLimitMin, angularSizeLimitMax)))
5766 continue;
5767
5768 designation = object->getEnglishName();
5769 if (designation.isEmpty())
5770 designation = object->getID();
5771
5772 if (!objectsList.contains(designation))
5773 {
5774 starName = object->getNameI18n();
5775 if (starName.isEmpty())
5776 starName = designation;
5777
5778 rts = object->getRTSTime(core, altitudeLimitMin);
5779 alt = computeMaxElevation(object);
5780 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5781
5782 fillWUTTable(starName, designation, mag, rts, alt, dblStar.value(object)/3600.0, constellation, withDecimalDegree);
5783 objectsList.insert(designation);
5784 }
5785 }
5786 }
5787 break;
5788 case EWAlgolTypeVariableStars:
5789 case EWClassicalCepheidsTypeVariableStars:
5790 case EWBrightVariableStars:
5791 {
5792 enableAngular = false;
5793 static QMap<int, QList<StelACStarData>>map = {
5794 {EWAlgolTypeVariableStars, algolTypeStars},
5795 {EWClassicalCepheidsTypeVariableStars, classicalCepheidsTypeStars},
5796 {EWBrightVariableStars, varHipStars}};
5797 for (const auto& varStar : map.value(categoryId, varHipStars))
5798 {
5799 StelObjectP object = varStar.firstKey();
5800 mag = object->getVMagnitude(core);
5801 if (mag <= magLimit && object->isAboveRealHorizon(core))
5802 {
5803 designation = object->getEnglishName();
5804 if (designation.isEmpty())
5805 designation = object->getID();
5806
5807 if (!objectsList.contains(designation))
5808 {
5809 starName = object->getNameI18n();
5810 if (starName.isEmpty())
5811 starName = designation;
5812
5813 rts = object->getRTSTime(core, altitudeLimitMin);
5814 alt = computeMaxElevation(object);
5815 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5816
5817 fillWUTTable(starName, designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5818 objectsList.insert(designation);
5819 }
5820 }
5821 }
5822 break;
5823 }
5824 case EWBrightStarsWithHighProperMotion:
5825 enableAngular = false;
5826 for (const auto& hpmStar : hpmHipStars)
5827 {
5828 StelObjectP object = hpmStar.firstKey();
5829 mag = object->getVMagnitude(core);
5830 if (mag <= magLimit && object->isAboveRealHorizon(core))
5831 {
5832 designation = object->getEnglishName();
5833 if (designation.isEmpty())
5834 designation = object->getID();
5835
5836 if (!objectsList.contains(designation))
5837 {
5838 starName = object->getNameI18n();
5839 if (starName.isEmpty())
5840 starName = designation;
5841
5842 rts = object->getRTSTime(core, altitudeLimitMin);
5843 alt = computeMaxElevation(object);
5844 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5845
5846 fillWUTTable(starName, designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5847 objectsList.insert(designation);
5848 }
5849 }
5850 }
5851 break;
5852 case EWActiveGalaxies:
5853 enableAngular = false;
5854 for (const auto& object : allDSO)
5855 {
5856 passByType = false;
5857 mag = object->getVMagnitude(core);
5858 Nebula::NebulaType ntype = object->getDSOType();
5859 if (static_cast<bool>(tflags & Nebula::TypeActiveGalaxies) && (ntype == Nebula::NebQSO || ntype == Nebula::NebPossQSO || ntype == Nebula::NebAGx || ntype == Nebula::NebRGx || ntype == Nebula::NebBLA || ntype == Nebula::NebBLL) && mag <= magLimit && object->isAboveRealHorizon(core))
5860 {
5861 QString d = object->getDSODesignation();
5862 if (d.isEmpty())
5863 d = object->getDSODesignationWIC();
5864 QString n = object->getNameI18n();
5865
5866 if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
5867 continue;
5868
5869 if (d.isEmpty() && n.isEmpty())
5870 continue;
5871
5872 designation = QString("%1:%2").arg(d, n);
5873 if (!objectsList.contains(designation))
5874 {
5875 rts = object->getRTSTime(core, altitudeLimitMin);
5876 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5877 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5878
5879 if (d.isEmpty())
5880 fillWUTTable(n, n, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5881 else if (n.isEmpty())
5882 fillWUTTable(d, d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5883 else
5884 fillWUTTable(QString("%1 (%2)").arg(d, n), d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
5885
5886 objectsList.insert(designation);
5887 }
5888 }
5889 }
5890
5891 #ifdef USE_STATIC_PLUGIN_QUASARS
5892 if (StelApp::getInstance().getModuleMgr().isPluginLoaded("Quasars"))
5893 {
5894 if (propMgr->getProperty("Quasars.quasarsVisible")->getValue().toBool())
5895 {
5896 for (const auto& object : GETSTELMODULE(Quasars)->getAllQuasars())
5897 {
5898 mag = object->getVMagnitude(core);
5899 if (mag <= magLimit && object->isAboveRealHorizon(core))
5900 {
5901 designation = object->getEnglishName();
5902 if (!objectsList.contains(designation) && !designation.isEmpty())
5903 {
5904 rts = object->getRTSTime(core, altitudeLimitMin);
5905 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5906 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5907
5908 fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5909 objectsList.insert(designation);
5910 }
5911 }
5912 }
5913 }
5914 }
5915 #endif
5916 break;
5917 case EWPulsars:
5918 enableAngular = false;
5919 #ifdef USE_STATIC_PLUGIN_PULSARS
5920 for (const auto& object : GETSTELMODULE(Pulsars)->getAllPulsars())
5921 {
5922 if (object->isAboveRealHorizon(core))
5923 {
5924 designation = object->getEnglishName();
5925 if (designation.isEmpty())
5926 designation = object->getDesignation();
5927
5928 if (!objectsList.contains(designation) && !designation.isEmpty())
5929 {
5930 starName = object->getNameI18n(); // Just re-use variable
5931 if (starName.isEmpty())
5932 starName = designation;
5933
5934 rts = object->getRTSTime(core, altitudeLimitMin);
5935 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5936 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5937
5938 fillWUTTable(starName, designation, 99.f, rts, alt, 0.0, constellation, withDecimalDegree);
5939 objectsList.insert(designation);
5940 }
5941 }
5942 }
5943 ui->wutMatchingObjectsTreeWidget->hideColumn(WUTMagnitude); // special case!
5944 #endif
5945 break;
5946 case EWExoplanetarySystems:
5947 enableAngular = false;
5948 #ifdef USE_STATIC_PLUGIN_EXOPLANETS
5949 for (const auto& object : GETSTELMODULE(Exoplanets)->getAllExoplanetarySystems())
5950 {
5951 mag = object->getVMagnitude(core);
5952 if (mag <= magLimit && object->isVMagnitudeDefined() && object->isAboveRealHorizon(core))
5953 {
5954 designation = object->getEnglishName();
5955 if (!objectsList.contains(designation) && !designation.isEmpty())
5956 {
5957 rts = object->getRTSTime(core, altitudeLimitMin);
5958 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5959 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5960
5961 fillWUTTable(object->getNameI18n().trimmed(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5962 objectsList.insert(designation);
5963 }
5964 }
5965 }
5966 #endif
5967 break;
5968 case EWBrightNovaStars:
5969 enableAngular = false;
5970 #ifdef USE_STATIC_PLUGIN_NOVAE
5971 for (const auto& object : GETSTELMODULE(Novae)->getAllBrightNovae())
5972 {
5973 mag = object->getVMagnitude(core);
5974 if (mag <= magLimit && object->isAboveRealHorizon(core))
5975 {
5976 designation = object->getEnglishName();
5977 if (!objectsList.contains(designation))
5978 {
5979 rts = object->getRTSTime(core, altitudeLimitMin);
5980 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
5981 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
5982
5983 fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
5984 objectsList.insert(designation);
5985 }
5986 }
5987 }
5988 #endif
5989 break;
5990 case EWBrightSupernovaStars:
5991 enableAngular = false;
5992 #ifdef USE_STATIC_PLUGIN_SUPERNOVAE
5993 for (const auto& object : GETSTELMODULE(Supernovae)->getAllBrightSupernovae())
5994 {
5995 mag = object->getVMagnitude(core);
5996 if (mag <= magLimit && object->isAboveRealHorizon(core))
5997 {
5998 designation = object->getEnglishName();
5999 if (!objectsList.contains(designation))
6000 {
6001 rts = object->getRTSTime(core, altitudeLimitMin);
6002 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
6003 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
6004
6005 fillWUTTable(object->getNameI18n(), designation, mag, rts, alt, 0.0, constellation, withDecimalDegree);
6006 objectsList.insert(designation);
6007 }
6008 }
6009 }
6010 #endif
6011 break;
6012 case EWMessierObjects:
6013 case EWNGCICObjects:
6014 case EWCaldwellObjects:
6015 case EWHerschel400Objects:
6016 {
6017 QList<NebulaP> catDSO;
6018 switch (categoryId)
6019 {
6020 case EWMessierObjects:
6021 catDSO = dsoMgr->getDeepSkyObjectsByType("100");
6022 break;
6023 case EWNGCICObjects:
6024 catDSO = dsoMgr->getDeepSkyObjectsByType("108");
6025 catDSO.append(dsoMgr->getDeepSkyObjectsByType("109"));
6026 break;
6027 case EWCaldwellObjects:
6028 catDSO = dsoMgr->getDeepSkyObjectsByType("101");
6029 break;
6030 case EWHerschel400Objects:
6031 catDSO = dsoMgr->getDeepSkyObjectsByType("151");
6032 break;
6033 default:
6034 qWarning() << "catDSO: should never come here";
6035 break;
6036 }
6037
6038 for (const auto& object : catDSO)
6039 {
6040 mag = object->getVMagnitude(core);
6041 if (mag <= magLimit && object->isAboveRealHorizon(core))
6042 {
6043 QString d = object->getDSODesignation();
6044 if (d.isEmpty())
6045 d = object->getDSODesignationWIC();
6046 QString n = object->getNameI18n();
6047
6048 if ((angularSizeLimit) && (!StelUtils::isWithin(object->getAngularSize(core), angularSizeLimitMin, angularSizeLimitMax)))
6049 continue;
6050
6051 if (d.isEmpty() && n.isEmpty())
6052 continue;
6053
6054 designation = QString("%1:%2").arg(d, n);
6055 if (!objectsList.contains(designation))
6056 {
6057 rts = object->getRTSTime(core, altitudeLimitMin);
6058 alt = computeMaxElevation(qSharedPointerCast<StelObject>(object));
6059 constellation = core->getIAUConstellation(object->getEquinoxEquatorialPos(core));
6060
6061 if (d.isEmpty())
6062 fillWUTTable(n, n, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
6063 else if (n.isEmpty())
6064 fillWUTTable(d, d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
6065 else
6066 fillWUTTable(QString("%1 (%2)").arg(d, n), d, mag, rts, alt, object->getAngularSize(core), constellation, withDecimalDegree);
6067
6068 objectsList.insert(designation);
6069 }
6070 }
6071 }
6072 break;
6073 }
6074 default:
6075 qWarning() << "unknown WUTCategory " << categoryId;
6076 break;
6077 }
6078 }
6079
6080 enableAngularLimits(enableAngular);
6081 core->setJD(JD);
6082 adjustWUTColumns();
6083 objectsList.clear();
6084 }
6085 }
6086
selectWutObject(const QModelIndex & index)6087 void AstroCalcDialog::selectWutObject(const QModelIndex &index)
6088 {
6089 if (index.isValid())
6090 {
6091 // Find the object
6092 QString wutObjectEnglisName = index.sibling(index.row(),WUTObjectName).data(Qt::UserRole).toString();
6093 if (objectMgr->findAndSelectI18n(wutObjectEnglisName) || objectMgr->findAndSelect(wutObjectEnglisName))
6094 {
6095 const QList<StelObjectP> newSelected = objectMgr->getSelectedObject();
6096 if (!newSelected.empty())
6097 {
6098 // Can't point to home planet
6099 if (newSelected[0]->getEnglishName() != core->getCurrentLocation().planetName)
6100 {
6101 mvMgr->moveToObject(newSelected[0], mvMgr->getAutoMoveDuration());
6102 mvMgr->setFlagTracking(true);
6103 }
6104 else
6105 GETSTELMODULE(StelObjectMgr)->unSelect();
6106 }
6107 }
6108 }
6109 }
6110
saveWutObjects()6111 void AstroCalcDialog::saveWutObjects()
6112 {
6113 QString filter = q_("Microsoft Excel Open XML Spreadsheet");
6114 filter.append(" (*.xlsx);;");
6115 filter.append(q_("CSV (Comma delimited)"));
6116 filter.append(" (*.csv)");
6117 // filter.append(q_("JSON (Stellarium bookmarks)"));
6118 // filter.append(" (*.json)");
6119 QString defaultFilter("(*.xlsx)");
6120 QString filePath = QFileDialog::getSaveFileName(Q_NULLPTR,
6121 q_("Save list of objects as..."),
6122 QDir::homePath() + "/wut-objects.xlsx",
6123 filter,
6124 &defaultFilter);
6125
6126 if (defaultFilter.contains(".csv", Qt::CaseInsensitive))
6127 saveTableAsCSV(filePath, ui->wutMatchingObjectsTreeWidget, wutHeader);
6128 else if (defaultFilter.contains(".json", Qt::CaseInsensitive))
6129 saveTableAsBookmarks(filePath, ui->wutMatchingObjectsTreeWidget);
6130 else
6131 {
6132 int count = ui->wutMatchingObjectsTreeWidget->topLevelItemCount();
6133 int columns = wutHeader.size();
6134
6135 int *width;
6136 width = new int[static_cast<unsigned int>(columns)];
6137 QString sData;
6138 int w;
6139
6140 QXlsx::Document xlsx;
6141 xlsx.setDocumentProperty("title", q_("What's Up Tonight"));
6142 xlsx.setDocumentProperty("creator", StelUtils::getApplicationName());
6143 if (ui->wutCategoryListWidget->currentRow()>0) // Fixed crash when category of objects is not selected
6144 xlsx.addSheet(ui->wutCategoryListWidget->currentItem()->text(), AbstractSheet::ST_WorkSheet);
6145
6146 QXlsx::Format header;
6147 header.setHorizontalAlignment(QXlsx::Format::AlignHCenter);
6148 header.setPatternBackgroundColor(Qt::yellow);
6149 header.setBorderStyle(QXlsx::Format::BorderThin);
6150 header.setBorderColor(Qt::black);
6151 header.setFontBold(true);
6152 for (int i = 0; i < columns; i++)
6153 {
6154 // Row 1: Names of columns
6155 sData = wutHeader.at(i).trimmed();
6156 xlsx.write(1, i + 1, sData, header);
6157 width[i] = sData.size();
6158 }
6159
6160 QXlsx::Format data;
6161 data.setHorizontalAlignment(QXlsx::Format::AlignRight);
6162 QXlsx::Format left;
6163 left.setHorizontalAlignment(QXlsx::Format::AlignLeft);
6164 for (int i = 0; i < count; i++)
6165 {
6166 for (int j = 0; j < columns; j++)
6167 {
6168 // Row 2 and next: the data
6169 sData = ui->wutMatchingObjectsTreeWidget->topLevelItem(i)->text(j).trimmed();
6170 xlsx.write(i + 2, j + 1, sData, j==0 ? left : data);
6171 w = sData.size();
6172 if (w > width[j])
6173 {
6174 width[j] = w;
6175 }
6176 }
6177 }
6178
6179 for (int i = 0; i < columns; i++)
6180 {
6181 xlsx.setColumnWidth(i+1, width[i]+2);
6182 }
6183
6184 delete[] width;
6185 xlsx.saveAs(filePath);
6186 }
6187 }
6188
saveFirstCelestialBody(int index)6189 void AstroCalcDialog::saveFirstCelestialBody(int index)
6190 {
6191 Q_ASSERT(ui->firstCelestialBodyComboBox);
6192 QComboBox* celestialBody = ui->firstCelestialBodyComboBox;
6193 conf->setValue("astrocalc/first_celestial_body", celestialBody->itemData(index).toString());
6194
6195 computePlanetaryData();
6196 drawDistanceGraph();
6197 }
6198
saveSecondCelestialBody(int index)6199 void AstroCalcDialog::saveSecondCelestialBody(int index)
6200 {
6201 Q_ASSERT(ui->secondCelestialBodyComboBox);
6202 QComboBox* celestialBody = ui->secondCelestialBodyComboBox;
6203 conf->setValue("astrocalc/second_celestial_body", celestialBody->itemData(index).toString());
6204
6205 computePlanetaryData();
6206 drawDistanceGraph();
6207 }
6208
computePlanetaryData()6209 void AstroCalcDialog::computePlanetaryData()
6210 {
6211 Q_ASSERT(ui->firstCelestialBodyComboBox);
6212 Q_ASSERT(ui->secondCelestialBodyComboBox);
6213
6214 QComboBox* fbody = ui->firstCelestialBodyComboBox;
6215 QComboBox* sbody = ui->secondCelestialBodyComboBox;
6216
6217 QString firstCelestialBody = fbody->currentData(Qt::UserRole).toString();
6218 QString secondCelestialBody = sbody->currentData(Qt::UserRole).toString();
6219 QString currentPlanet = core->getCurrentPlanet()->getEnglishName();
6220
6221 PlanetP firstCBId = solarSystem->searchByEnglishName(firstCelestialBody);
6222 Vec3d posFCB = firstCBId->getJ2000EquatorialPos(core);
6223 PlanetP secondCBId = solarSystem->searchByEnglishName(secondCelestialBody);
6224 Vec3d posSCB = secondCBId->getJ2000EquatorialPos(core);
6225
6226 const double distanceAu = (posFCB - posSCB).length();
6227 const double distanceKm = AU * distanceAu;
6228 QString degree = QChar(0x00B0);
6229 // TRANSLATORS: Unit of measure for distance - kilometers
6230 QString km = qc_("km", "distance");
6231 // TRANSLATORS: Unit of measure for distance - millions kilometers
6232 QString Mkm = qc_("M km", "distance");
6233 const bool useKM = (distanceAu < 0.1);
6234 QString distAU = QString::number(distanceAu, 'f', 5);
6235 QString distKM = useKM ? QString::number(distanceKm, 'f', 3) : QString::number(distanceKm / 1.0e6, 'f', 3);
6236
6237 //const double r = std::acos(sin(posFCB.latitude()) * sin(posSCB.latitude()) + cos(posFCB.latitude()) * cos(posSCB.latitude()) * cos(posFCB.longitude() - posSCB.longitude()));
6238 const double r= posFCB.angle(posSCB);
6239
6240 unsigned int d, m;
6241 double s, dd;
6242 bool sign;
6243
6244 StelUtils::radToDms(r, sign, d, m, s);
6245 StelUtils::radToDecDeg(r, sign, dd);
6246
6247 double spcb1 = firstCBId->getSiderealPeriod();
6248 double spcb2 = secondCBId->getSiderealPeriod();
6249 QString parentFCBName = "";
6250 if (firstCelestialBody != "Sun")
6251 parentFCBName = firstCBId->getParent()->getEnglishName();
6252 QString parentSCBName = "";
6253 if (secondCelestialBody != "Sun")
6254 parentSCBName = secondCBId->getParent()->getEnglishName();
6255
6256 QString distanceUM = qc_("AU", "distance, astronomical unit");
6257 ui->labelLinearDistanceValue->setText(QString("%1 %2 (%3 %4)").arg(distAU, distanceUM, distKM, useKM ? km : Mkm));
6258
6259 QString angularDistance = dash;
6260 if (firstCelestialBody != currentPlanet && secondCelestialBody != currentPlanet)
6261 angularDistance = QString("%1%2 %3' %4\" (%5%2)").arg(d).arg(degree).arg(m).arg(s, 0, 'f', 2).arg(dd, 0, 'f', 5);
6262 ui->labelAngularDistanceValue->setText(angularDistance);
6263
6264 // TRANSLATORS: Part of unit of measure for mean motion - degrees per day
6265 QString day = qc_("day", "mean motion");
6266 // TRANSLATORS: Unit of measure for period - days
6267 QString days = qc_("days", "duration");
6268 QString synodicPeriod = dash;
6269 QString orbitalPeriodsRatio = dash;
6270 if (spcb1 > 0.0 && spcb2 > 0.0 && parentFCBName==parentSCBName && firstCelestialBody!="Sun")
6271 {
6272 double sp = qAbs(1/(1/spcb1 - 1/spcb2));
6273 synodicPeriod = QString("%1 %2 (%3 a)").arg(QString::number(sp, 'f', 3), days, QString::number(sp/365.25, 'f', 5));
6274
6275 double minp = spcb2;
6276 if (qAbs(spcb1)<=qAbs(spcb2)) { minp = spcb1; }
6277 int a = qRound(qAbs(spcb1/minp)*10);
6278 int b = qRound(qAbs(spcb2/minp)*10);
6279 int lcm = qAbs(a*b)/StelUtils::gcd(a, b);
6280 orbitalPeriodsRatio = QString("%1:%2").arg(lcm/a).arg(lcm/b);
6281 }
6282 ui->labelSynodicPeriodValue->setText(synodicPeriod);
6283 ui->labelOrbitalPeriodsRatioValue->setText(orbitalPeriodsRatio);
6284
6285 if (spcb1>0. && firstCelestialBody!="Sun")
6286 ui->labelMeanMotionFCBValue->setText(QString("%1 %2/%3").arg(QString::number(360./spcb1, 'f', 5), degree, day));
6287 else
6288 ui->labelMeanMotionFCBValue->setText(dash);
6289
6290 if (spcb2>0. && secondCelestialBody!="Sun")
6291 ui->labelMeanMotionSCBValue->setText(QString("%1 %2/%3").arg(QString::number(360./spcb2, 'f', 5), degree, day));
6292 else
6293 ui->labelMeanMotionSCBValue->setText(dash);
6294
6295 // TRANSLATORS: Unit of measure for speed - kilometers per second
6296 QString kms = qc_("km/s", "speed");
6297
6298 double orbVelFCB = firstCBId->getEclipticVelocity().length();
6299 QString orbitalVelocityFCB = orbVelFCB<=0. ? dash : QString("%1 %2").arg(QString::number(orbVelFCB * AU/86400., 'f', 3), kms);
6300 ui->labelOrbitalVelocityFCBValue->setText(orbitalVelocityFCB);
6301
6302 double orbVelSCB = secondCBId->getEclipticVelocity().length();
6303 QString orbitalVelocitySCB = orbVelSCB<=0. ? dash : QString("%1 %2").arg(QString::number(orbVelSCB * AU/86400., 'f', 3), kms);
6304 ui->labelOrbitalVelocitySCBValue->setText(orbitalVelocitySCB);
6305
6306 double fcbs = 2.0 * AU * firstCBId->getEquatorialRadius();
6307 double scbs = 2.0 * AU * secondCBId->getEquatorialRadius();
6308 double sratio = fcbs/scbs;
6309 int ss = (sratio < 1.0 ? 6 : 2);
6310
6311 QString sizeRatio = QString("%1 (%2 %4 / %3 %4)").arg(QString::number(sratio, 'f', ss), QString::number(fcbs, 'f', 1), QString::number(scbs, 'f', 1), km);
6312 ui->labelEquatorialRadiiRatioValue->setText(sizeRatio);
6313 }
6314
prepareDistanceAxesAndGraph()6315 void AstroCalcDialog::prepareDistanceAxesAndGraph()
6316 {
6317 QString xAxisStr = q_("Days from today");
6318 QString yAxisLegend1 = QString("%1, %2").arg(q_("Linear distance"), qc_("AU", "distance, astronomical unit"));
6319 QString yAxisLegend2 = QString("%1, %2").arg(q_("Angular distance"), QChar(0x00B0)); // decimal degrees
6320
6321 QColor axisColor(Qt::white);
6322 QPen axisPen(axisColor, 1);
6323 QColor axisColorL(Qt::green);
6324 QPen axisPenL(axisColorL, 1);
6325 QColor axisColorR(Qt::yellow);
6326 QPen axisPenR(axisColorR, 1);
6327
6328 ui->pcDistanceGraphPlot->xAxis->setLabel(xAxisStr);
6329 ui->pcDistanceGraphPlot->yAxis->setLabel(yAxisLegend1);
6330 ui->pcDistanceGraphPlot->yAxis2->setLabel(yAxisLegend2);
6331
6332 ui->pcDistanceGraphPlot->xAxis->setRange(-300, 300);
6333 ui->pcDistanceGraphPlot->xAxis->setScaleType(QCPAxis::stLinear);
6334 ui->pcDistanceGraphPlot->xAxis->setLabelColor(axisColor);
6335 ui->pcDistanceGraphPlot->xAxis->setTickLabelColor(axisColor);
6336 ui->pcDistanceGraphPlot->xAxis->setBasePen(axisPen);
6337 ui->pcDistanceGraphPlot->xAxis->setTickPen(axisPen);
6338 ui->pcDistanceGraphPlot->xAxis->setSubTickPen(axisPen);
6339 ui->pcDistanceGraphPlot->xAxis->setAutoTicks(true);
6340 ui->pcDistanceGraphPlot->xAxis->setAutoTickCount(15);
6341
6342 ui->pcDistanceGraphPlot->yAxis->setRange(minYld, maxYld);
6343 ui->pcDistanceGraphPlot->yAxis->setScaleType(QCPAxis::stLinear);
6344 ui->pcDistanceGraphPlot->yAxis->setLabelColor(axisColorL);
6345 ui->pcDistanceGraphPlot->yAxis->setTickLabelColor(axisColorL);
6346 ui->pcDistanceGraphPlot->yAxis->setBasePen(axisPenL);
6347 ui->pcDistanceGraphPlot->yAxis->setTickPen(axisPenL);
6348 ui->pcDistanceGraphPlot->yAxis->setSubTickPen(axisPenL);
6349
6350 ui->pcDistanceGraphPlot->yAxis2->setRange(minYad, maxYad);
6351 ui->pcDistanceGraphPlot->yAxis2->setScaleType(QCPAxis::stLinear);
6352 ui->pcDistanceGraphPlot->yAxis2->setLabelColor(axisColorR);
6353 ui->pcDistanceGraphPlot->yAxis2->setTickLabelColor(axisColorR);
6354 ui->pcDistanceGraphPlot->yAxis2->setBasePen(axisPenR);
6355 ui->pcDistanceGraphPlot->yAxis2->setTickPen(axisPenR);
6356 ui->pcDistanceGraphPlot->yAxis2->setSubTickPen(axisPenR);
6357 ui->pcDistanceGraphPlot->yAxis2->setVisible(true);
6358
6359 ui->pcDistanceGraphPlot->clearGraphs();
6360 ui->pcDistanceGraphPlot->addGraph(ui->pcDistanceGraphPlot->xAxis, ui->pcDistanceGraphPlot->yAxis);
6361 ui->pcDistanceGraphPlot->setBackground(QBrush(QColor(86, 87, 90)));
6362 ui->pcDistanceGraphPlot->graph(0)->setPen(axisPenL);
6363 ui->pcDistanceGraphPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
6364
6365 ui->pcDistanceGraphPlot->addGraph(ui->pcDistanceGraphPlot->xAxis, ui->pcDistanceGraphPlot->yAxis2);
6366 ui->pcDistanceGraphPlot->setBackground(QBrush(QColor(86, 87, 90)));
6367 ui->pcDistanceGraphPlot->graph(1)->setPen(axisPenR);
6368 ui->pcDistanceGraphPlot->graph(1)->setLineStyle(QCPGraph::lsLine);
6369 }
6370
drawDistanceGraph()6371 void AstroCalcDialog::drawDistanceGraph()
6372 {
6373 // special case - plot the graph when tab is visible
6374 if (!plotDistanceGraph || !dialog->isVisible())
6375 return;
6376
6377 Q_ASSERT(ui->firstCelestialBodyComboBox);
6378 Q_ASSERT(ui->secondCelestialBodyComboBox);
6379
6380 QComboBox* fbody = ui->firstCelestialBodyComboBox;
6381 QComboBox* sbody = ui->secondCelestialBodyComboBox;
6382
6383 PlanetP currentPlanet = core->getCurrentPlanet();
6384 PlanetP firstCBId = solarSystem->searchByEnglishName(fbody->currentData(Qt::UserRole).toString());
6385 PlanetP secondCBId = solarSystem->searchByEnglishName(sbody->currentData(Qt::UserRole).toString());
6386
6387 if (firstCBId==secondCBId)
6388 {
6389 ui->pcDistanceGraphPlot->graph(0)->clearData();
6390 ui->pcDistanceGraphPlot->graph(1)->clearData();
6391 ui->pcDistanceGraphPlot->replot();
6392 return;
6393 }
6394
6395 int limit = 76, step = 4;
6396 if (firstCBId->getParent() == currentPlanet || secondCBId->getParent() == currentPlanet)
6397 {
6398 limit = 151; step = 2;
6399 }
6400
6401 QList<double> aX, aY1, aY2;
6402 const double currentJD = core->getJD();
6403 for (int i = (-1*limit); i <= limit; i++)
6404 {
6405 double JD = currentJD + i*step;
6406 core->setJD(JD);
6407 Vec3d posFCB = firstCBId->getJ2000EquatorialPos(core);
6408 Vec3d posSCB = secondCBId->getJ2000EquatorialPos(core);
6409 double distanceAu = (posFCB - posSCB).length();
6410 //r = std::acos(sin(posFCB.latitude()) * sin(posSCB.latitude()) + cos(posFCB.latitude()) * cos(posSCB.latitude()) * cos(posFCB.longitude() - posSCB.longitude()));
6411 double r= posFCB.angle(posSCB);
6412 double dd;
6413 bool sign;
6414 StelUtils::radToDecDeg(r, sign, dd);
6415 aX.append(i*step);
6416 aY1.append(distanceAu);
6417 if (firstCBId != currentPlanet && secondCBId != currentPlanet)
6418 aY2.append(dd);
6419 core->update(0.0);
6420 }
6421 core->setJD(currentJD);
6422
6423 QVector<double> x = aX.toVector(), y1 = aY1.toVector(), y2;
6424 minYld = *std::min_element(aY1.begin(), aY1.end());
6425 maxYld = *std::max_element(aY1.begin(), aY1.end());
6426
6427 if (!aY2.isEmpty()) // mistake-proofing!
6428 {
6429 y2 = aY2.toVector();
6430 minYad = *std::min_element(aY2.begin(), aY2.end());
6431 maxYad = *std::max_element(aY2.begin(), aY2.end());
6432 }
6433
6434 prepareDistanceAxesAndGraph();
6435
6436 ui->pcDistanceGraphPlot->graph(0)->setData(x, y1);
6437 ui->pcDistanceGraphPlot->graph(0)->setName("[LD]");
6438 if (!aY2.isEmpty()) // mistake-proofing!
6439 {
6440 ui->pcDistanceGraphPlot->graph(1)->setData(x, y2);
6441 ui->pcDistanceGraphPlot->graph(1)->setName("[AD]");
6442 }
6443 ui->pcDistanceGraphPlot->replot();
6444 }
6445
mouseOverDistanceGraph(QMouseEvent * event)6446 void AstroCalcDialog::mouseOverDistanceGraph(QMouseEvent* event)
6447 {
6448 const double x = ui->pcDistanceGraphPlot->xAxis->pixelToCoord(event->pos().x());
6449 const double y = ui->pcDistanceGraphPlot->yAxis->pixelToCoord(event->pos().y());
6450 const double y2 = ui->pcDistanceGraphPlot->yAxis2->pixelToCoord(event->pos().y());
6451
6452 QCPAbstractPlottable* abstractGraph = ui->pcDistanceGraphPlot->plottableAt(event->pos(), false);
6453 QCPGraph* graph = qobject_cast<QCPGraph*>(abstractGraph);
6454
6455 if (ui->pcDistanceGraphPlot->xAxis->range().contains(x) && ui->pcDistanceGraphPlot->yAxis->range().contains(y))
6456 {
6457 QString info;
6458 if (graph)
6459 {
6460 const double currentJD = core->getJD();
6461 if (graph->name()=="[LD]")
6462 info = QString("%1<br />%2: %3%4").arg(StelUtils::julianDayToISO8601String(currentJD + x).replace("T", " "), q_("Linear distance"), QString::number(y), qc_("AU", "distance, astronomical unit"));
6463
6464 if (graph->name()=="[AD]")
6465 info = QString("%1<br />%2: %3%4").arg(StelUtils::julianDayToISO8601String(currentJD + x).replace("T", " "), q_("Angular distance"), QString::number(y2), QChar(0x00B0));
6466 }
6467 ui->pcDistanceGraphPlot->setToolTip(info);
6468 }
6469
6470 ui->pcDistanceGraphPlot->update();
6471 ui->pcDistanceGraphPlot->replot();
6472 }
6473
prepareAngularDistanceAxesAndGraph()6474 void AstroCalcDialog::prepareAngularDistanceAxesAndGraph()
6475 {
6476 QString xAxisStr = q_("Days from today");
6477 QString yAxisLegend = QString("%1, %2").arg(q_("Angular distance"), QChar(0x00B0)); // decimal degrees
6478
6479 QColor axisColor(Qt::white);
6480 QPen axisPen(axisColor, 1);
6481
6482 ui->angularDistancePlot->xAxis->setLabel(xAxisStr);
6483 ui->angularDistancePlot->yAxis->setLabel(yAxisLegend);
6484
6485 ui->angularDistancePlot->xAxis->setRange(-2, 31);
6486 ui->angularDistancePlot->xAxis->setScaleType(QCPAxis::stLinear);
6487 ui->angularDistancePlot->xAxis->setLabelColor(axisColor);
6488 ui->angularDistancePlot->xAxis->setTickLabelColor(axisColor);
6489 ui->angularDistancePlot->xAxis->setBasePen(axisPen);
6490 ui->angularDistancePlot->xAxis->setTickPen(axisPen);
6491 ui->angularDistancePlot->xAxis->setSubTickPen(axisPen);
6492 ui->angularDistancePlot->xAxis->setAutoTicks(true);
6493
6494 ui->angularDistancePlot->yAxis->setRange(minYadm, maxYadm);
6495 ui->angularDistancePlot->yAxis->setScaleType(QCPAxis::stLinear);
6496 ui->angularDistancePlot->yAxis->setLabelColor(axisColor);
6497 ui->angularDistancePlot->yAxis->setTickLabelColor(axisColor);
6498 ui->angularDistancePlot->yAxis->setBasePen(axisPen);
6499 ui->angularDistancePlot->yAxis->setTickPen(axisPen);
6500 ui->angularDistancePlot->yAxis->setSubTickPen(axisPen);
6501
6502 ui->angularDistancePlot->clearGraphs();
6503 ui->angularDistancePlot->addGraph(ui->angularDistancePlot->xAxis, ui->angularDistancePlot->yAxis);
6504 ui->angularDistancePlot->setBackground(QBrush(QColor(86, 87, 90)));
6505 ui->angularDistancePlot->graph(0)->setPen(QPen(Qt::red, 1));
6506 ui->angularDistancePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
6507 ui->angularDistancePlot->graph(0)->rescaleAxes(true);
6508
6509 ui->angularDistancePlot->addGraph(ui->angularDistancePlot->xAxis, ui->angularDistancePlot->yAxis);
6510 ui->angularDistancePlot->setBackground(QBrush(QColor(86, 87, 90)));
6511 ui->angularDistancePlot->graph(1)->setPen(QPen(Qt::yellow, 1));
6512 ui->angularDistancePlot->graph(1)->setLineStyle(QCPGraph::lsLine);
6513 ui->angularDistancePlot->graph(1)->rescaleAxes(true);
6514 }
6515
drawAngularDistanceGraph()6516 void AstroCalcDialog::drawAngularDistanceGraph()
6517 {
6518 QString label = q_("Angular distance between the Moon and selected object");
6519 ui->angularDistancePlot->setToolTip(label);
6520
6521 // special case - plot the graph when tab is visible
6522 //..
6523 // we got notified about a reason to redraw the plot, but dialog was
6524 // not visible. which means we must redraw when becoming visible again!
6525 if (!dialog->isVisible() && !plotAngularDistanceGraph)
6526 {
6527 graphPlotNeedsRefresh = true;
6528 return;
6529 }
6530
6531 if (!plotAngularDistanceGraph) return;
6532
6533 // special case - the tool is not applicable on non-Earth locations
6534 if (core->getCurrentPlanet()!=solarSystem->getEarth())
6535 return;
6536
6537 QList<StelObjectP> selectedObjects = objectMgr->getSelectedObject();
6538 if (!selectedObjects.isEmpty())
6539 {
6540 PlanetP moon = solarSystem->getMoon();
6541 StelObjectP selectedObject = selectedObjects[0];
6542 if (selectedObject==moon || selectedObject->getType() == "Satellite")
6543 {
6544 ui->angularDistancePlot->graph(0)->clearData();
6545 ui->angularDistancePlot->replot();
6546 return;
6547 }
6548
6549 QList<double> aX, aY;
6550 Vec3d selectedObjectPosition, moonPosition;
6551 const double currentJD = core->getJD();
6552 double JD, distance, dd;
6553 bool sign;
6554 for (int i = -5; i <= 35; i++)
6555 {
6556 JD = currentJD + i;
6557 core->setJD(JD);
6558 moonPosition = moon->getJ2000EquatorialPos(core);
6559 selectedObjectPosition = selectedObject->getJ2000EquatorialPos(core);
6560 distance = moonPosition.angle(selectedObjectPosition);
6561 StelUtils::radToDecDeg(distance, sign, dd);
6562 aX.append(i);
6563 aY.append(dd);
6564 core->update(0.0);
6565 }
6566 core->setJD(currentJD);
6567
6568 QVector<double> x = aX.toVector(), y = aY.toVector();
6569 minYadm = *std::min_element(aY.begin(), aY.end()) - 5.0;
6570 maxYadm = *std::max_element(aY.begin(), aY.end()) + 5.0;
6571 int limit = ui->angularDistanceLimitSpinBox->value();
6572 if (minYadm > limit)
6573 minYadm = limit - 5.0;
6574 if (maxYadm < limit)
6575 maxYadm = limit + 5.0;
6576
6577 QString name = selectedObject->getNameI18n();
6578 if (name.isEmpty())
6579 {
6580 QString otype = selectedObject->getType();
6581 if (otype == "Nebula")
6582 {
6583 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignation();
6584 if (name.isEmpty())
6585 name = GETSTELMODULE(NebulaMgr)->getLatestSelectedDSODesignationWIC();
6586 }
6587 if (otype == "Star" || otype=="Pulsar")
6588 selectedObject->getID().isEmpty() ? name = q_("Unnamed star") : name = selectedObject->getID();
6589 }
6590 ui->angularDistancePlot->setToolTip(QString("%1 (%2)").arg(label, name));
6591 ui->angularDistanceTitle->setText(QString("%1 (%2)").arg(label, name));
6592
6593 prepareAngularDistanceAxesAndGraph();
6594
6595 ui->angularDistancePlot->graph(0)->setData(x, y);
6596 ui->angularDistancePlot->graph(0)->setName("[Angular distance]");
6597 ui->angularDistancePlot->replot();
6598 }
6599
6600 // clean up the data when selection is removed
6601 if (!objectMgr->getWasSelected())
6602 {
6603 ui->angularDistancePlot->graph(0)->clearData();
6604 ui->angularDistancePlot->replot();
6605 }
6606 drawAngularDistanceLimitLine();
6607 }
6608
saveAngularDistanceLimit(int limit)6609 void AstroCalcDialog::saveAngularDistanceLimit(int limit)
6610 {
6611 conf->setValue("astrocalc/angular_distance_limit", limit);
6612 drawAngularDistanceLimitLine();
6613 }
6614
drawAngularDistanceLimitLine()6615 void AstroCalcDialog::drawAngularDistanceLimitLine()
6616 {
6617 // special case - plot the graph when tab is visible
6618 if (!plotAngularDistanceGraph || !dialog->isVisible())
6619 return;
6620
6621 double limit = ui->angularDistanceLimitSpinBox->value();
6622 QVector<double> x = {-5, 35};
6623 QVector<double> y = {limit, limit};
6624 ui->angularDistancePlot->graph(1)->setData(x, y);
6625 ui->angularDistancePlot->replot();
6626 }
6627
saveTableAsCSV(const QString & fileName,QTreeWidget * tWidget,QStringList & headers)6628 void AstroCalcDialog::saveTableAsCSV(const QString &fileName, QTreeWidget* tWidget, QStringList &headers)
6629 {
6630 const int count = tWidget->topLevelItemCount();
6631 const int columns = headers.size();
6632
6633 QFile table(fileName);
6634 if (!table.open(QFile::WriteOnly | QFile::Truncate))
6635 {
6636 qWarning() << "[AstroCalc] Unable to open file" << QDir::toNativeSeparators(fileName);
6637 return;
6638 }
6639
6640 QTextStream tableData(&table);
6641 tableData.setCodec("UTF-8");
6642
6643 for (int i = 0; i < columns; i++)
6644 {
6645 QString h = headers.at(i).trimmed();
6646 tableData << ((h.contains(",")) ? QString("\"%1\"").arg(h) : h);
6647 tableData << ((i < columns - 1) ? delimiter : StelUtils::getEndLineChar());
6648 }
6649
6650 for (int i = 0; i < count; i++)
6651 {
6652 for (int j = 0; j < columns; j++)
6653 {
6654 tableData << tWidget->topLevelItem(i)->text(j);
6655 tableData << ((j < columns - 1) ? delimiter : StelUtils::getEndLineChar());
6656 }
6657 }
6658
6659 table.close();
6660 }
6661
saveTableAsBookmarks(const QString & fileName,QTreeWidget * tWidget)6662 void AstroCalcDialog::saveTableAsBookmarks(const QString &fileName, QTreeWidget* tWidget)
6663 {
6664 const int count = tWidget->topLevelItemCount();
6665
6666 QFile bookmarksFile(fileName);
6667 if (!bookmarksFile.open(QFile::WriteOnly | QFile::Truncate))
6668 {
6669 qWarning() << "[AstroCalc] Unable to open file" << QDir::toNativeSeparators(fileName);
6670 return;
6671 }
6672
6673 QVariantMap bookmarksDataList;
6674 double fov = GETSTELMODULE(StelMovementMgr)->getCurrentFov();
6675 for (int i = 0; i < count; i++)
6676 {
6677 QString uuid = QUuid::createUuid().toString();
6678 QVariantMap bm;
6679 bm.insert("name", tWidget->topLevelItem(i)->data(0, Qt::UserRole).toString());
6680 bm.insert("nameI18n", tWidget->topLevelItem(i)->data(0, Qt::DisplayRole).toString());
6681 bm.insert("fov", fov);
6682 bookmarksDataList.insert(uuid, bm);
6683 }
6684
6685 QVariantMap bmList;
6686 bmList.insert("bookmarks", bookmarksDataList);
6687
6688 //Convert the tree to JSON
6689 StelJsonParser::write(bmList, &bookmarksFile);
6690 bookmarksFile.flush();
6691 bookmarksFile.close();
6692 }
6693