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 | |
37 | namespace Ekos |
38 | { |
39 | |
40 | // 30 arcmiutes RA movement |
41 | const double Align::RAMotion = 0.5; |
42 | // Sidereal rate, degrees/s |
43 | const float Align::SIDRATE = 0.004178; |
44 | |
45 | Align::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 | |
139 | Align::~Align() |
140 | { |
141 | delete(pi); |
142 | } |
143 | |
144 | bool 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 | |
157 | bool Align::isVerbose() |
158 | { |
159 | return solverVerbose->isChecked(); |
160 | } |
161 | |
162 | void 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 | |
201 | void 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 | |
213 | void 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 | |
231 | void 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 | |
240 | void 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 | |
286 | void 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 | |
332 | void 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 | |
347 | void 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 | |
416 | void 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 | |
425 | void Align::copyCoordsToBoxes() |
426 | { |
427 | raBox->setText(ScopeRAOut->text()); |
428 | decBox->setText(ScopeDecOut->text()); |
429 | |
430 | checkLineEdits(); |
431 | } |
432 | |
433 | void Align::clearCoordBoxes() |
434 | { |
435 | raBox->clear(); |
436 | decBox->clear(); |
437 | |
438 | generateArgs(); |
439 | } |
440 | |
441 | bool 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 | |
492 | void 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 | |
505 | void 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 | |
528 | void 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 | |
551 | void 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 | |
563 | void 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 | |
590 | void 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 | |
597 | void Align::clearLog() |
598 | { |
599 | logText.clear(); |
600 | emit newLog(); |
601 | } |
602 | |
603 | void 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 | |
707 | void Align::executeMode() |
708 | { |
709 | if (gotoR->isChecked()) |
710 | executeGOTO(); |
711 | else |
712 | executePolarAlign(); |
713 | } |
714 | |
715 | |
716 | void 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 | |
729 | void Align::Sync() |
730 | { |
731 | if (currentTelescope->Sync(&alignCoord)) |
732 | appendLogText(i18n("Syncing successful." )); |
733 | else |
734 | appendLogText(i18n("Syncing failed." )); |
735 | |
736 | } |
737 | |
738 | void Align::SlewToTarget() |
739 | { |
740 | if (canSync && loadSlewMode == false) |
741 | Sync(); |
742 | |
743 | currentTelescope->Slew(&targetCoord); |
744 | |
745 | appendLogText(i18n("Slewing to target." )); |
746 | } |
747 | |
748 | void 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 | |
764 | void 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 | |
792 | void 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 | |
877 | void 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 | |
964 | void 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 | |
1072 | void 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 | |
1102 | void 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 | |
1129 | void 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 | |
1142 | void 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 | |