Instrument Neutral Distributed Interface INDI 2.2.5
SPKMathPlugin.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 Christian Kemper. All rights reserved.
3
4 SPK Math Plugin for INDI Alignment Subsystem.
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License version 2 as published by the Free Software Foundation.
9 .
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14 .
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*******************************************************************************/
20
21#include "SPKMathPlugin.h"
22#include "DriverCommon.h"
23#include <vector>
24#include <cmath>
25#include <cstring>
26#include "indicom.h"
27#include "libastro.h"
28#include "spk/sofa.h"
29#include <libnova/julian_day.h>
30#include <libnova/sidereal_time.h>
31
32namespace INDI
33{
34namespace AlignmentSubsystem
35{
36
37#ifndef NO_PLUGIN_HOOKS
38// Standard functions required for all plugins
39extern "C" {
40 SPKMathPlugin *Create()
41 {
42 return new SPKMathPlugin;
43 }
44
45 void Destroy(SPKMathPlugin *pPlugin)
46 {
47 delete pPlugin;
48 }
49
50 const char *GetDisplayName()
51 {
52 return "SPK Math Plugin";
53 }
54}
55#endif
56
58{
59 // Initialize structures with defaults
60 memset(&m_Obs, 0, sizeof(m_Obs));
61 memset(&m_PM, 0, sizeof(m_PM));
62 memset(&m_Ast, 0, sizeof(m_Ast));
63 memset(&m_Opt, 0, sizeof(m_Opt));
64 memset(&m_PO, 0, sizeof(m_PO));
65
66 m_Opt.fl = 1000.0; // Default fl
67 m_Opt.wl = 0.55; // Default wavelength
68
69 memset(m_NE_A, 0, sizeof(m_NE_A));
70 memset(m_NE_v, 0, sizeof(m_NE_v));
71 m_NE_n = 0;
72 m_NE_mountChar = 0;
73 m_NE_valid = false;
74}
75
79
81{
82 if (!pInMemoryDatabase) return false;
84
85 if (!UpdateObsConfig()) return true; // no location yet — model unconstrained, not an error
86
88 int n = static_cast<int>(syncPoints.size());
89 char mountChar = (m_Obs.mount == ALTAZ) ? 'A' : 'E';
90
91 if (n < 1) return true; // No sync points yet — not an error, model is simply unconstrained.
92
93 ASSDEBUGF("SPK Initialise: n=%d mount=%c NE_valid=%d NE_n=%d",
94 n, mountChar, m_NE_valid, m_NE_n);
95
96 // Hot path: full 6-term model already fitted, exactly one new point added,
97 // same mount type.
98 if (m_NE_valid &&
99 n == m_NE_n + 1 &&
100 mountChar == m_NE_mountChar)
101 {
102 ASSDEBUGF("SPK Initialise: hot path — incremental NE update for point %d", n);
103 double oblon, oblat, rdem, pdem;
104 if (ExtractObsRow(syncPoints.back(), oblon, oblat, rdem, pdem) &&
105 AccumulateObsRow(oblon, oblat, rdem, pdem))
106 {
107 double pmv[6];
108 if (SolveNormalEquations(pmv))
109 {
110 m_NE_n = n;
111 ParsePmfitCoefficients(pmv, 6);
112 return true;
113 }
114 }
115 // Fall through to cold path on any failure.
116 ASSDEBUG("SPK hot path failed - falling through to cold path");
117 }
118
119 // Cold path: full Pmfit rebuild.
120 int nt = 0;
121 std::vector<double> obsData = BuildObservationData(syncPoints, nt);
122
123 double pmv[6] = {}, pms[6] = {}, skysig = 0;
124 int js = Pmfit(m_Obs.slat, mountChar, n, obsData.data(), nt, pmv, pms, &skysig);
125 if (js != 0)
126 {
127 ASSDEBUGF("SPK Pmfit failed: js=%d", js);
128 m_NE_valid = false;
129 return false;
130 }
131
132 ParsePmfitCoefficients(pmv, nt);
133
134 // If we have a full 6-term model, build the NE accumulator so subsequent
135 // single-point additions can use the hot path.
136 if (nt == 6)
137 {
138 m_NE_mountChar = mountChar;
139 memset(m_NE_A, 0, sizeof(m_NE_A));
140 memset(m_NE_v, 0, sizeof(m_NE_v));
141 m_NE_valid = true;
142
143 for (const auto &point : syncPoints)
144 {
145 double oblon, oblat, rdem, pdem;
146 if (!ExtractObsRow(point, oblon, oblat, rdem, pdem) ||
147 !AccumulateObsRow(oblon, oblat, rdem, pdem))
148 {
149 m_NE_valid = false;
150 break;
151 }
152 }
153 m_NE_n = m_NE_valid ? n : 0;
154 }
155 else
156 {
157 m_NE_valid = false;
158 }
159
160 return true;
161}
162
163bool SPKMathPlugin::TransformCelestialToTelescope(const double RightAscension, const double Declination,
164 double JulianOffset,
165 TelescopeDirectionVector &ApparentTelescopeDirectionVector)
166{
167 return TransformCelestialToTelescopeJD(RightAscension, Declination,
168 ln_get_julian_from_sys() + JulianOffset,
169 ApparentTelescopeDirectionVector);
170}
171
172bool SPKMathPlugin::TransformCelestialToTelescopeJD(double RightAscension, double Declination,
173 double JulianDate,
174 TelescopeDirectionVector &ApparentTelescopeDirectionVector)
175{
176 if (!UpdateObsConfig()) return false;
177 UpdateAstrometry(JulianDate);
178
179 spkTAR tar;
180 // Input RA/Dec is in INDI's "observed" (= SOFA's "apparent") form: precession,
181 // nutation, and aberration have been applied by the INDI subsystem (J2000toObserved),
182 // but atmospheric refraction has not. tar.sys = APPT matches this — it tells spkVtel
183 // the coordinates are already in apparent form and not to re-apply those transforms.
184 // Refraction is not modeled in this pipeline; pressure=0 in spkAIR disables
185 // spkVtel's internal refraction as well.
186 tar.sys = APPT;
187 tar.a = HOURS_TO_RAD(RightAscension);
188 tar.b = DEG_TO_RAD(Declination);
189
190 double tara, tare, tarr, tarp, soln[5];
191
192 // Two-pass: first AXES call with ax3={0,0,0} gives an initial encoder
193 // demand; second pass re-evaluates the VD correction at the correct
194 // elevation. One refinement suffices; residual is O(pvd^2).
195 spkAX3 ax3 = {0, 0, 0};
196 int status = spkVtel(AXES, &m_Obs, &m_Opt, &m_PM, &m_Ast, &ax3, &tar, &m_PO,
197 &tara, &tare, &tarr, &tarp, soln);
198
199 if (status >= 0)
200 {
201 ax3.a = soln[0];
202 ax3.b = soln[1];
203 status = spkVtel(AXES, &m_Obs, &m_Opt, &m_PM, &m_Ast, &ax3, &tar, &m_PO,
204 &tara, &tare, &tarr, &tarp, soln);
205 }
206
207 if (status >= 0)
208 {
209 double roll = soln[0];
210 double pitch = soln[1];
211 ApparentTelescopeDirectionVector = RollPitchToDirectionVector(roll, pitch);
212 return true;
213 }
214 ASSDEBUGF("SPK C->T failed: RA=%.4fh Dec=%.4fdeg status=%d", RightAscension, Declination, status);
215 return false;
216}
217
219 double &RightAscension, double &Declination, double JulianOffset)
220{
221 return TransformTelescopeToCelestialJD(ApparentTelescopeDirectionVector, RightAscension, Declination,
222 ln_get_julian_from_sys() + JulianOffset);
223}
224
226 double &RightAscension, double &Declination, double JulianDate)
227{
228 if (!UpdateObsConfig()) return false;
229 UpdateAstrometry(JulianDate);
230
231 spkAX3 ax3;
232 double roll, pitch;
233 DirectionVectorToRollPitch(ApparentTelescopeDirectionVector, roll, pitch);
234
235 ax3.a = roll;
236 ax3.b = pitch;
237 ax3.r = 0.0;
238
239 spkTAR tar;
240 tar.sys = APPT;
241 tar.a = 0.0; // not used by spkVtel in TARG mode, but read unconditionally before the mode switch
242 tar.b = 0.0;
243
244 double tara, tare, tarr, tarp, soln[5];
245 int status = spkVtel(TARG, &m_Obs, &m_Opt, &m_PM, &m_Ast, &ax3, &tar, &m_PO,
246 &tara, &tare, &tarr, &tarp, soln);
247
248 if (status >= 0)
249 {
250 // soln[0] is RA in radians
251 RightAscension = RAD_TO_HOURS(iauAnp(soln[0]));
252 Declination = RAD_TO_DEG(soln[1]);
253 ASSDEBUGF("SPK T->C: roll=%.4fdeg pitch=%.4fdeg => RA=%.4fh Dec=%.4fdeg",
254 RAD_TO_DEG(roll), RAD_TO_DEG(pitch), RightAscension, Declination);
255 return true;
256 }
257 ASSDEBUGF("SPK T->C failed: roll=%.4fdeg pitch=%.4fdeg status=%d",
258 RAD_TO_DEG(roll), RAD_TO_DEG(pitch), status);
259 return false;
260}
261
262bool SPKMathPlugin::UpdateObsConfig()
263{
264 if (!pInMemoryDatabase) return false;
267 return false;
268 m_Obs.slat = DEG_TO_RAD(pos.latitude);
269 m_Obs.slon = DEG_TO_RAD(pos.longitude);
270 m_Obs.sh = pos.elevation;
272 return true;
273}
274
275void SPKMathPlugin::UpdateAstrometry(double JD)
276{
277 // Use libnova for JD to calendar conversion
278 ln_date date;
279 ln_get_date(JD, &date);
280
281 spkUTC utc;
282 utc.iy = date.years;
283 utc.mo = date.months;
284 utc.id = date.days;
285 utc.ih = date.hours;
286 utc.mi = date.minutes;
287 utc.sec = date.seconds;
288
289 // Compute LST first — needed both for the ERA synchronization below and
290 // as a safe fallback if spkAstr fails.
291 double gmst_hrs = ln_get_apparent_sidereal_time(JD);
292 double lst_rad = HOURS_TO_RAD(range24(gmst_hrs + RAD_TO_HOURS(m_Obs.slon)));
293 m_LST_rad = lst_rad;
294
295 spkEOP eop = {0, 0, 0};
296 // pressure=0 disables spkVtel's internal refraction model; temperature and humidity
297 // are nominal placeholders. Atmospheric refraction is not modeled in this pipeline
298 // (INDI's coordinate flow stops at "apparent" / JNow; see tar.sys = APPT above).
299 spkAIR air = {0.0, 10.0, 0.5};
300
301 // Zero m_Ast before calling spkAstr so a failure cannot leave stale data behind.
302 memset(&m_Ast, 0, sizeof(m_Ast));
303 int astrStatus = spkAstr(1, utc, 0.0, &eop, &m_Obs, &air, &m_Opt, &m_Ast);
304 if (astrStatus < 0)
305 {
306 ASSDEBUGF("SPK UpdateAstrometry: spkAstr failed (status=%d), transforms will be inaccurate", astrStatus);
307 // Leave m_Ast zeroed; set eral from libnova LST so subsequent spkVtel
308 // calls see a minimally coherent ERA rather than garbage.
309 m_Ast.astrom.eral = iauAnp(lst_rad);
310 return;
311 }
312
313 // Synchronize SOFA internal "clock" (ERA) with libnova LST.
314 // This ensures consistency with the simulator's view of HA/Az.
315 // eral = ERA + Longitude = LST + EO
316 m_Ast.astrom.eral = iauAnp(lst_rad + m_Ast.eo);
317}
318
319
320bool SPKMathPlugin::ExtractObsRow(const AlignmentDatabaseEntry &point,
321 double &oblon, double &oblat,
322 double &rdem, double &pdem)
323{
324 UpdateAstrometry(point.ObservationJulianDate);
325
326 double gmst_hrs = ln_get_apparent_sidereal_time(point.ObservationJulianDate);
327 double lst_hrs = range24(gmst_hrs + RAD_TO_HOURS(m_Obs.slon));
328 double obslon_ha = HOURS_TO_RAD(get_local_hour_angle(lst_hrs, point.RightAscension));
329 double obslat_ = DEG_TO_RAD(point.Declination);
330
331 double roll, pitch;
332 DirectionVectorToRollPitch(point.TelescopeDirection, roll, pitch);
333
334 if (m_Obs.mount == ALTAZ)
335 {
336 // SPK altaz roll convention: roll = pi - Az_NE (vtel.c Note 7).
337 // Bfun('A') expects rdem in SOFA convention (0=N, East+), so recover
338 // the SOFA azimuth via pi - roll.
339 double az_cel, el_cel;
340 iauHd2ae(obslon_ha, obslat_, m_Obs.slat, &az_cel, &el_cel);
341 oblon = az_cel;
342 oblat = el_cel;
343 rdem = iauAnp(M_PI - roll);
344 }
345 else
346 {
347 oblon = obslon_ha;
348 oblat = obslat_;
349 rdem = -roll;
350 }
351 pdem = pitch;
352 ASSDEBUGF("SPK ExtractObsRow: RA=%.4fh Dec=%.4fdeg oblon=%.4fdeg oblat=%.4fdeg rdem=%.4fdeg pdem=%.4fdeg residual=(%.1f\" %.1f\")",
353 point.RightAscension, point.Declination,
354 RAD_TO_DEG(oblon), RAD_TO_DEG(oblat),
355 RAD_TO_DEG(rdem), RAD_TO_DEG(pdem),
356 RAD_TO_DEG(iauAnpm(oblon - rdem)) * cos(oblat) * 3600.0,
357 RAD_TO_DEG(iauAnpm(oblat - pdem)) * 3600.0);
358 return true;
359}
360
361bool SPKMathPlugin::AccumulateObsRow(double oblon, double oblat,
362 double rdem, double pdem)
363{
364 // Bfun is evaluated at the celestial (observed) coordinates, matching
365 // Pmfit's accumulation pass. xi/eta are the residuals with pm=0
366 // (encoder demand = rdem/pdem), which is Pmfit iteration-0.
367 double bf[12]; // 2 * 6 terms
368 char mountChar = (m_Obs.mount == ALTAZ) ? 'A' : 'E';
369 if (Bfun(6, m_Obs.slat, mountChar, oblon, oblat, bf) != 0)
370 return false;
371
372 double xi = iauAnpm(oblon - rdem) * cos(oblat);
373 double eta = iauAnpm(oblat - pdem);
374
375 for (int i = 0; i < 6; i++)
376 {
377 double bfi0 = bf[i*2 + 0];
378 double bfi1 = bf[i*2 + 1];
379 m_NE_v[i] += bfi0 * xi + bfi1 * eta;
380 for (int j = 0; j < 6; j++)
381 m_NE_A[i*6 + j] += bfi0 * bf[j*2 + 0] + bfi1 * bf[j*2 + 1];
382 }
383 return true;
384}
385
386bool SPKMathPlugin::SolveNormalEquations(double pmv[6])
387{
388 // Copy both arrays: Simeqn overwrites in-place, but we need to keep the
389 // accumulators intact for future incremental additions.
390 double Acopy[36];
391 double vcopy[6];
392 memcpy(Acopy, m_NE_A, sizeof(m_NE_A));
393 memcpy(vcopy, m_NE_v, sizeof(m_NE_v));
394
395 if (Simeqn(6, Acopy, vcopy) != 0)
396 return false;
397
398 memcpy(pmv, vcopy, 6 * sizeof(double));
399 return true;
400}
401
402std::vector<double> SPKMathPlugin::BuildObservationData(const InMemoryDatabase::AlignmentDatabaseType &syncPoints, int &outTermCount)
403{
404 // Pmfit term order: IH, ID, ME, MA, CH, TF (equatorial)
405 // IA, IE, AN, AW, CA, TF (altazimuth)
406 // Polar/axis-tilt terms come before collimation so that a 4-term fit on
407 // 3 points (6 measurements, 4 unknowns) is well-conditioned.
408 outTermCount = 6;
409 if (syncPoints.size() < 3) outTermCount = 2; // 1-2 pts: IH, ID
410 else if (syncPoints.size() < 5) outTermCount = 4; // 3-4 pts: IH, ID, ME, MA
411 else if (syncPoints.size() < 6) outTermCount = 5; // 5 pts: IH, ID, ME, MA, CH
412 // 6+ pts: full model
413
414 std::vector<double> obsData;
415 for (const auto &point : syncPoints)
416 {
417 UpdateAstrometry(point.ObservationJulianDate);
418
419 double gmst_hrs = ln_get_apparent_sidereal_time(point.ObservationJulianDate);
420 double lst_hrs = range24(gmst_hrs + RAD_TO_HOURS(m_Obs.slon));
421
422 double obslon_ha = HOURS_TO_RAD(get_local_hour_angle(lst_hrs, point.RightAscension));
423 double obslat = DEG_TO_RAD(point.Declination);
424
425 double roll, pitch;
426 DirectionVectorToRollPitch(point.TelescopeDirection, roll, pitch);
427
428 if (m_Obs.mount == ALTAZ)
429 {
430 double az_cel, el_cel;
431 iauHd2ae(obslon_ha, obslat, m_Obs.slat, &az_cel, &el_cel);
432 obsData.push_back(az_cel);
433 obsData.push_back(el_cel);
434 // SPK altaz roll = pi - Az_NE, so rdem (SOFA Az) = pi - roll.
435 obsData.push_back(iauAnp(M_PI - roll));
436 }
437 else
438 {
439 obsData.push_back(obslon_ha);
440 obsData.push_back(obslat);
441 obsData.push_back(-roll); // roll = -HA
442 }
443 obsData.push_back(pitch);
444 }
445 return obsData;
446}
447
448void SPKMathPlugin::ParsePmfitCoefficients(const double pmv[6], int terms)
449{
450 memset(&m_PM, 0, sizeof(m_PM));
451
452 if (m_Obs.mount == ALTAZ)
453 {
454 // Pmfit order: IA, IE, AN, AW, CA, TF
455 // Wallace (2002) Note 4 (altaz): IA=bf[0], IB=bf[1], AN=bf[2], AW=bf[3], CA=bf[4], VD=bf[5]
456 if (terms >= 1) m_PM.pia = pmv[0];
457 if (terms >= 2) m_PM.pib = pmv[1];
458 if (terms >= 3) m_PM.pan = pmv[2];
459 if (terms >= 4) m_PM.paw = pmv[3];
460 if (terms >= 5) m_PM.pca = pmv[4];
461 if (terms >= 6) m_PM.pvd = pmv[5];
462 }
463 else // EQUATORIAL
464 {
465 // Pmfit order: IH, ID, ME, MA, CH, TF
466 // Wallace (2002) Note 4 (equat): IA=-bf[0], IB=bf[1], AW=-bf[3], AN=bf[2], CA=-bf[4], VD=bf[5]
467 if (terms >= 1) m_PM.pia = -pmv[0];
468 if (terms >= 2) m_PM.pib = pmv[1];
469 if (terms >= 3) m_PM.paw = -pmv[3];
470 if (terms >= 4) m_PM.pan = pmv[2];
471 if (terms >= 5) m_PM.pca = -pmv[4];
472 if (terms >= 6) m_PM.pvd = pmv[5];
473 }
474}
475
476TelescopeDirectionVector SPKMathPlugin::RollPitchToDirectionVector(double roll, double pitch)
477{
478 if (m_Obs.mount == ALTAZ)
479 {
480 // SPK altaz roll = pi - Az_NE (vtel.c Note 7), so Az_NE = pi - roll.
482 hor.azimuth = RAD_TO_DEG(iauAnp(M_PI - roll));
483 hor.altitude = RAD_TO_DEG(pitch);
485 }
486 else // EQ: roll = -HA, convert to RA = LST - HA = LST + roll
487 {
489 eq.rightascension = range24(RAD_TO_HOURS(iauAnp(m_LST_rad + roll)));
490 eq.declination = RAD_TO_DEG(pitch);
492 }
493}
494
495void SPKMathPlugin::DirectionVectorToRollPitch(const TelescopeDirectionVector &v, double &roll, double &pitch)
496{
497 if (m_Obs.mount == ALTAZ)
498 {
499 // SPK altaz roll = pi - Az_NE (vtel.c Note 7).
502 roll = iauAnp(M_PI - DEG_TO_RAD(hor.azimuth));
503 pitch = DEG_TO_RAD(hor.altitude);
504 }
505 else // EQ: decode RA-based TDV, convert RA to HA, roll = -HA
506 {
509 double ha_rad = iauAnpm(m_LST_rad - HOURS_TO_RAD(eq.rightascension));
510 roll = -ha_rad;
511 pitch = DEG_TO_RAD(eq.declination);
512 }
513}
514
515} // namespace AlignmentSubsystem
516} // namespace INDI
#define ASSDEBUG(msg)
#define ASSDEBUGF(msg,...)
int spkAstr(int jfull, spkUTC utc, double dt, spkEOP *eop, spkOBS *obs, spkAIR *air, spkOPT *opt, spkAST *ast)
Definition astr.c:4
This class provides the driver side API to the in memory alignment database.
std::vector< AlignmentDatabaseEntry > AlignmentDatabaseType
AlignmentDatabaseType & GetAlignmentDatabase()
Get a reference to the in memory database.
bool GetDatabaseReferencePosition(IGeographicCoordinates &Position)
Get the database reference position.
virtual bool Initialise(InMemoryDatabase *pInMemoryDatabase)
Initialise or re-initialise the math plugin. Re-reading the in memory database as necessary.
MountAlignment_t ApproximateMountAlignment
Describe the approximate alignment of the mount. This information is normally used in a one star alig...
Definition MathPlugin.h:128
virtual bool TransformTelescopeToCelestialJD(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianDate) override
Get the true celestial coordinates for the supplied telescope pointing direction.
virtual bool TransformCelestialToTelescopeJD(double RightAscension, double Declination, double JulianDate, TelescopeDirectionVector &ApparentTelescopeDirectionVector) override
Get the alignment corrected telescope pointing direction for the supplied celestial coordinates.
virtual bool TransformTelescopeToCelestial(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianOffset=0) override
Get the true celestial coordinates for the supplied telescope pointing direction.
virtual bool TransformCelestialToTelescope(const double RightAscension, const double Declination, double JulianOffset, TelescopeDirectionVector &ApparentTelescopeDirectionVector) override
Get the alignment corrected telescope pointing direction for the supplied celestial coordinates.
virtual bool Initialise(InMemoryDatabase *pInMemoryDatabase) override
Initialise or re-initialise the math plugin. Re-reading the in memory database as necessary.
const TelescopeDirectionVector TelescopeDirectionVectorFromAltitudeAzimuth(INDI::IHorizontalCoordinates HorizontalCoordinates)
Calculates a normalised direction vector from the supplied altitude and azimuth.
void EquatorialCoordinatesFromTelescopeDirectionVector(const TelescopeDirectionVector TelescopeDirectionVector, INDI::IEquatorialCoordinates &EquatorialCoordinates)
Calculates equatorial coordinates from the supplied telescope direction vector and declination.
const TelescopeDirectionVector TelescopeDirectionVectorFromEquatorialCoordinates(INDI::IEquatorialCoordinates EquatorialCoordinates)
Calculates a telescope direction vector from the supplied equatorial coordinates.
void AltitudeAzimuthFromTelescopeDirectionVector(const TelescopeDirectionVector TelescopeDirectionVector, INDI::IHorizontalCoordinates &HorizontalCoordinates)
Calculates an altitude and azimuth from the supplied normalised direction vector and declination.
#define M_PI
Definition dsp.h:52
double range24(double r)
range24 Limits a number to be between 0-24 range.
Definition indicom.c:1311
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition indicom.c:1379
Implementations for common driver routines.
#define RAD_TO_DEG(r)
Definition indimacros.h:131
#define DEG_TO_RAD(d)
Definition indimacros.h:128
#define RAD_TO_HOURS(r)
Definition indimacros.h:125
#define HOURS_TO_RAD(h)
Definition indimacros.h:122
INumber eq[]
void Destroy(DummyMathPlugin *pPlugin)
DummyMathPlugin * Create()
Namespace to encapsulate INDI client, drivers, and mediator classes.
int Simeqn(int, double *, double *)
Definition pmfit.c:461
int Pmfit(double phi, char mount, int n, double *obs, int nt, double *pm, double *sigmas, double *skysig)
Definition pmfit.c:10
int Bfun(int, double, char, double, double, double *)
Definition pmfit.c:267
#define iauHd2ae
Definition sofa.h:55
#define iauAnpm
Definition sofa.h:39
#define iauAnp
Definition sofa.h:38
@ EQUAT
Definition spk.h:41
@ ALTAZ
Definition spk.h:42
@ AXES
Definition spk.h:52
@ TARG
Definition spk.h:53
@ APPT
Definition spk.h:47
int spkVtel(spkVTS isoln, spkOBS *obs, spkOPT *opt, spkPM *pm, spkAST *ast, spkAX3 *ax3, spkTAR *tar, spkPO *po, double *tara, double *tare, double *tarr, double *tarp, double soln[5])
Definition vtel.c:33
Holds a normalised direction vector (direction cosines)
Definition Common.h:69
Definition spk.h:87
double eo
Definition spk.h:139
iauASTROM astrom
Definition spk.h:138
Definition spk.h:119
double b
Definition spk.h:121
double a
Definition spk.h:120
double r
Definition spk.h:122
Definition spk.h:71
double slat
Definition spk.h:80
double slon
Definition spk.h:79
spkMOUNT mount
Definition spk.h:82
double sh
Definition spk.h:81
double wl
Definition spk.h:96
double fl
Definition spk.h:95
double pib
Definition spk.h:102
double paw
Definition spk.h:106
double pca
Definition spk.h:104
double pia
Definition spk.h:101
double pvd
Definition spk.h:103
double pan
Definition spk.h:107
Definition spk.h:143
spkSYS sys
Definition spk.h:144
double a
Definition spk.h:145
double b
Definition spk.h:146
Definition spk.h:126
int mi
Definition spk.h:131
int mo
Definition spk.h:128
double sec
Definition spk.h:132
int id
Definition spk.h:129
int iy
Definition spk.h:127
int ih
Definition spk.h:130