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

KDE's Doxygen guidelines are available online.