1/* Ekos Alignment Module
2 Copyright (C) 2013 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 This application is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8 */
9
10#include <config-kstars.h>
11#include <QProcess>
12
13#include "kstars.h"
14#include "kstarsdata.h"
15#include "align.h"
16#include "dms.h"
17#include "Options.h"
18
19#include <KFileDialog>
20#include <KMessageBox>
21
22#include "QProgressIndicator.h"
23#include "indi/driverinfo.h"
24#include "indi/indicommon.h"
25
26#include "fitsviewer/fitsviewer.h"
27#include "fitsviewer/fitstab.h"
28#include "fitsviewer/fitsview.h"
29
30#ifdef HAVE_QJSON
31#include "onlineastrometryparser.h"
32#endif
33#include "offlineastrometryparser.h"
34
35#include <basedevice.h>
36
37namespace Ekos
38{
39
40// 30 arcmiutes RA movement
41const double Align::RAMotion = 0.5;
42// Sidereal rate, degrees/s
43const float Align::SIDRATE = 0.004178;
44
45Align::Align()
46{
47 setupUi(this);
48
49 currentCCD = NULL;
50 currentTelescope = NULL;
51 useGuideHead = false;
52 canSync = false;
53 loadSlewMode = false;
54 ccd_hor_pixel = ccd_ver_pixel = focal_length = aperture = -1;
55 decDeviation = azDeviation = altDeviation = 0;
56
57 parser = NULL;
58
59 #ifdef HAVE_QJSON
60 onlineParser = NULL;
61 #endif
62 offlineParser = NULL;
63
64 connect(solveB, SIGNAL(clicked()), this, SLOT(capture()));
65 connect(stopB, SIGNAL(clicked()), this, SLOT(stopSolving()));
66 connect(measureAltB, SIGNAL(clicked()), this, SLOT(measureAltError()));
67 connect(measureAzB, SIGNAL(clicked()), this, SLOT(measureAzError()));
68 connect(polarR, SIGNAL(toggled(bool)), this, SLOT(checkPolarAlignment()));
69 connect(raBox, SIGNAL(textChanged( const QString & ) ), this, SLOT( checkLineEdits() ) );
70 connect(decBox, SIGNAL(textChanged( const QString & ) ), this, SLOT( checkLineEdits() ) );
71 connect(syncBoxesB, SIGNAL(clicked()), this, SLOT(copyCoordsToBoxes()));
72 connect(clearBoxesB, SIGNAL(clicked()), this, SLOT(clearCoordBoxes()));
73 connect(CCDCaptureCombo, SIGNAL(activated(int)), this, SLOT(checkCCD(int)));
74 connect(correctAltB, SIGNAL(clicked()), this, SLOT(correctAltError()));
75 connect(correctAzB, SIGNAL(clicked()), this, SLOT(correctAzError()));
76 connect(loadSlewB, SIGNAL(clicked()), this, SLOT(loadFITS()));
77
78 kcfg_solverXBin->setValue(Options::solverXBin());
79 kcfg_solverYBin->setValue(Options::solverYBin());
80 kcfg_solverUpdateCoords->setChecked(Options::solverUpdateCoords());
81
82 syncBoxesB->setIcon(KIcon("edit-copy"));
83 clearBoxesB->setIcon(KIcon("edit-clear"));
84
85 raBox->setDegType(false); //RA box should be HMS-style
86
87 appendLogText(i18n("Idle."));
88
89 pi = new QProgressIndicator(this);
90
91 controlLayout->addWidget(pi, 0, 2, 1, 1);
92
93 exposureSpin->setValue(Options::alignExposure());
94
95 altStage = ALT_INIT;
96 azStage = AZ_INIT;
97
98 #ifndef HAVE_QJSON
99 kcfg_onlineSolver->setChecked(false);
100 kcfg_onlineSolver->setEnabled(false);
101 #else
102 kcfg_onlineSolver->setChecked(Options::solverOnline());
103 #endif
104
105 kcfg_offlineSolver->setChecked(Options::solverOnline() == false);
106 connect(kcfg_onlineSolver, SIGNAL(toggled(bool)), SLOT(updateSolverType(bool)));
107
108
109 if (kcfg_onlineSolver->isChecked())
110 {
111 #ifdef HAVE_QJSON
112 onlineParser = new Ekos::OnlineAstrometryParser();
113 parser = onlineParser;
114 #else
115 kcfg_onlineSolver->setChecked(false);
116 kcfg_onlineSolver->setEnabled(false);
117 kcfg_offlineSolver->setChecked(true);
118 offlineParser = new OfflineAstrometryParser();
119 parser = offlineParser;
120 #endif
121 }
122 else
123 {
124 offlineParser = new OfflineAstrometryParser();
125 parser = offlineParser;
126 }
127
128 parser->setAlign(this);
129 if (parser->init() == false)
130 setEnabled(false);
131 else
132 {
133 connect(parser, SIGNAL(solverFinished(double,double,double)), this, SLOT(solverFinished(double,double,double)));
134 connect(parser, SIGNAL(solverFailed()), this, SLOT(solverFailed()));
135 }
136
137}
138
139Align::~Align()
140{
141 delete(pi);
142}
143
144bool Align::parserOK()
145{
146 bool rc = parser->init();
147
148 if (rc)
149 {
150 connect(parser, SIGNAL(solverFinished(double,double,double)), this, SLOT(solverFinished(double,double,double)));
151 connect(parser, SIGNAL(solverFailed()), this, SLOT(solverFailed()));
152 }
153
154 return rc;
155}
156
157bool Align::isVerbose()
158{
159 return solverVerbose->isChecked();
160}
161
162void Align::updateSolverType(bool useOnline)
163{
164
165 if (useOnline)
166 {
167 #ifdef HAVE_QJSON
168 if (onlineParser != NULL)
169 {
170 parser = onlineParser;
171 return;
172 }
173
174 onlineParser = new Ekos::OnlineAstrometryParser();
175 parser = onlineParser;
176 #endif
177 }
178 else
179 {
180 if (offlineParser != NULL)
181 {
182 parser = offlineParser;
183 return;
184 }
185
186 offlineParser = new Ekos::OfflineAstrometryParser();
187 parser = offlineParser;
188 }
189
190 parser->setAlign(this);
191 if (parser->init())
192 {
193 connect(parser, SIGNAL(solverFinished(double,double,double)), this, SLOT(solverFinished(double,double,double)));
194 connect(parser, SIGNAL(solverFailed()), this, SLOT(solverFailed()));
195 }
196 else
197 parser->disconnect();
198
199}
200
201void Align::checkCCD(int ccdNum)
202{
203 if (ccdNum == -1)
204 ccdNum = CCDCaptureCombo->currentIndex();
205
206 if (ccdNum <= CCDs.count())
207 currentCCD = CCDs.at(ccdNum);
208
209 syncCCDInfo();
210
211}
212
213void Align::addCCD(ISD::GDInterface *newCCD, bool isPrimaryCCD)
214{
215 CCDCaptureCombo->addItem(newCCD->getDeviceName());
216
217 CCDs.append(static_cast<ISD::CCD *>(newCCD));
218
219 if (isPrimaryCCD)
220 {
221 checkCCD(CCDs.count()-1);
222 CCDCaptureCombo->setCurrentIndex(CCDs.count()-1);
223 }
224 else
225 {
226 checkCCD(0);
227 CCDCaptureCombo->setCurrentIndex(0);
228 }
229}
230
231void Align::setTelescope(ISD::GDInterface *newTelescope)
232{
233 currentTelescope = static_cast<ISD::Telescope*> (newTelescope);
234
235 connect(currentTelescope, SIGNAL(numberUpdated(INumberVectorProperty*)), this, SLOT(updateScopeCoords(INumberVectorProperty*)));
236
237 syncTelescopeInfo();
238}
239
240void Align::syncTelescopeInfo()
241{
242 INumberVectorProperty * nvp = currentTelescope->getBaseDevice()->getNumber("TELESCOPE_INFO");
243
244 if (nvp)
245 {
246 INumber *np = IUFindNumber(nvp, "TELESCOPE_APERTURE");
247
248 if (np && np->value > 0)
249 aperture = np->value;
250 else
251 {
252 np = IUFindNumber(nvp, "GUIDER_APERTURE");
253 if (np && np->value > 0)
254 aperture = np->value;
255 }
256
257 np = IUFindNumber(nvp, "TELESCOPE_FOCAL_LENGTH");
258 if (np && np->value > 0)
259 focal_length = np->value;
260 else
261 {
262 np = IUFindNumber(nvp, "GUIDER_FOCAL_LENGTH");
263 if (np && np->value > 0)
264 focal_length = np->value;
265 }
266 }
267
268 if (focal_length == -1 || aperture == -1)
269 return;
270
271 if (ccd_hor_pixel != -1 && ccd_ver_pixel != -1 && focal_length != -1 && aperture != -1)
272 calculateFOV();
273
274 if (currentCCD && currentTelescope)
275 generateArgs();
276
277 if (syncR->isEnabled() && (canSync = currentTelescope->canSync()) == false)
278 {
279 syncR->setEnabled(false);
280 slewR->setChecked(true);
281 appendLogText(i18n("Telescope does not support syncing."));
282 }
283}
284
285
286void Align::syncCCDInfo()
287{
288 INumberVectorProperty * nvp = NULL;
289 int x,y;
290
291 if (currentCCD == NULL)
292 return;
293
294 if (useGuideHead)
295 nvp = currentCCD->getBaseDevice()->getNumber("GUIDER_INFO");
296 else
297 nvp = currentCCD->getBaseDevice()->getNumber("CCD_INFO");
298
299 if (nvp)
300 {
301 INumber *np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_X");
302 if (np && np->value >0)
303 ccd_hor_pixel = ccd_ver_pixel = np->value;
304
305 np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y");
306 if (np && np->value >0)
307 ccd_ver_pixel = np->value;
308
309 np = IUFindNumber(nvp, "CCD_PIXEL_SIZE_Y");
310 if (np && np->value >0)
311 ccd_ver_pixel = np->value;
312 }
313
314 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
315
316 targetChip->getFrame(&x,&y,&ccd_width,&ccd_height);
317 kcfg_solverXBin->setEnabled(targetChip->canBin());
318 kcfg_solverYBin->setEnabled(targetChip->canBin());
319
320 if (ccd_hor_pixel == -1 || ccd_ver_pixel == -1)
321 return;
322
323 if (ccd_hor_pixel != -1 && ccd_ver_pixel != -1 && focal_length != -1 && aperture != -1)
324 calculateFOV();
325
326 if (currentCCD && currentTelescope)
327 generateArgs();
328
329}
330
331
332void Align::calculateFOV()
333{
334 // Calculate FOV
335 fov_x = 206264.8062470963552 * ccd_width * ccd_hor_pixel / 1000.0 / focal_length;
336 fov_y = 206264.8062470963552 * ccd_height * ccd_ver_pixel / 1000.0 / focal_length;
337
338 fov_x /= 60.0;
339 fov_y /= 60.0;
340
341 FOVOut->setText(QString("%1' x %2'").arg(fov_x, 0, 'g', 3).arg(fov_y, 0, 'g', 3));
342
343 parser->verifyIndexFiles(fov_x, fov_y);
344
345}
346
347void Align::generateArgs()
348{
349 // -O overwrite
350 // -3 Expected RA
351 // -4 Expected DEC
352 // -5 Radius (deg)
353 // -L lower scale of image in arcminutes
354 // -H upper scale of image in arcmiutes
355 // -u aw set scale to be in arcminutes
356 // -W solution.wcs name of solution file
357 // apog1.jpg name of target file to analyze
358 //solve-field -O -3 06:40:51 -4 +09:49:53 -5 1 -L 40 -H 100 -u aw -W solution.wcs apod1.jpg
359
360 double ra=0,dec=0, fov_lower, fov_upper;
361 QString ra_dms, dec_dms;
362 QString fov_low,fov_high;
363 QStringList solver_args;
364
365 // let's stretch the boundaries by 5%
366 fov_lower = ((fov_x < fov_y) ? (fov_x *0.95) : (fov_y *0.95));
367 fov_upper = ((fov_x > fov_y) ? (fov_x * 1.05) : (fov_y * 1.05));
368
369 currentTelescope->getEqCoords(&ra, &dec);
370
371 fov_low = QString("%1").arg(fov_lower);
372 fov_high = QString("%1").arg(fov_upper);
373
374 getFormattedCoords(ra, dec, ra_dms, dec_dms);
375
376 solver_args << "--no-verify" << "--no-plots" << "--no-fits2fits" << "--resort"
377 << "--downsample" << "2" << "-O" << "-L" << fov_low << "-H" << fov_high << "-u" << "aw";
378
379 if (raBox->isEmpty() == false && decBox->isEmpty() == false)
380 {
381 bool raOk(false), decOk(false), radiusOk(false);
382 dms ra( raBox->createDms( false, &raOk ) ); //false means expressed in hours
383 dms dec( decBox->createDms( true, &decOk ) );
384 int radius = 30;
385 QString message;
386
387 if ( raOk && decOk )
388 {
389 //make sure values are in valid range
390 if ( ra.Hours() < 0.0 || ra.Hours() > 24.0 )
391 message = i18n( "The Right Ascension value must be between 0.0 and 24.0." );
392 if ( dec.Degrees() < -90.0 || dec.Degrees() > 90.0 )
393 message += '\n' + i18n( "The Declination value must be between -90.0 and 90.0." );
394 if ( ! message.isEmpty() )
395 {
396 KMessageBox::sorry( 0, message, i18n( "Invalid Coordinate Data" ) );
397 return;
398 }
399 }
400
401 if (radiusBox->text().isEmpty() == false)
402 radius = radiusBox->text().toInt(&radiusOk);
403
404 if (radiusOk == false)
405 {
406 KMessageBox::sorry( 0, message, i18n( "Invalid radius value" ) );
407 return;
408 }
409
410 solver_args << "-3" << QString().setNum(ra.Degrees()) << "-4" << QString().setNum(dec.Degrees()) << "-5" << QString().setNum(radius);
411 }
412
413 solverOptions->setText(solver_args.join(" "));
414}
415
416void Align::checkLineEdits()
417{
418 bool raOk(false), decOk(false);
419 raBox->createDms( false, &raOk );
420 decBox->createDms( true, &decOk );
421 if ( raOk && decOk )
422 generateArgs();
423}
424
425void Align::copyCoordsToBoxes()
426{
427 raBox->setText(ScopeRAOut->text());
428 decBox->setText(ScopeDecOut->text());
429
430 checkLineEdits();
431}
432
433void Align::clearCoordBoxes()
434{
435 raBox->clear();
436 decBox->clear();
437
438 generateArgs();
439}
440
441bool Align::capture()
442{
443 if (currentCCD == NULL)
444 return false;
445
446 if (parser->init() == false)
447 return false;
448
449 if (focal_length == -1 || aperture == -1)
450 {
451 KMessageBox::error(0, i18n("Telescope aperture and focal length are missing. Please check your driver settings and try again."));
452 return false;
453 }
454
455 if (ccd_hor_pixel == -1 || ccd_ver_pixel == -1)
456 {
457 KMessageBox::error(0, i18n("CCD pixel size is missing. Please check your driver settings and try again."));
458 return false;
459 }
460
461 double seqExpose = exposureSpin->value();
462
463 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
464
465 CCDFrameType ccdFrame = FRAME_LIGHT;
466
467 if (currentCCD->isConnected() == false)
468 {
469 appendLogText(i18n("Error: Lost connection to CCD."));
470 return false;
471 }
472
473 connect(currentCCD, SIGNAL(BLOBUpdated(IBLOB*)), this, SLOT(newFITS(IBLOB*)));
474
475 targetChip->setCaptureMode(FITS_WCSM);
476 targetChip->setBinning(kcfg_solverXBin->value(), kcfg_solverYBin->value());
477 targetChip->setFrameType(ccdFrame);
478
479 targetChip->capture(seqExpose);
480
481 Options::setAlignExposure(seqExpose);
482
483 solveB->setEnabled(false);
484 stopB->setEnabled(true);
485 pi->startAnimation();
486
487 appendLogText(i18n("Capturing image..."));
488
489 return true;
490}
491
492void Align::newFITS(IBLOB *bp)
493{
494 // Ignore guide head if there is any.
495 if (!strcmp(bp->name, "CCD2"))
496 return;
497
498 currentCCD->disconnect(this);
499
500 appendLogText(i18n("Image received."));
501
502 startSovling(QString(bp->label));
503}
504
505void Align::startSovling(const QString &filename, bool isGenerated)
506{
507 QStringList solverArgs;
508 double ra,dec;
509
510 Options::setSolverXBin(kcfg_solverXBin->value());
511 Options::setSolverYBin(kcfg_solverYBin->value());
512 Options::setSolverUpdateCoords(kcfg_solverUpdateCoords->isChecked());
513 Options::setSolverOnline(kcfg_onlineSolver->isChecked());
514
515 currentTelescope->getEqCoords(&ra, &dec);
516
517 targetCoord.setRA(ra);
518 targetCoord.setDec(dec);
519
520 solverTimer.start();
521
522 solverArgs = solverOptions->text().split(" ");
523
524 parser->startSovler(filename, solverArgs, isGenerated);
525
526}
527
528void Align::solverFinished(double orientation, double ra, double dec)
529{
530 pi->stopAnimation();
531 stopB->setEnabled(false);
532 solveB->setEnabled(true);
533
534 alignCoord.setRA0(ra/15.0);
535 alignCoord.setDec0(dec);
536 RotOut->setText(QString("%1").arg(orientation, 0, 'g', 5));
537
538 // Convert to JNow
539 alignCoord.apparentCoord((long double) J2000, KStars::Instance()->data()->ut().djd());
540
541 QString ra_dms, dec_dms;
542 getFormattedCoords(alignCoord.ra().Hours(), alignCoord.dec().Degrees(), ra_dms, dec_dms);
543
544 SolverRAOut->setText(ra_dms);
545 SolverDecOut->setText(dec_dms);
546
547 executeMode();
548
549}
550
551void Align::solverFailed()
552{
553 pi->stopAnimation();
554 stopB->setEnabled(false);
555 solveB->setEnabled(true);
556
557 azStage = AZ_INIT;
558 altStage = ALT_INIT;
559
560
561}
562
563void Align::stopSolving()
564{
565 parser->stopSolver();
566 pi->stopAnimation();
567 stopB->setEnabled(false);
568 solveB->setEnabled(true);
569
570 azStage = AZ_INIT;
571 altStage = ALT_INIT;
572
573 loadSlewMode = false;
574
575 ISD::CCDChip *targetChip = currentCCD->getChip(useGuideHead ? ISD::CCDChip::GUIDE_CCD : ISD::CCDChip::PRIMARY_CCD);
576
577 // If capture is still in progress, let's stop that.
578 if (targetChip->isCapturing())
579 {
580 targetChip->abortExposure();
581 appendLogText(i18n("Capture aborted."));
582 }
583 else
584 {
585 int elapsed = (int) round(solverTimer.elapsed()/1000.0);
586 appendLogText(i18np("Solver aborted after %1 second.", "Solver aborted after %1 seconds", elapsed));
587 }
588}
589
590void Align::appendLogText(const QString &text)
591{
592 logText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss"), text));
593
594 emit newLog();
595}
596
597void Align::clearLog()
598{
599 logText.clear();
600 emit newLog();
601}
602
603void Align::updateScopeCoords(INumberVectorProperty *coord)
604{
605 QString ra_dms, dec_dms;
606 static bool slew_dirty=false;
607
608 if (!strcmp(coord->name, "EQUATORIAL_EOD_COORD"))
609 {
610 getFormattedCoords(coord->np[0].value, coord->np[1].value, ra_dms, dec_dms);
611
612 telescopeCoord.setRA(coord->np[0].value);
613 telescopeCoord.setDec(coord->np[1].value);
614
615 ScopeRAOut->setText(ra_dms);
616 ScopeDecOut->setText(dec_dms);
617
618 if (kcfg_solverUpdateCoords->isChecked())
619 {
620 if (currentTelescope->isSlewing() && slew_dirty == false)
621 slew_dirty = true;
622 else if (currentTelescope->isSlewing() == false && slew_dirty)
623 {
624 slew_dirty = false;
625 copyCoordsToBoxes();
626
627 if (loadSlewMode)
628 {
629 loadSlewMode = false;
630 capture();
631 return;
632 }
633 }
634 }
635
636 switch (azStage)
637 {
638 case AZ_SYNCING:
639 if (currentTelescope->isSlewing())
640 azStage=AZ_SLEWING;
641 break;
642
643 case AZ_SLEWING:
644 if (currentTelescope->isSlewing() == false)
645 {
646 azStage = AZ_SECOND_TARGET;
647 measureAzError();
648 }
649 break;
650
651 case AZ_CORRECTING:
652 if (currentTelescope->isSlewing() == false)
653 {
654 if(decDeviation > 0){
655 appendLogText(i18n("Slew complete. Please adjust your mount's' azimuth knob eastward until the target is in the center of the view."));
656 }
657 else{
658 appendLogText(i18n("Slew complete. Please adjust your mount's' azimuth knob westward until the target is in the center of the view."));
659 }
660 azStage = AZ_INIT;
661 }
662 break;
663
664 default:
665 break;
666 }
667
668 switch (altStage)
669 {
670 case ALT_SYNCING:
671 if (currentTelescope->isSlewing())
672 altStage = ALT_SLEWING;
673 break;
674
675 case ALT_SLEWING:
676 if (currentTelescope->isSlewing() == false)
677 {
678 altStage = ALT_SECOND_TARGET;
679 measureAltError();
680 }
681 break;
682
683 case ALT_CORRECTING:
684 if (currentTelescope->isSlewing() == false)
685 {
686 if(decDeviation > 0){
687 appendLogText(i18n("Slew complete. Please lower the altitude knob on your mount until the target is in the center of the view."));
688 }
689 else{
690 appendLogText(i18n("Slew complete. Please raise the altitude knob on your mount until the target is in the center of the view."));
691 }
692 altStage = ALT_INIT;
693 }
694 break;
695
696
697 default:
698 break;
699 }
700 }
701
702 if (!strcmp(coord->name, "TELESCOPE_INFO"))
703 syncTelescopeInfo();
704
705}
706
707void Align::executeMode()
708{
709 if (gotoR->isChecked())
710 executeGOTO();
711 else
712 executePolarAlign();
713}
714
715
716void Align::executeGOTO()
717{
718 if (loadSlewMode)
719 {
720 targetCoord = alignCoord;
721 SlewToTarget();
722 }
723 else if (syncR->isChecked())
724 Sync();
725 else if (slewR->isChecked())
726 SlewToTarget();
727}
728
729void Align::Sync()
730{
731 if (currentTelescope->Sync(&alignCoord))
732 appendLogText(i18n("Syncing successful."));
733 else
734 appendLogText(i18n("Syncing failed."));
735
736}
737
738void Align::SlewToTarget()
739{
740 if (canSync && loadSlewMode == false)
741 Sync();
742
743 currentTelescope->Slew(&targetCoord);
744
745 appendLogText(i18n("Slewing to target."));
746}
747
748void Align::checkPolarAlignment()
749{
750 if (polarR->isChecked())
751 {
752 measureAltB->setEnabled(true);
753 measureAzB->setEnabled(true);
754 gotoBox->setEnabled(false);
755 }
756 else
757 {
758 measureAltB->setEnabled(false);
759 measureAzB->setEnabled(false);
760 gotoBox->setEnabled(true);
761 }
762}
763
764void Align::executePolarAlign()
765{
766 appendLogText(i18n("Processing solution for polar alignment..."));
767
768 switch (azStage)
769 {
770 case AZ_FIRST_TARGET:
771 case AZ_FINISHED:
772 measureAzError();
773 break;
774
775 default:
776 break;
777 }
778
779 switch (altStage)
780 {
781 case ALT_FIRST_TARGET:
782 case ALT_FINISHED:
783 measureAltError();
784 break;
785
786 default:
787 break;
788 }
789}
790
791
792void Align::measureAzError()
793{
794 static double initRA=0, initDEC=0, finalRA=0, finalDEC=0;
795
796 switch (azStage)
797 {
798 case AZ_INIT:
799
800 // Display message box confirming user point scope near meridian and south
801
802 if (KMessageBox::warningContinueCancel( 0, hemisphereCombo->currentIndex() == 0
803 ? i18n("Point the telescope at the southern meridian. Press continue when ready.")
804 : i18n("Point the telescope at the northern meridian. Press continue when ready.")
805 , i18n("Polar Alignment Measurement"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
806 "ekos_measure_az_error")!=KMessageBox::Continue)
807 return;
808
809 appendLogText(i18n("Solving first frame near the meridian."));
810 azStage = AZ_FIRST_TARGET;
811 polarR->setChecked(true);
812 solveB->click();
813 break;
814
815 case AZ_FIRST_TARGET:
816 // start solving there, find RA/DEC
817 initRA = alignCoord.ra().Degrees();
818 initDEC = alignCoord.dec().Degrees();
819
820 // Now move 30 arcminutes in RA
821 if (canSync)
822 {
823 azStage = AZ_SYNCING;
824 currentTelescope->Sync(initRA/15.0, initDEC);
825 currentTelescope->Slew((initRA - RAMotion)/15.0, initDEC);
826 }
827 // If telescope doesn't sync, we slew relative to its current coordinates
828 else
829 {
830 azStage = AZ_SLEWING;
831 currentTelescope->Slew(telescopeCoord.ra().Hours() - RAMotion/15.0, telescopeCoord.dec().Degrees());
832 }
833
834 appendLogText(i18n("Slewing 30 arcminutes in RA..."));
835 break;
836
837 case AZ_SECOND_TARGET:
838 // We reached second target now
839 // Let now solver for RA/DEC
840 appendLogText(i18n("Solving second frame near the meridian."));
841 azStage = AZ_FINISHED;
842 polarR->setChecked(true);
843 solveB->click();
844 break;
845
846
847 case AZ_FINISHED:
848 // Measure deviation in DEC
849 // Call function to report error
850 // set stage to AZ_FIRST_TARGET again
851 appendLogText(i18n("Calculating azimuth alignment error..."));
852 finalRA = alignCoord.ra().Degrees();
853 finalDEC = alignCoord.dec().Degrees();
854
855 // Slew back to original position
856 if (canSync)
857 currentTelescope->Slew(initRA/15.0, initDEC);
858 else
859 {
860 currentTelescope->Slew(telescopeCoord.ra().Hours() + RAMotion/15.0, telescopeCoord.dec().Degrees());
861 }
862
863 appendLogText(i18n("Slewing back to original position..."));
864
865 calculatePolarError(initRA, initDEC, finalRA, finalDEC);
866
867 azStage = AZ_INIT;
868 break;
869
870 default:
871 break;
872
873 }
874
875}
876
877void Align::measureAltError()
878{
879 static double initRA=0, initDEC=0, finalRA=0, finalDEC=0;
880
881 switch (altStage)
882 {
883 case ALT_INIT:
884
885 // Display message box confirming user point scope near meridian and south
886
887 if (KMessageBox::warningContinueCancel( 0, altDirCombo->currentIndex() == 0
888 ? i18n("Point the telescope to the east with a minimum altitude of 20 degrees. Press continue when ready.")
889 : i18n("Point the telescope to the west with a minimum altitude of 20 degrees. Press continue when ready.")
890 , i18n("Polar Alignment Measurement"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
891 "ekos_measure_alt_error")!=KMessageBox::Continue)
892 return;
893
894 appendLogText(i18n("Solving first frame."));
895 altStage = ALT_FIRST_TARGET;
896 polarR->setChecked(true);
897 solveB->click();
898 break;
899
900 case ALT_FIRST_TARGET:
901 // start solving there, find RA/DEC
902 initRA = alignCoord.ra().Degrees();
903 initDEC = alignCoord.dec().Degrees();
904
905 // Now move 30 arcminutes in RA
906 if (canSync)
907 {
908 altStage = ALT_SYNCING;
909 currentTelescope->Sync(initRA/15.0, initDEC);
910 currentTelescope->Slew((initRA - RAMotion)/15.0, initDEC);
911
912 }
913 // If telescope doesn't sync, we slew relative to its current coordinates
914 else
915 {
916 altStage = ALT_SLEWING;
917 currentTelescope->Slew(telescopeCoord.ra().Hours() - RAMotion/15.0, telescopeCoord.dec().Degrees());
918 }
919
920
921 appendLogText(i18n("Slewing 30 arcminutes in RA..."));
922 break;
923
924 case ALT_SECOND_TARGET:
925 // We reached second target now
926 // Let now solver for RA/DEC
927 appendLogText(i18n("Solving second frame."));
928 altStage = ALT_FINISHED;
929 polarR->setChecked(true);
930 solveB->click();
931 break;
932
933
934 case ALT_FINISHED:
935 // Measure deviation in DEC
936 // Call function to report error
937 appendLogText(i18n("Calculating altitude alignment error..."));
938 finalRA = alignCoord.ra().Degrees();
939 finalDEC = alignCoord.dec().Degrees();
940
941 // Slew back to original position
942 if (canSync)
943 currentTelescope->Slew(initRA/15.0, initDEC);
944 // If telescope doesn't sync, we slew relative to its current coordinates
945 else
946 {
947 currentTelescope->Slew(telescopeCoord.ra().Hours() + RAMotion/15.0, telescopeCoord.dec().Degrees());
948 }
949
950 appendLogText(i18n("Slewing back to original position..."));
951
952 calculatePolarError(initRA, initDEC, finalRA, finalDEC);
953
954 altStage = ALT_INIT;
955 break;
956
957 default:
958 break;
959
960 }
961
962}
963
964void Align::calculatePolarError(double initRA, double initDEC, double finalRA, double finalDEC)
965{
966 double raMotion = finalRA - initRA;
967 decDeviation = finalDEC - initDEC;
968
969 // How much time passed siderrally form initRA to finalRA?
970 double RATime = fabs(raMotion / SIDRATE) / 60.0;
971
972 qDebug() << "initRA " << initRA << " initDEC " << initDEC << " finalRA " << finalRA << " finalDEC " << finalDEC << endl;
973 qDebug() << "decDeviation " << decDeviation*3600 << " arcsec " << " RATime " << RATime << endl;
974
975 // Equation by Frank Berret (Measuring Polar Axis Alignment Error, page 4)
976 // In degrees
977 double deviation = (3.81 * (decDeviation * 3600) ) / ( RATime * cos(initDEC * dms::DegToRad)) / 60.0;
978
979 KLocalizedString deviationDirection;
980
981 switch (hemisphereCombo->currentIndex())
982 {
983 // Northern hemisphere
984 case 0:
985 if (azStage == AZ_FINISHED)
986 {
987 if (decDeviation > 0)
988 deviationDirection = ki18n("%1° too far west");
989 else
990 deviationDirection = ki18n("%1° too far east");
991 }
992 else if (altStage == ALT_FINISHED)
993 {
994 switch (altDirCombo->currentIndex())
995 {
996 // East
997 case 0:
998 if (decDeviation > 0)
999 deviationDirection = ki18n("%1° too far high");
1000 else
1001 deviationDirection = ki18n("%1° too far low");
1002 break;
1003
1004 // West
1005 case 1:
1006 if (decDeviation > 0)
1007 deviationDirection = ki18n("%1° too far low");
1008 else
1009 deviationDirection = ki18n("%1° too far high");
1010 break;
1011
1012 default:
1013 break;
1014 }
1015 }
1016 break;
1017
1018 // Southern hemisphere
1019 case 1:
1020 if (azStage == AZ_FINISHED)
1021 {
1022 if (decDeviation > 0)
1023 deviationDirection = ki18n("%1° too far east");
1024 else
1025 deviationDirection = ki18n("%1° too far west");
1026 }
1027 else if (altStage == ALT_FINISHED)
1028 {
1029 switch (altDirCombo->currentIndex())
1030 {
1031 // East
1032 case 0:
1033 if (decDeviation > 0)
1034 deviationDirection = ki18n("%1° too far low");
1035 else
1036 deviationDirection = ki18n("%1° too far high");
1037 break;
1038
1039 // West
1040 case 1:
1041 if (decDeviation > 0)
1042 deviationDirection = ki18n("%1° too far high");
1043 else
1044 deviationDirection = ki18n("%1° too far low");
1045 break;
1046
1047 default:
1048 break;
1049 }
1050 }
1051 break;
1052
1053 default:
1054 break;
1055
1056 }
1057
1058 if (azStage == AZ_FINISHED)
1059 {
1060 azError->setText(deviationDirection.subs(QString("%1").arg(fabs(deviation), 0, 'g', 3)).toString());
1061 azDeviation = deviation * (decDeviation > 0 ? 1 : -1);
1062 correctAzB->setEnabled(true);
1063 }
1064 if (altStage == ALT_FINISHED)
1065 {
1066 altError->setText(deviationDirection.subs(QString("%1").arg(fabs(deviation), 0, 'g', 3)).toString());
1067 altDeviation = deviation * (decDeviation > 0 ? 1 : -1);
1068 correctAltB->setEnabled(true);
1069 }
1070}
1071
1072void Align::correctAltError()
1073{
1074 double newRA, newDEC, currentAlt, currentAz;
1075
1076 SkyPoint currentCoord (telescopeCoord);
1077 dms targetLat;
1078
1079 targetLat.setD(KStars::Instance()->data()->geo()->lat()->Degrees() + altDeviation);
1080
1081 currentCoord.EquatorialToHorizontal(KStars::Instance()->data()->lst(), &targetLat );
1082
1083 currentAlt = currentCoord.alt().Degrees();
1084 currentAz = currentCoord.az().Degrees();
1085
1086 currentCoord.setAlt(currentAlt);
1087 currentCoord.setAz(currentAz);
1088
1089 currentCoord.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1090
1091 newRA = currentCoord.ra().Hours();
1092 newDEC = currentCoord.dec().Degrees();
1093
1094 altStage = ALT_CORRECTING;
1095
1096 currentTelescope->Slew(newRA, newDEC);
1097
1098 appendLogText(i18n("Slewing to calibration position, please wait until telescope is finished slewing."));
1099
1100}
1101
1102void Align::correctAzError()
1103{
1104 double newRA, newDEC, currentAlt, currentAz;
1105
1106 SkyPoint currentCoord (telescopeCoord);
1107
1108 currentCoord.EquatorialToHorizontal(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1109
1110 currentAlt = currentCoord.alt().Degrees();
1111 currentAz = currentCoord.az().Degrees() + azDeviation;
1112
1113 currentCoord.setAlt(currentAlt);
1114 currentCoord.setAz(currentAz);
1115
1116 currentCoord.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1117
1118 newRA = currentCoord.ra().Hours();
1119 newDEC = currentCoord.dec().Degrees();
1120
1121 azStage = AZ_CORRECTING;
1122
1123 currentTelescope->Slew(newRA, newDEC);
1124
1125 appendLogText(i18n("Slewing to calibration position, please wait until telescope is finished slewing."));
1126
1127}
1128
1129void Align::getFormattedCoords(double ra, double dec, QString &ra_str, QString &dec_str)
1130{
1131 dms ra_s,dec_s;
1132 ra_s.setH(ra);
1133 dec_s.setD(dec);
1134
1135 ra_str = QString("%1:%2:%3").arg(ra_s.hour(), 2, 10, QChar('0')).arg(ra_s.minute(), 2, 10, QChar('0')).arg(ra_s.second(), 2, 10, QChar('0'));
1136 if (dec_s.Degrees() < 0)
1137 dec_str = QString("-%1:%2:%3").arg(abs(dec_s.degree()), 2, 10, QChar('0')).arg(abs(dec_s.arcmin()), 2, 10, QChar('0')).arg(dec_s.arcsec(), 2, 10, QChar('0'));
1138 else
1139 dec_str = QString("%1:%2:%3").arg(dec_s.degree(), 2, 10, QChar('0')).arg(dec_s.arcmin(), 2, 10, QChar('0')).arg(dec_s.arcsec(), 2, 10, QChar('0'));
1140}
1141
1142void Align::loadFITS()
1143{
1144 KUrl fileURL = KFileDialog::getOpenUrl( KUrl(), "*.fits *.fit *.jpg *.jpeg");
1145 if (fileURL.isEmpty())
1146 return;
1147
1148 loadSlewMode = true;
1149
1150 slewR->setChecked(true);
1151
1152 solveB->setEnabled(false);
1153 stopB->setEnabled(true);
1154 pi->startAnimation();
1155
1156 startSovling(fileURL.path(), false);
1157}
1158
1159}
1160
1161#include "align.moc"
1162