Kstars

polaralign.cpp
1 /*
2  SPDX-FileCopyrightText: 2021 Hy Murveit <[email protected]>
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 /******************************************************************
19 PolarAlign is a class that supports polar alignment by determining the
20 mount's axis of rotation when given 3 solved images taken with RA mount
21 rotations between the images.
22 
23 addPoint(image) is called by the polar alignment UI after it takes and
24 solves each of its three images. The solutions are store in SkyPoints (see below)
25 and are processed so that the sky positions correspond to "what's in the sky
26 now" and "at this geographic localtion".
27 
28 Addpoint() samples the location of a particular pixel in its image.
29 When the 3 points are sampled, they should not be taken
30 from the center of the image, as HA rotations may not move that point
31 if the telescope and mount are well aligned. Thus, the points are sampled
32 from the edge of the image.
33 
34 After all 3 images are sampled, findAxis() is called, which solves for the mount's
35 axis of rotation. It then transforms poleAxis' result into azimuth and altitude
36 offsets from the pole.
37 
38 After the mount's current RA axis is determined, the user then attempts to correct/improve
39 it to match the Earth's real polar axes. Ekos has two techniques to do that. In both
40 Ekos takes a series of "refresh images". The user looks at the images and their
41 associated analyses and adjusts the mount's altitude and azimuth knobs.
42 
43 In the first scheme, the user identifies a refrence star on the image. Ekos draws a triangle
44 over the image, and user attempts to "move the star" along two sides of that triangle.
45 
46 In the 2nd scheme, the system plate-solves the refresh images, telling the user which direction
47 and how much to adjust the knobs.
48 
49 findCorrectedPixel() supports the "move the star" refresh scheme.
50 It is given an x,y position on an image and the offsets
51 generated by findAxis(). It computes a "corrected position" for that input
52 x,y point such that if a user adjusted the GEM mount's altitude and azimuth
53 knobs to move a star centered in the image's original x,y position to the corrected
54 position in the image, the mount's axis of rotation should then coincide with the pole.
55 
56 processRefreshCoords() supports the plate-solving refresh scheme.
57 It is given the center coordinates of a refresh image. It remembers the originally
58 calculated mount axis, and the position of the 3rd measurement image. It computes how
59 much the user has already adjusted the azimuth and altitude knobs from the difference
60 in pointing between the new refresh image's center coordinates and that of the 3rd measurement
61 image. It infers what the mounts new RA axis must be (based on that adjustment) and returns
62 the new polar alignment error.
63 ******************************************************************/
64 
65 using Rotations::V3;
66 
67 PolarAlign::PolarAlign(const GeoLocation *geo)
68 {
69  if (geo == nullptr && KStarsData::Instance() != nullptr)
70  geoLocation = KStarsData::Instance()->geo();
71  else
72  geoLocation = geo;
73 }
74 
75 bool PolarAlign::northernHemisphere() const
76 {
77  if ((geoLocation == nullptr) || (geoLocation->lat() == nullptr))
78  return true;
79  return geoLocation->lat()->Degrees() > 0;
80 }
81 
82 void 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.
90 bool 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 
103 bool 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 
124 namespace
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".
130 double 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.
141 double 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".
169 double 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(5.0, 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 } // namespace
189 
190 // Compute the polar-alignment azimuth and altitude error by comparing the new image's coordinates
191 // with the coordinates from the 3rd measurement image. Use the difference to infer a rotation angle,
192 // and rotate the originally computed polar-alignment axis by that angle to find the new axis
193 // around which RA now rotates.
194 bool PolarAlign::processRefreshCoords(const SkyPoint &coords, const KStarsDateTime &time,
195  double *azError, double *altError,
196  double *azAdjustment, double *altAdjustment) const
197 {
198  // Get the az and alt from this new measurement (coords), and from that derive its x,y,z coordinates.
199  auto c = coords; // apparentCoord modifies its input. Use the temp variable c to keep coords const.
200  c.apparentCoord(static_cast<long double>(J2000), time.djd());
201  SkyPoint point = SkyPoint::timeTransformed(&c, time, geoLocation, 0);
202  const double az = point.az().Degrees(), alt = point.alt().Degrees();
203  const V3 newPoint = Rotations::azAlt2xyz(QPointF(az, alt));
204 
205  // Get the x,y,z coordinates of the original position (from the 3rd polar-align image).
206  // We can't simply use the az/alt already computed for point3 because the mount is tracking,
207  // and thus, even if the user made no adjustments, but simply took an image a little while
208  // later at the same RA/DEC coordinates, the Az/Alt would have changed and we'd believe there
209  // was a user rotation due to changes in the Az/Alt knobs. Instead we must convert point3's az/alt
210  // values to what they would be if that image had been taken now, still pointing at point3's ra/dec.
211  // The key is to rotate the original point around the original RA axis by the rotation given by the time
212  // difference multiplied by the sidereal rate.
213 
214  // Figure out what the az/alt would be if the user hadn't modified the knobs.
215  // That is, just rotate the 3rd measurement point around the mount's original RA axis.
216  // Time since third point in seconds
217  const double p3secs = times[2].secsTo(time);
218  // Angle corresponding to that interval assuming the sidereal rate.
219  const double p3Angle = (-15.041067 * p3secs) / 3600.0; // degrees
220 
221  // Get the xyz coordinates of the original 3rd point.
222  const V3 p3OrigPoint = Rotations::azAlt2xyz(QPointF(points[2].az().Degrees(), points[2].alt().Degrees()));
223  // Get the unit vector corresponding the original RA axis
224  const V3 origAxisPt = Rotations::azAlt2xyz(QPointF(azimuthCenter, altitudeCenter));
225  // Rotate the original 3rd point around that axis, simulating the mount's tracking movements.
226  const V3 point3 = Rotations::rotateAroundAxis(p3OrigPoint, origAxisPt, p3Angle);
227 
228  // Find the adjustment the user must have made by examining the change from point3 to newPoint
229  // (i.e. the rotation caused by the user adjusting the azimuth and altitude knobs).
230  // We assume that this was a rotation around a level mount's y axis and z axis.
231  double zAdjustment, yAdjustment;
232  double residual = getRotationAngles(point3, newPoint, &zAdjustment, &yAdjustment);
233  if (residual > 0.5)
234  {
235  qCInfo(KSTARS_EKOS_ALIGN) << QString("PAA refresh: failed to estimate rotation angle (residual %1'").arg(residual * 60);
236  return false;
237  }
238  qCInfo(KSTARS_EKOS_ALIGN) << QString("PAA refresh: Estimated current adjustment: Az %1' Alt %2' residual %3a-s")
239  .arg(zAdjustment * 60, 0, 'f', 1).arg(yAdjustment * 60, 0, 'f', 1).arg(residual * 3600, 0, 'f', 0);
240 
241  // Return the estimated adjustments (used by testing).
242  if (altAdjustment != nullptr) *altAdjustment = yAdjustment;
243  if (azAdjustment != nullptr) *azAdjustment = zAdjustment;
244 
245  // Rotate the original RA axis position by the above adjustments.
246  const V3 origAxisPoint = Rotations::azAlt2xyz(QPointF(azimuthCenter, altitudeCenter));
247  const V3 tempPoint = Rotations::rotateAroundY(origAxisPoint, yAdjustment);
248  const V3 newAxisPoint = Rotations::rotateAroundZ(tempPoint, zAdjustment);
249 
250  // Convert the rotated axis point back to an az/alt coordinate, representing the new RA axis.
251  const QPointF newAxisAzAlt = Rotations::xyz2azAlt(newAxisPoint);
252  const double newAxisAz = newAxisAzAlt.x();
253  const double newAxisAlt = newAxisAzAlt.y();
254 
255  // Compute the polar alignment error for the new RA axis.
256  const double latitudeDegrees = geoLocation->lat()->Degrees();
257  *altError = northernHemisphere() ? newAxisAlt - latitudeDegrees : newAxisAlt + latitudeDegrees;
258  *azError = northernHemisphere() ? newAxisAz : newAxisAz + 180.0;
259  while (*azError > 180.0)
260  *azError -= 360;
261 
262  QString infoString =
263  QString("PAA refresh: ra0 %1 dec0 %2 Az/Alt: %3 %4 AXIS: %5 %6 --> %7 %8 ERR: %9' alt %10'")
264  .arg(coords.ra0().Degrees(), 0, 'f', 3).arg(coords.dec0().Degrees(), 0, 'f', 3)
265  .arg(az, 0, 'f', 3).arg(alt, 0, 'f', 3)
266  .arg(azimuthCenter, 0, 'f', 3).arg(altitudeCenter, 0, 'f', 3)
267  .arg(newAxisAz, 0, 'f', 3).arg(newAxisAlt, 0, 'f', 3)
268  .arg(*azError * 60, 0, 'f', 1).arg(*altError * 60, 0, 'f', 1);
269  qCInfo(KSTARS_EKOS_ALIGN) << infoString;
270 
271  return true;
272 }
273 
274 // Given the telescope's current RA axis, and the its current pointing position,
275 // compute the coordinates where it should point such that its RA axis will be at the pole.
276 bool PolarAlign::refreshSolution(SkyPoint *solution, SkyPoint *altOnlySolution) const
277 {
278  if (points.size() != 3)
279  return false;
280 
281  double azError, altError;
282  calculateAzAltError(&azError, &altError);
283 
284  // The Y rotation to correct polar alignment is -altitude error, and the Z correction is -azimuth error.
285  // Rotate the 3rd-image center coordinate by the above angles.
286  // This is the position the telescope needs to point to (if it is taken there
287  // by adjusting alt and az knobs) such that the new RA rotation axis is aligned with the pole.
288  const V3 point3 = Rotations::azAlt2xyz(QPointF(points[2].az().Degrees(), points[2].alt().Degrees()));
289  const V3 altSolutionPoint = Rotations::rotateAroundY(point3, altError);
290  const V3 solutionPoint = Rotations::rotateAroundZ(altSolutionPoint, azError);
291 
292  // Convert the solution xyz points back to az/alt and ra/dec.
293  const QPointF solutionAzAlt = Rotations::xyz2azAlt(solutionPoint);
294  solution->setAz(solutionAzAlt.x());
295  solution->setAlt(solutionAzAlt.y());
296  auto lst = geoLocation->GSTtoLST(times[2].gst());
297  solution->HorizontalToEquatorial(&lst, geoLocation->lat());
298 
299  // Not sure if this is needed
300  solution->setRA0(solution->ra());
301  solution->setDec0(solution->dec());
302 
303  // Move the solution back to J2000
304  KSNumbers num(times[2].djd());
305  *solution = solution->deprecess(&num);
306  solution->setRA0(solution->ra());
307  solution->setDec0(solution->dec());
308 
309  // Ditto for alt-only solution
310  const QPointF altOnlySolutionAzAlt = Rotations::xyz2azAlt(altSolutionPoint);
311  altOnlySolution->setAz(altOnlySolutionAzAlt.x());
312  altOnlySolution->setAlt(altOnlySolutionAzAlt.y());
313  auto altOnlyLst = geoLocation->GSTtoLST(times[2].gst());
314  altOnlySolution->HorizontalToEquatorial(&altOnlyLst, geoLocation->lat());
315  altOnlySolution->setRA0(altOnlySolution->ra());
316  altOnlySolution->setDec0(altOnlySolution->dec());
317  KSNumbers altOnlyNum(times[2].djd());
318  *altOnlySolution = altOnlySolution->deprecess(&altOnlyNum);
319  altOnlySolution->setRA0(altOnlySolution->ra());
320  altOnlySolution->setDec0(altOnlySolution->dec());
321 
322  return true;
323 }
324 
325 bool PolarAlign::findAxis()
326 {
327  if (points.size() != 3)
328  return false;
329 
330  // We have 3 points, get their xyz positions.
331  V3 p1(Rotations::azAlt2xyz(QPointF(points[0].az().Degrees(), points[0].alt().Degrees())));
332  V3 p2(Rotations::azAlt2xyz(QPointF(points[1].az().Degrees(), points[1].alt().Degrees())));
333  V3 p3(Rotations::azAlt2xyz(QPointF(points[2].az().Degrees(), points[2].alt().Degrees())));
334  V3 axis = Rotations::getAxis(p1, p2, p3);
335 
336  if (axis.length() < 0.9)
337  {
338  // It failed to normalize the vector, something's wrong.
339  qCInfo(KSTARS_EKOS_ALIGN) << "Normal vector too short. findAxis failed.";
340  return false;
341  }
342 
343  // Need to make sure we're pointing to the right pole.
344  if ((northernHemisphere() && (axis.x() < 0)) || (!northernHemisphere() && axis.x() > 0))
345  {
346  axis = V3(-axis.x(), -axis.y(), -axis.z());
347  }
348 
349  QPointF azAlt = Rotations::xyz2azAlt(axis);
350  azimuthCenter = azAlt.x();
351  altitudeCenter = azAlt.y();
352 
353  return true;
354 }
355 
356 void PolarAlign::getAxis(double *azAxis, double *altAxis) const
357 {
358  *azAxis = azimuthCenter;
359  *altAxis = altitudeCenter;
360 }
361 
362 // Find the pixel in image corresponding to the desired azimuth & altitude.
363 bool PolarAlign::findAzAlt(const QSharedPointer<FITSData> &image, double azimuth, double altitude, QPointF *pixel) const
364 {
365  SkyPoint spt;
366  spt.setAz(azimuth);
367  spt.setAlt(altitude);
368  dms LST = geoLocation->GSTtoLST(image->getDateTime().gst());
369  spt.HorizontalToEquatorial(&LST, geoLocation->lat());
370  SkyPoint j2000Coord = spt.catalogueCoord(image->getDateTime().djd());
371  QPointF imagePoint;
372  if (!image->wcsToPixel(j2000Coord, *pixel, imagePoint))
373  {
374  QString debugString =
375  QString("PolarAlign: Couldn't get pixel from WCS for az %1 alt %2 with j2000 RA %3 DEC %4")
376  .arg(QString::number(azimuth), QString::number(altitude), j2000Coord.ra0().toHMSString(), j2000Coord.dec0().toDMSString());
377  qCInfo(KSTARS_EKOS_ALIGN) << debugString;
378  return false;
379  }
380  return true;
381 }
382 
383 // Calculate the mount's azimuth and altitude error given the known geographic location
384 // and the azimuth center and altitude center computed in findAxis().
385 void PolarAlign::calculateAzAltError(double *azError, double *altError) const
386 {
387  const double latitudeDegrees = geoLocation->lat()->Degrees();
388  *altError = northernHemisphere() ?
389  altitudeCenter - latitudeDegrees : altitudeCenter + latitudeDegrees;
390  *azError = northernHemisphere() ? azimuthCenter : azimuthCenter + 180.0;
391  while (*azError > 180.0)
392  *azError -= 360;
393 }
394 
395 void PolarAlign::setMaxPixelSearchRange(double degrees)
396 {
397  // Suggestion for how far pixelError() below searches.
398  // Don't allow the search to be modified too much.
399  const double d = fabs(degrees);
400  if (d < 2)
401  maxPixelSearchRange = 2.0;
402  else if (d > 10)
403  maxPixelSearchRange = 10.0;
404  else
405  maxPixelSearchRange = d;
406 }
407 
408 // Given the currently estimated RA axis polar alignment error, and given a start pixel,
409 // find the polar-alignment error if the user moves a star (from his point of view)
410 // from that pixel to pixel2.
411 //
412 // FindCorrectedPixel() determines where the user should move the star to fully correct
413 // the alignment error. However, while the user is doing that, he/she may be at an intermediate
414 // point (pixel2) and we want to feed back to the user what the "current" polar-alignment error is.
415 // This searches using findCorrectedPixel() to
416 // find the RA axis error which would be fixed by the user moving pixel to pixel2. The input
417 // thus should be pixel = "current star position", and pixel2 = "solution star position"
418 // from the original call to findCorrectedPixel. This calls findCorrectedPixel several hundred times
419 // but is not too costly (about .1s on a RPi4). One could write a method that more directly estimates
420 // the error given the current position, but it might not be applicable to our use-case as
421 // we are constrained to move along paths detemined by a user adjusting an altitude knob and then
422 // an azimuth adjustment. These corrections are likely not the most direct path to solve the axis error.
423 bool PolarAlign::pixelError(const QSharedPointer<FITSData> &image, const QPointF &pixel, const QPointF &pixel2,
424  double *azError, double *altError)
425 {
426  double azOffset, altOffset;
427  calculateAzAltError(&azOffset, &altOffset);
428 
429  QPointF pix;
430  double azE = 0, altE = 0;
431 
432  pixelError(image, pixel, pixel2,
433  -maxPixelSearchRange, maxPixelSearchRange, 0.2,
434  -maxPixelSearchRange, maxPixelSearchRange, 0.2, &azE, &altE, &pix);
435  pixelError(image, pixel, pixel2, azE - .2, azE + .2, 0.02,
436  altE - .2, altE + .2, 0.02, &azE, &altE, &pix);
437  pixelError(image, pixel, pixel2, azE - .02, azE + .02, 0.002,
438  altE - .02, altE + .02, 0.002, &azE, &altE, &pix);
439 
440  const double pixDist = hypot(pix.x() - pixel2.x(), pix.y() - pixel2.y());
441  if (pixDist > 10)
442  return false;
443 
444  *azError = azE;
445  *altError = altE;
446  return true;
447 }
448 
449 void PolarAlign::pixelError(const QSharedPointer<FITSData> &image, const QPointF &pixel, const QPointF &pixel2,
450  double minAz, double maxAz, double azInc,
451  double minAlt, double maxAlt, double altInc,
452  double *azError, double *altError, QPointF *actualPixel)
453 {
454  double minDistSq = 1e9;
455  for (double eAz = minAz; eAz < maxAz; eAz += azInc)
456  {
457  for (double eAlt = minAlt; eAlt < maxAlt; eAlt += altInc)
458  {
459  QPointF pix;
460  if (findCorrectedPixel(image, pixel, &pix, eAz, eAlt))
461  {
462  // compare the distance to the pixel
463  double distSq = ((pix.x() - pixel2.x()) * (pix.x() - pixel2.x()) +
464  (pix.y() - pixel2.y()) * (pix.y() - pixel2.y()));
465  if (distSq < minDistSq)
466  {
467  minDistSq = distSq;
468  *actualPixel = pix;
469  *azError = eAz;
470  *altError = eAlt;
471  }
472  }
473  }
474  }
475 }
476 
477 // Given a pixel, find its RA/DEC, then its alt/az, and then solve for another pixel
478 // where, if the star in pixel is moved to that star in the user's image (by adjusting alt and az controls)
479 // the polar alignment error would be 0.
480 bool PolarAlign::findCorrectedPixel(const QSharedPointer<FITSData> &image, const QPointF &pixel, QPointF *corrected,
481  bool altOnly)
482 {
483  double azOffset, altOffset;
484  calculateAzAltError(&azOffset, &altOffset);
485  if (altOnly)
486  azOffset = 0.0;
487  return findCorrectedPixel(image, pixel, corrected, azOffset, altOffset);
488 }
489 
490 // Given a pixel, find its RA/DEC, then its alt/az, and then solve for another pixel
491 // where, if the star in pixel is moved to that star in the user's image (by adjusting alt and az controls)
492 // the polar alignment error would be 0. We use the fact that we can only move by adjusting and altitude
493 // knob, then an azimuth knob--i.e. we likely don't traverse a great circle.
494 bool PolarAlign::findCorrectedPixel(const QSharedPointer<FITSData> &image, const QPointF &pixel, QPointF *corrected,
495  double azOffset,
496  double altOffset)
497 {
498  // 1. Find the az/alt for the x,y point on the image.
499  SkyPoint p;
500  if (!prepareAzAlt(image, pixel, &p))
501  return false;
502  double pixelAz = p.az().Degrees(), pixelAlt = p.alt().Degrees();
503 
504  // 2. Apply the az/alt offsets.
505  // We know that the pole's az and alt offsets are effectively rotations
506  // of a sphere. The offsets that apply to correct different points depend
507  // on where (in the sphere) those points are. Points close to the pole can probably
508  // just add the pole's offsets. This calculation is a bit more precise, and is
509  // necessary if the points are not near the pole.
510  double altRotation = northernHemisphere() ? altOffset : -altOffset;
511  QPointF rotated = Rotations::rotateRaAxis(QPointF(pixelAz, pixelAlt), QPointF(azOffset, altRotation));
512 
513  // 3. Find a pixel with those alt/az values.
514  if (!findAzAlt(image, rotated.x(), rotated.y(), corrected))
515  return false;
516 
517  return true;
518 }
const dms & alt() const
Definition: skypoint.h:281
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
void setAlt(dms alt)
Sets Alt, the Altitude.
Definition: skypoint.h:194
long double djd() const
QString number(int n, int base)
static SkyPoint timeTransformed(const SkyPoint *p, const KStarsDateTime &dt, const GeoLocation *geo, const double hour=0)
returns a time-transformed SkyPoint.
Definition: skypoint.cpp:1121
Stores dms coordinates for a point in the sky. for converting between coordinate systems.
Definition: skypoint.h:44
void setDec0(dms d)
Sets Dec0, the catalog Declination.
Definition: skypoint.h:119
void setRA0(dms r)
Sets RA0, the catalog Right Ascension.
Definition: skypoint.h:94
const QString toHMSString(const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:370
const QString toDMSString(const bool forceSign=false, const bool machineReadable=false, const bool highPrecision=false) const
Definition: dms.cpp:279
Store several time-dependent astronomical quantities.
Definition: ksnumbers.h:42
const CachingDms & dec() const
Definition: skypoint.h:269
GeoLocation * geo()
Definition: kstarsdata.h:229
void push_back(QChar ch)
GeoCoordinates geo(const QVariant &location)
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
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
void setAz(dms az)
Sets Az, the Azimuth.
Definition: skypoint.h:230
qreal x() const const
qreal y() const const
const CachingDms & ra() const
Definition: skypoint.h:263
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
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 & dec0() const
Definition: skypoint.h:257
const double & Degrees() const
Definition: dms.h:141
const CachingDms & ra0() const
Definition: skypoint.h:251
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
SkyPoint catalogueCoord(long double jdf)
Computes the J2000.0 catalogue coordinates for this SkyPoint using the epoch removing aberration,...
Definition: skypoint.cpp:710
Relevant data about an observing location on Earth.
Definition: geolocation.h:27
const dms & az() const
Definition: skypoint.h:275
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Aug 8 2022 04:13:23 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.