Kstars

polaralign.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Hy Murveit <hy@murveit.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "polaralign.h"
8#include "poleaxis.h"
9#include "rotations.h"
10
11#include <cmath>
12
13#include "fitsviewer/fitsdata.h"
14#include "kstarsdata.h"
15#include "skypoint.h"
16#include <ekos_align_debug.h>
17
18/******************************************************************
19PolarAlign is a class that supports polar alignment by determining the
20mount's axis of rotation when given 3 solved images taken with RA mount
21rotations between the images.
22
23addPoint(image) is called by the polar alignment UI after it takes and
24solves each of its three images. The solutions are store in SkyPoints (see below)
25and are processed so that the sky positions correspond to "what's in the sky
26now" and "at this geographic localtion".
27
28Addpoint() samples the location of a particular pixel in its image.
29When the 3 points are sampled, they should not be taken
30from the center of the image, as HA rotations may not move that point
31if the telescope and mount are well aligned. Thus, the points are sampled
32from the edge of the image.
33
34After all 3 images are sampled, findAxis() is called, which solves for the mount's
35axis of rotation. It then transforms poleAxis' result into azimuth and altitude
36offsets from the pole.
37
38After the mount's current RA axis is determined, the user then attempts to correct/improve
39it to match the Earth's real polar axes. Ekos has two techniques to do that. In both
40Ekos takes a series of "refresh images". The user looks at the images and their
41associated analyses and adjusts the mount's altitude and azimuth knobs.
42
43In the first scheme, the user identifies a refrence star on the image. Ekos draws a triangle
44over the image, and user attempts to "move the star" along two sides of that triangle.
45
46In the 2nd scheme, the system plate-solves the refresh images, telling the user which direction
47and how much to adjust the knobs.
48
49findCorrectedPixel() supports the "move the star" refresh scheme.
50It is given an x,y position on an image and the offsets
51generated by findAxis(). It computes a "corrected position" for that input
52x,y point such that if a user adjusted the GEM mount's altitude and azimuth
53knobs to move a star centered in the image's original x,y position to the corrected
54position in the image, the mount's axis of rotation should then coincide with the pole.
55
56processRefreshCoords() supports the plate-solving refresh scheme.
57It is given the center coordinates of a refresh image. It remembers the originally
58calculated mount axis, and the position of the 3rd measurement image. It computes how
59much the user has already adjusted the azimuth and altitude knobs from the difference
60in pointing between the new refresh image's center coordinates and that of the 3rd measurement
61image. It infers what the mounts new RA axis must be (based on that adjustment) and returns
62the new polar alignment error.
63******************************************************************/
64
65using Rotations::V3;
66
67PolarAlign::PolarAlign(const GeoLocation *geo)
68{
69 if (geo == nullptr && KStarsData::Instance() != nullptr)
70 geoLocation = KStarsData::Instance()->geo();
71 else
72 geoLocation = geo;
73}
74
75bool PolarAlign::northernHemisphere() const
76{
77 if ((geoLocation == nullptr) || (geoLocation->lat() == nullptr))
78 return true;
79 return geoLocation->lat()->Degrees() > 0;
80}
81
82void PolarAlign::reset()
83{
84 points.clear();
85 times.clear();
86}
87
88// Gets the pixel's j2000 RA&DEC coordinates, converts to JNow, adjust to
89// the local time, and sets up the azimuth and altitude coordinates.
90bool PolarAlign::prepareAzAlt(const QSharedPointer<FITSData> &image, const QPointF &pixel, SkyPoint *point) const
91{
92 // WCS must be set up for this image.
93 SkyPoint coords;
94 if (image && image->pixelToWCS(pixel, coords))
95 {
96 coords.apparentCoord(static_cast<long double>(J2000), image->getDateTime().djd());
97 *point = SkyPoint::timeTransformed(&coords, image->getDateTime(), geoLocation, 0);
98 return true;
99 }
100 return false;
101}
102
103bool PolarAlign::addPoint(const QSharedPointer<FITSData> &image)
104{
105 SkyPoint coords;
106 auto time = image->getDateTime();
107 // Use the HA and DEC from the center of the image.
108 if (!prepareAzAlt(image, QPointF(image->width() / 2, image->height() / 2), &coords))
109 return false;
110
111 QString debugString = QString("PAA: addPoint ra0 %1 dec0 %2 ra %3 dec %4 az %5 alt %6")
112 .arg(coords.ra0().Degrees()).arg(coords.dec0().Degrees())
113 .arg(coords.ra().Degrees()).arg(coords.dec().Degrees())
114 .arg(coords.az().Degrees()).arg(coords.alt().Degrees());
115 qCInfo(KSTARS_EKOS_ALIGN) << debugString;
116 if (points.size() > 2)
117 return false;
118 points.push_back(coords);
119 times.push_back(time);
120
121 return true;
122}
123
124namespace
125{
126
127// Returns the distance, in absolute-value degrees, of taking point "from",
128// rotating it around the Y axis by yAngle, then rotating around the Z axis
129// by zAngle and comparing that with "goal".
130double getResidual(const V3 &from, double yAngle, double zAngle, const V3 &goal)
131{
132 V3 point1 = Rotations::rotateAroundY(from, yAngle);
133 V3 point2 = Rotations::rotateAroundZ(point1, zAngle);
134 return fabs(getAngle(point2, goal));
135}
136
137// Finds the best rotations to change from pointing to 'from' to pointing to 'goal'.
138// It tries 'all' possible pairs of x and y rotations (sampled by increment).
139// Note that you can't simply find the best Z rotation, and the go from there to find the best Y.
140// The space is non-linear, and that would often lead to poor solutions.
141double getBestRotation(const V3 &from, const V3 &goal,
142 double zStart, double yStart,
143 double *bestAngleZ, double *bestAngleY,
144 double range, double increment)
145{
146
147 *bestAngleZ = 0;
148 *bestAngleY = 0;
149 double minDist = 1e8;
150 range = fabs(range);
151 for (double thetaY = yStart - range; thetaY <= yStart + range; thetaY += increment)
152 {
153 for (double thetaZ = zStart - range; thetaZ <= zStart + range; thetaZ += increment)
154 {
155 double dist = getResidual(from, thetaY, thetaZ, goal);
156 if (dist < minDist)
157 {
158 minDist = dist;
159 *bestAngleY = thetaY;
160 *bestAngleZ = thetaZ;
161 }
162 }
163 }
164 return minDist;
165}
166
167// Computes the rotations in Y (altitude) and Z (azimuth) that brings 'from' closest to 'goal'.
168// Returns the residual (error angle between where these rotations lead and "goal".
169double getRotationAngles(const V3 &from, const V3 &goal, double *zAngle, double *yAngle)
170{
171 // All in degrees.
172 constexpr double pass1Resolution = 1.0 / 60.0;
173 constexpr double pass2Resolution = 5 / 3600.0;
174 constexpr double pass2Range = 4.0 / 60.0;
175
176 // Compute the rotation using a great circle. This somewhat constrains our search below.
177 const double rotationAngle = getAngle(from, goal); // degrees
178 const double pass1Range = std::max(1.0, std::min(10.0, 2.5 * fabs(rotationAngle)));
179
180 // Grid search across all y,z angle possibilities, sampling by 2 arc-minutes.
181 const double pass1Residual = getBestRotation(from, goal, 0, 0, zAngle, yAngle, pass1Range, pass1Resolution);
182 Q_UNUSED(pass1Residual);
183
184 // Refine the search around the best solution so far
185 return getBestRotation(from, goal, *zAngle, *yAngle, zAngle, yAngle, pass2Range, pass2Resolution);
186}
187
188// Computes the new RA axis resulting from when point "before" is rotated to "after". This corresponds to the
189// change in RA axis that happens when the user adjusts the mount's altitude and azimuth knobs.
190// Before is the telescope's view before the knobs are changed (which is the polar-align's point3)
191// and After is the telescope's view after the mount is rotated (which is the plate solve of some refresh point).
192V3 getNewAxis(const V3 &before, const V3 &after, const V3 &originalAxis)
193{
194 V3 rotationAxis = Rotations::getAxis(before, after);
195 double rotationDegrees = Rotations::getAngle(before, after);
196 // Rotate the original RA axis position by the above adjustments.
197 return Rotations::rotateAroundAxis(originalAxis, rotationAxis, rotationDegrees);
198}
199
200} // namespace
201
202// Compute the polar-alignment azimuth and altitude error by comparing the new image's coordinates
203// with the coordinates from the 3rd measurement image. Use the difference to infer a rotation angle,
204// and rotate the originally computed polar-alignment axis by that angle to find the new axis
205// around which RA now rotates.
206bool PolarAlign::processRefreshCoords(const SkyPoint &coords, const KStarsDateTime &time,
207 double *azError, double *altError,
208 double *azAdjustment, double *altAdjustment) const
209{
210 // Get the az and alt from this new measurement (coords), and from that derive its x,y,z coordinates.
211 auto c = coords; // apparentCoord modifies its input. Use the temp variable c to keep coords const.
212 c.apparentCoord(static_cast<long double>(J2000), time.djd());
213 SkyPoint point = SkyPoint::timeTransformed(&c, time, geoLocation, 0);
214 const double az = point.az().Degrees(), alt = point.alt().Degrees();
215 const V3 newPoint = Rotations::azAlt2xyz(QPointF(az, alt));
216
217 // Get the x,y,z coordinates of the original position (from the 3rd polar-align image).
218 // We can't simply use the az/alt already computed for point3 because the mount is tracking,
219 // and thus, even if the user made no adjustments, but simply took an image a little while
220 // later at the same RA/DEC coordinates, the Az/Alt would have changed and we'd believe there
221 // was a user rotation due to changes in the Az/Alt knobs. Instead we must convert point3's az/alt
222 // values to what they would be if that image had been taken now, still pointing at point3's ra/dec.
223 // The key is to rotate the original point around the original RA axis by the rotation given by the time
224 // difference multiplied by the sidereal rate.
225
226 // Figure out what the az/alt would be if the user hadn't modified the knobs.
227 // That is, just rotate the 3rd measurement point around the mount's original RA axis.
228 // Time since third point in seconds
229 const double p3secs = times[2].secsTo(time);
230 // Angle corresponding to that interval assuming the sidereal rate.
231 const double p3Angle = (-15.041067 * p3secs) / 3600.0; // degrees
232
233 // Get the xyz coordinates of the original 3rd point.
234 const V3 p3OrigPoint = Rotations::azAlt2xyz(QPointF(points[2].az().Degrees(), points[2].alt().Degrees()));
235 // Get the unit vector corresponding the original RA axis
236 const V3 origAxisPt = Rotations::azAlt2xyz(QPointF(azimuthCenter, altitudeCenter));
237 // Rotate the original 3rd point around that axis, simulating the mount's tracking movements.
238 const V3 point3 = Rotations::rotateAroundAxis(p3OrigPoint, origAxisPt, p3Angle);
239
240 // Find the adjustment the user must have made by examining the change from point3 to newPoint
241 // (i.e. the rotation caused by the user adjusting the azimuth and altitude knobs).
242 // We assume that this was a rotation around a level mount's y axis and z axis.
243 const V3 origAxisPoint = Rotations::azAlt2xyz(QPointF(azimuthCenter, altitudeCenter));
244 const V3 newAxisPoint = getNewAxis(point3, newPoint, origAxisPoint);
245 // Convert the rotated axis point back to an az/alt coordinate, representing the new RA axis.
246 const QPointF newAxisAzAlt = Rotations::xyz2azAlt(newAxisPoint);
247 const double newAxisAz = newAxisAzAlt.x();
248 const double newAxisAlt = newAxisAzAlt.y();
249 double azAdjustmentKeep = newAxisAz - azimuthCenter;
250 double altAdjustmentKeep = newAxisAlt - altitudeCenter;
251
252 qCInfo(KSTARS_EKOS_ALIGN) << QString("PAA refresh: Estimated current adjustment: Az %1' Alt %2'")
253 .arg(azAdjustmentKeep * 60, 0, 'f', 1).arg(altAdjustmentKeep * 60, 0, 'f', 1);
254
255 // Return the estimated adjustments (used by testing).
256 if (altAdjustment != nullptr) *altAdjustment = altAdjustmentKeep;
257 if (azAdjustment != nullptr) *azAdjustment = azAdjustmentKeep;
258
259 // Compute the polar alignment error for the new RA axis.
260 const double latitudeDegrees = geoLocation->lat()->Degrees();
261 *altError = northernHemisphere() ? newAxisAlt - latitudeDegrees : newAxisAlt + latitudeDegrees;
262 *azError = northernHemisphere() ? newAxisAz : newAxisAz + 180.0;
263 while (*azError > 180.0)
264 *azError -= 360;
265
266 QString infoString =
267 QString("PAA refresh: ra0 %1 dec0 %2 Az/Alt: %3 %4 AXIS: %5 %6 --> %7 %8 ADJ: %9' %10' ERR: %11' %12'")
268 .arg(coords.ra0().Degrees(), 0, 'f', 3).arg(coords.dec0().Degrees(), 0, 'f', 3)
269 .arg(az, 0, 'f', 3).arg(alt, 0, 'f', 3)
270 .arg(azimuthCenter, 0, 'f', 3).arg(altitudeCenter, 0, 'f', 3)
271 .arg(newAxisAz, 0, 'f', 3).arg(newAxisAlt, 0, 'f', 3)
272 .arg(azAdjustmentKeep * 60, 0, 'f', 1).arg(altAdjustmentKeep * 60, 0, 'f', 1)
273 .arg(*azError * 60, 0, 'f', 1).arg(*altError * 60, 0, 'f', 1);
274 qCInfo(KSTARS_EKOS_ALIGN) << infoString;
275
276 return true;
277}
278
279// Given the telescope's current RA axis, and the its current pointing position,
280// compute the coordinates where it should point such that its RA axis will be at the pole.
281bool PolarAlign::refreshSolution(SkyPoint *solution, SkyPoint *altOnlySolution) const
282{
283 if (points.size() != 3)
284 return false;
285
286 double azError, altError;
287 calculateAzAltError(&azError, &altError);
288
289 // The Y rotation to correct polar alignment is -altitude error, and the Z correction is -azimuth error.
290 // Rotate the 3rd-image center coordinate by the above angles.
291 // This is the position the telescope needs to point to (if it is taken there
292 // by adjusting alt and az knobs) such that the new RA rotation axis is aligned with the pole.
293 const V3 point3 = Rotations::azAlt2xyz(QPointF(points[2].az().Degrees(), points[2].alt().Degrees()));
294 const V3 altSolutionPoint = Rotations::rotateAroundY(point3, altError);
295 const V3 solutionPoint = Rotations::rotateAroundZ(altSolutionPoint, azError);
296
297 // Convert the solution xyz points back to az/alt and ra/dec.
298 const QPointF solutionAzAlt = Rotations::xyz2azAlt(solutionPoint);
299 solution->setAz(solutionAzAlt.x());
300 solution->setAlt(solutionAzAlt.y());
301 auto lst = geoLocation->GSTtoLST(times[2].gst());
302 solution->HorizontalToEquatorial(&lst, geoLocation->lat());
303
304 // Not sure if this is needed
305 solution->setRA0(solution->ra());
306 solution->setDec0(solution->dec());
307
308 // Move the solution back to J2000
309 KSNumbers num(times[2].djd());
310 *solution = solution->deprecess(&num);
311 solution->setRA0(solution->ra());
312 solution->setDec0(solution->dec());
313
314 // Ditto for alt-only solution
315 const QPointF altOnlySolutionAzAlt = Rotations::xyz2azAlt(altSolutionPoint);
316 altOnlySolution->setAz(altOnlySolutionAzAlt.x());
317 altOnlySolution->setAlt(altOnlySolutionAzAlt.y());
318 auto altOnlyLst = geoLocation->GSTtoLST(times[2].gst());
319 altOnlySolution->HorizontalToEquatorial(&altOnlyLst, geoLocation->lat());
320 altOnlySolution->setRA0(altOnlySolution->ra());
321 altOnlySolution->setDec0(altOnlySolution->dec());
322 KSNumbers altOnlyNum(times[2].djd());
323 *altOnlySolution = altOnlySolution->deprecess(&altOnlyNum);
324 altOnlySolution->setRA0(altOnlySolution->ra());
325 altOnlySolution->setDec0(altOnlySolution->dec());
326
327 return true;
328}
329
330bool PolarAlign::findAxis()
331{
332 if (points.size() != 3)
333 return false;
334
335 // We have 3 points, get their xyz positions.
336 V3 p1(Rotations::azAlt2xyz(QPointF(points[0].az().Degrees(), points[0].alt().Degrees())));
337 V3 p2(Rotations::azAlt2xyz(QPointF(points[1].az().Degrees(), points[1].alt().Degrees())));
338 V3 p3(Rotations::azAlt2xyz(QPointF(points[2].az().Degrees(), points[2].alt().Degrees())));
339 V3 axis = Rotations::getAxis(p1, p2, p3);
340
341 if (axis.length() < 0.9)
342 {
343 // It failed to normalize the vector, something's wrong.
344 qCInfo(KSTARS_EKOS_ALIGN) << "Normal vector too short. findAxis failed.";
345 return false;
346 }
347
348 // Need to make sure we're pointing to the right pole.
349 if ((northernHemisphere() && (axis.x() < 0)) || (!northernHemisphere() && axis.x() > 0))
350 {
351 axis = V3(-axis.x(), -axis.y(), -axis.z());
352 }
353
354 QPointF azAlt = Rotations::xyz2azAlt(axis);
355 azimuthCenter = azAlt.x();
356 altitudeCenter = azAlt.y();
357
358 return true;
359}
360
361void PolarAlign::getAxis(double *azAxis, double *altAxis) const
362{
363 *azAxis = azimuthCenter;
364 *altAxis = altitudeCenter;
365}
366
367// Find the pixel in image corresponding to the desired azimuth & altitude.
368bool PolarAlign::findAzAlt(const QSharedPointer<FITSData> &image, double azimuth, double altitude, QPointF *pixel) const
369{
370 SkyPoint spt;
371 spt.setAz(azimuth);
372 spt.setAlt(altitude);
373 dms LST = geoLocation->GSTtoLST(image->getDateTime().gst());
374 spt.HorizontalToEquatorial(&LST, geoLocation->lat());
375 SkyPoint j2000Coord = spt.catalogueCoord(image->getDateTime().djd());
376 QPointF imagePoint;
377 if (!image->wcsToPixel(j2000Coord, *pixel, imagePoint))
378 {
379 QString debugString =
380 QString("PolarAlign: Couldn't get pixel from WCS for az %1 alt %2 with j2000 RA %3 DEC %4")
381 .arg(QString::number(azimuth), QString::number(altitude), j2000Coord.ra0().toHMSString(), j2000Coord.dec0().toDMSString());
382 qCInfo(KSTARS_EKOS_ALIGN) << debugString;
383 return false;
384 }
385 return true;
386}
387
388// Calculate the mount's azimuth and altitude error given the known geographic location
389// and the azimuth center and altitude center computed in findAxis().
390void PolarAlign::calculateAzAltError(double *azError, double *altError) const
391{
392 const double latitudeDegrees = geoLocation->lat()->Degrees();
393 *altError = northernHemisphere() ?
394 altitudeCenter - latitudeDegrees : altitudeCenter + latitudeDegrees;
395 *azError = northernHemisphere() ? azimuthCenter : azimuthCenter + 180.0;
396 while (*azError > 180.0)
397 *azError -= 360;
398}
399
400void PolarAlign::setMaxPixelSearchRange(double degrees)
401{
402 // Suggestion for how far pixelError() below searches.
403 // Don't allow the search to be modified too much.
404 const double d = fabs(degrees);
405 if (d < 2)
406 maxPixelSearchRange = 2.0;
407 else if (d > 10)
408 maxPixelSearchRange = 10.0;
409 else
410 maxPixelSearchRange = d;
411}
412
413// Given the currently estimated RA axis polar alignment error, and given a start pixel,
414// find the polar-alignment error if the user moves a star (from his point of view)
415// from that pixel to pixel2.
416//
417// FindCorrectedPixel() determines where the user should move the star to fully correct
418// the alignment error. However, while the user is doing that, he/she may be at an intermediate
419// point (pixel2) and we want to feed back to the user what the "current" polar-alignment error is.
420// This searches using findCorrectedPixel() to
421// find the RA axis error which would be fixed by the user moving pixel to pixel2. The input
422// thus should be pixel = "current star position", and pixel2 = "solution star position"
423// from the original call to findCorrectedPixel. This calls findCorrectedPixel several hundred times
424// but is not too costly (about .1s on a RPi4). One could write a method that more directly estimates
425// the error given the current position, but it might not be applicable to our use-case as
426// we are constrained to move along paths detemined by a user adjusting an altitude knob and then
427// an azimuth adjustment. These corrections are likely not the most direct path to solve the axis error.
428bool PolarAlign::pixelError(const QSharedPointer<FITSData> &image, const QPointF &pixel, const QPointF &pixel2,
429 double *azError, double *altError)
430{
431 double azOffset, altOffset;
432 calculateAzAltError(&azOffset, &altOffset);
433
434 QPointF pix;
435 double azE = 0, altE = 0;
436
437 pixelError(image, pixel, pixel2,
438 -maxPixelSearchRange, maxPixelSearchRange, 0.2,
439 -maxPixelSearchRange, maxPixelSearchRange, 0.2, &azE, &altE, &pix);
440 pixelError(image, pixel, pixel2, azE - .2, azE + .2, 0.02,
441 altE - .2, altE + .2, 0.02, &azE, &altE, &pix);
442 pixelError(image, pixel, pixel2, azE - .02, azE + .02, 0.002,
443 altE - .02, altE + .02, 0.002, &azE, &altE, &pix);
444
445 const double pixDist = hypot(pix.x() - pixel2.x(), pix.y() - pixel2.y());
446 if (pixDist > 10)
447 return false;
448
449 *azError = azE;
450 *altError = altE;
451 return true;
452}
453
454void PolarAlign::pixelError(const QSharedPointer<FITSData> &image, const QPointF &pixel, const QPointF &pixel2,
455 double minAz, double maxAz, double azInc,
456 double minAlt, double maxAlt, double altInc,
457 double *azError, double *altError, QPointF *actualPixel)
458{
459 double minDistSq = 1e9;
460 for (double eAz = minAz; eAz < maxAz; eAz += azInc)
461 {
462 for (double eAlt = minAlt; eAlt < maxAlt; eAlt += altInc)
463 {
464 QPointF pix;
465 if (findCorrectedPixel(image, pixel, &pix, eAz, eAlt))
466 {
467 // compare the distance to the pixel
468 double distSq = ((pix.x() - pixel2.x()) * (pix.x() - pixel2.x()) +
469 (pix.y() - pixel2.y()) * (pix.y() - pixel2.y()));
470 if (distSq < minDistSq)
471 {
472 minDistSq = distSq;
473 *actualPixel = pix;
474 *azError = eAz;
475 *altError = eAlt;
476 }
477 }
478 }
479 }
480}
481
482// Given a pixel, find its RA/DEC, then its alt/az, and then solve for another pixel
483// where, if the star in pixel is moved to that star in the user's image (by adjusting alt and az controls)
484// the polar alignment error would be 0.
485bool PolarAlign::findCorrectedPixel(const QSharedPointer<FITSData> &image, const QPointF &pixel, QPointF *corrected,
486 bool altOnly)
487{
488 double azOffset, altOffset;
489 calculateAzAltError(&azOffset, &altOffset);
490 if (altOnly)
491 azOffset = 0.0;
492 return findCorrectedPixel(image, pixel, corrected, azOffset, altOffset);
493}
494
495// Given a pixel, find its RA/DEC, then its alt/az, and then solve for another pixel
496// where, if the star in pixel is moved to that star in the user's image (by adjusting alt and az controls)
497// the polar alignment error would be 0. We use the fact that we can only move by adjusting and altitude
498// knob, then an azimuth knob--i.e. we likely don't traverse a great circle.
499bool PolarAlign::findCorrectedPixel(const QSharedPointer<FITSData> &image, const QPointF &pixel, QPointF *corrected,
500 double azOffset,
501 double altOffset)
502{
503 // 1. Find the az/alt for the x,y point on the image.
504 SkyPoint p;
505 if (!prepareAzAlt(image, pixel, &p))
506 return false;
507 double pixelAz = p.az().Degrees(), pixelAlt = p.alt().Degrees();
508
509 // 2. Apply the az/alt offsets.
510 // We know that the pole's az and alt offsets are effectively rotations
511 // of a sphere. The offsets that apply to correct different points depend
512 // on where (in the sphere) those points are. Points close to the pole can probably
513 // just add the pole's offsets. This calculation is a bit more precise, and is
514 // necessary if the points are not near the pole.
515 double altRotation = northernHemisphere() ? altOffset : -altOffset;
516 QPointF rotated = Rotations::rotateRaAxis(QPointF(pixelAz, pixelAlt), QPointF(azOffset, altRotation));
517
518 // 3. Find a pixel with those alt/az values.
519 if (!findAzAlt(image, rotated.x(), rotated.y(), corrected))
520 return false;
521
522 return true;
523}
Contains all relevant information for specifying a location on Earth: City Name, State/Province name,...
Definition geolocation.h:28
const CachingDms * lat() const
Definition geolocation.h:70
There are several time-dependent values used in position calculations, that are not specific to an ob...
Definition ksnumbers.h:43
GeoLocation * geo()
Definition kstarsdata.h:232
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
long double djd() const
The sky coordinates of a point in the sky.
Definition skypoint.h:45
void apparentCoord(long double jd0, long double jdf)
Computes the apparent coordinates for this SkyPoint for any epoch, accounting for the effects of prec...
Definition skypoint.cpp:700
const CachingDms & dec() const
Definition skypoint.h:269
const CachingDms & ra0() const
Definition skypoint.h:251
const CachingDms & ra() const
Definition skypoint.h:263
static SkyPoint timeTransformed(const SkyPoint *p, const KStarsDateTime &dt, const GeoLocation *geo, const double hour=0)
returns a time-transformed SkyPoint.
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
Definition skypoint.h:94
const dms & az() const
Definition skypoint.h:275
void setAlt(dms alt)
Sets Alt, the Altitude.
Definition skypoint.h:194
const dms & alt() const
Definition skypoint.h:281
void HorizontalToEquatorial(const dms *LST, const dms *lat)
Determine the (RA, Dec) coordinates of the SkyPoint from its (Altitude, Azimuth) coordinates,...
Definition skypoint.cpp:143
void setAz(dms az)
Sets Az, the Azimuth.
Definition skypoint.h:230
const CachingDms & dec0() const
Definition skypoint.h:257
void setDec0(dms d)
Sets Dec0, the catalog Declination.
Definition skypoint.h:119
SkyPoint catalogueCoord(long double jdf)
Computes the J2000.0 catalogue coordinates for this SkyPoint using the epoch removing aberration,...
Definition skypoint.cpp:710
SkyPoint deprecess(const KSNumbers *num, long double epoch=J2000)
Obtain a Skypoint with RA0 and Dec0 set from the RA, Dec of this skypoint.
Definition skypoint.cpp:257
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition dms.cpp:287
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
Definition dms.cpp:378
const double & Degrees() const
Definition dms.h:141
GeoCoordinates geo(const QVariant &location)
qreal x() const const
qreal y() const const
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:04:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.