#include #include #include #include #include #include #include "qfits.h" #include #include #define NMAX 8192 #define LIM 128 #define D2R M_PI/180.0 #define R2D 180.0/M_PI struct image { int naxis, naxis1, naxis2, nframes; float *zavg, *zstd, *zmax, *znum; double ra0, de0; float x0, y0; float a[2], b[2]; double mjd; float *dt; }; struct transformation { double mjd; double ra0, de0; float a[3], b[3]; float x0, y0; float xrms, yrms, rms; }; struct star { double ra, de; float pmra, pmde; float mag; }; struct catalog { int n; float x[NMAX], y[NMAX], imag[NMAX], fm[NMAX], fb[NMAX], bg[NMAX]; double ra[NMAX], de[NMAX], vmag[NMAX]; double rx[NMAX], ry[NMAX]; float xres[NMAX], yres[NMAX], res[NMAX]; int usage[NMAX]; }; struct image read_fits (char *filename); void forward (double ra0, double de0, double ra, double de, double *x, double *y); void reverse (double ra0, double de0, double x, double y, double *ra, double *de); double gmst (double mjd); double modulo (double x, double y); int fgetline (FILE * file, char *s, int lim); struct catalog match_catalogs (char *pixcat, char *astcat, struct transformation t, struct image img, float rmax, float mmin); void plot_astrometric_catalog (struct transformation t, struct image img, float mmin); void plot_pixel_catalog (char *filename); void lfit2d (float *x, float *y, float *z, int n, float *a); void add_fits_keywords (struct transformation t, char *filename); void modify_fits_keywords (struct transformation t, char *filename); void precess (double mjd0, double ra0, double de0, double mjd, double *ra, double *de); void plot_image (struct image img, struct transformation t, struct catalog c, char *filename, float mmin) { int i; float tr[] = { -0.5, 1.0, 0.0, -0.5, 0.0, 1.0 }; float heat_l[] = { 0.0, 0.2, 0.4, 0.6, 1.0 }; float heat_r[] = { 0.0, 0.5, 1.0, 1.0, 1.0 }; float heat_g[] = { 0.0, 0.0, 0.5, 1.0, 1.0 }; float heat_b[] = { 0.0, 0.0, 0.0, 0.3, 1.0 }; float zmin, zmax, zavg, zstd; for (i = 0, zavg = 0.0; i < img.naxis1 * img.naxis2; i++) zavg += img.zavg[i]; zavg /= (float) img.naxis1 * img.naxis2; for (i = 0, zstd = 0.0; i < img.naxis1 * img.naxis2; i++) zstd += pow (img.zavg[i] - zavg, 2); zstd = sqrt (zstd / (float) (img.naxis1 * img.naxis2)); zmin = zavg - 2 * zstd; zmax = zavg + 6 * zstd; cpgopen ("1/xs"); cpgwnad (0.0, img.naxis1, 0.0, img.naxis2); cpgctab (heat_l, heat_r, heat_g, heat_b, 5, 1.0, 0.5); cpgimag (img.zavg, img.naxis1, img.naxis2, 1, img.naxis1, 1, img.naxis2, zmin, zmax, tr); cpgbox ("BCTSNI", 0., 0, "BCTSNI", 0., 0); cpgsci (3); plot_pixel_catalog (filename); cpgsci (4); plot_astrometric_catalog (t, img, mmin); cpgsci (2); for (i = 0; i < c.n; i++) cpgpt1 (c.x[i] + t.x0, c.y[i] + t.y0, 24); cpgend (); return; } void usage (float mmin, float rmin) { printf ("addwcs: Add/fit World Coordinate System to a FITS file\n\n"); printf ("-f : FITS file to add/fit WCS to [required]\n"); printf ("-r : FITS file with reference WCS [required]\n"); printf ("-m : Magnitude cut-off in Tycho-2 catalog [optional; default %.1f]\n", mmin); printf ("-R : Radius cut-off for matching [optional; default %.1f pix]\n", rmin); printf ("-p Plot image and selected stars [optional]\n"); printf ("-a Add WCS keywords to input file (instead of modify) [optional]\n"); printf ("-t Track on a fixed RA/Dec (correct for field rotation)\n"); printf ("-h Print this help\n"); return; } // Get reference transformation struct transformation reference (char *filename) { struct transformation t; t.mjd = atof (qfits_query_hdr (filename, "MJD-OBS")); t.ra0 = atof (qfits_query_hdr (filename, "CRVAL1")); t.de0 = atof (qfits_query_hdr (filename, "CRVAL2")); t.x0 = atof (qfits_query_hdr (filename, "CRPIX1")); t.y0 = atof (qfits_query_hdr (filename, "CRPIX2")); t.a[0] = 0.0; t.a[1] = 3600.0 * atof (qfits_query_hdr (filename, "CD1_1")); t.a[2] = 3600.0 * atof (qfits_query_hdr (filename, "CD1_2")); t.b[0] = 0.0; t.b[1] = 3600.0 * atof (qfits_query_hdr (filename, "CD2_1")); t.b[2] = 3600.0 * atof (qfits_query_hdr (filename, "CD2_2")); return t; } void rotate (float theta, float *x, float *y) { float ct, st; float x0, y0; ct = cos (theta * D2R); st = sin (theta * D2R); x0 = *x; y0 = *y; *x = ct * x0 - st * y0; *y = st * x0 + ct * y0; return; } int main (int argc, char *argv[]) { int i, j, k, l, m; struct transformation t; struct image img; char *fitsfile = NULL, *reffile = NULL, catfile[128], calfile[128]; FILE *outfile; struct catalog c; float mmin = 10.0, rmin = 10.0; double mjd0 = 51544.5, ra0, de0, ra1, de1; float q0, q1; float rmsmin; float x[NMAX], y[NMAX], rx[NMAX], ry[NMAX]; int arg = 0, plot = 0, add = 0, track = 0; char *env, starfile[128]; // Environment variables env = getenv ("ST_DATADIR"); sprintf (starfile, "%s/data/tycho2.dat", env); // Decode options if (argc > 1) { while ((arg = getopt (argc, argv, "f:r:m:R:hpnta")) != -1) { switch (arg) { case 'f': fitsfile = optarg; break; case 'r': reffile = optarg; break; case 'm': mmin = atof (optarg); break; case 't': track = 1; break; case 'R': rmin = atof (optarg); break; case 'p': plot = 1; break; case 'a': add = 1; break; case 'h': usage (mmin, rmin); return 0; default: usage (mmin, rmin); return 0; } } } else { usage (mmin, rmin); return 0; } // Check if minimum input is provided if (fitsfile == NULL || reffile == NULL) { usage (mmin, rmin); return 0; } // Check this is indeed a FITS file if (is_fits_file (fitsfile) != 1) { printf ("%s is not a FITS file\n", fitsfile); return -1; } // Check this is indeed a FITS file if (is_fits_file (reffile) != 1) { printf ("%s is not a FITS file\n", reffile); return -1; } // Read fits file img = read_fits (fitsfile); sprintf (catfile, "%s.cat", fitsfile); sprintf (calfile, "%s.cal", fitsfile); // Read reference transformation t = reference (reffile); // Correct astrometry for fixed or tracked setup if (track == 0) { precess (mjd0, t.ra0, t.de0, t.mjd, &ra1, &de1); ra1 = modulo (ra1 + gmst (img.mjd) - gmst (t.mjd), 360.0); precess (img.mjd, ra1, de1, mjd0, &t.ra0, &t.de0); } // Match catalog c = match_catalogs (catfile, starfile, t, img, rmin, mmin); // Plot if (plot == 1) plot_image (img, t, c, catfile, mmin); // Do fit if (c.n > 10) { for (l = 0; l < 10; l++) { for (j = 0; j < 5; j++) { // Transform for (i = 0; i < c.n; i++) forward (t.ra0, t.de0, c.ra[i], c.de[i], &c.rx[i], &c.ry[i]); // Select for (i = 0, k = 0; i < c.n; i++) { if (c.usage[i] == 1) { x[k] = c.x[i]; y[k] = c.y[i]; rx[k] = (float) c.rx[i]; ry[k] = (float) c.ry[i]; k++; } } // Fit lfit2d (x, y, rx, k, t.a); lfit2d (x, y, ry, k, t.b); // Move reference point reverse (t.ra0, t.de0, t.a[0], t.b[0], &ra0, &de0); t.ra0 = ra0; t.de0 = de0; } // Compute and plot residuals for (i = 0, t.xrms = 0.0, t.yrms = 0.0, m = 0; i < c.n; i++) { if (c.usage[i] == 1) { c.xres[i] = c.rx[i] - (t.a[0] + t.a[1] * c.x[i] + t.a[2] * c.y[i]); c.yres[i] = c.ry[i] - (t.b[0] + t.b[1] * c.x[i] + t.b[2] * c.y[i]); c.res[i] = sqrt (c.xres[i] * c.xres[i] + c.yres[i] * c.yres[i]); t.xrms += c.xres[i] * c.xres[i]; t.yrms += c.yres[i] * c.yres[i]; t.rms += c.xres[i] * c.xres[i] + c.yres[i] * c.yres[i]; m++; } } t.xrms = sqrt (t.xrms / (float) m); t.yrms = sqrt (t.yrms / (float) m); t.rms = sqrt (t.rms / (float) m); // Deselect outliers for (i = 0; i < c.n; i++) { if (c.res[i] > 2 * t.rms) c.usage[i] = 0; } } } else { t.xrms = 0.0; t.yrms = 0.0; t.rms = 0.0; } // Print results outfile = fopen (calfile, "w"); for (i = 0; i < c.n; i++) if (c.usage[i] == 1) fprintf (outfile, "%10.4f %10.4f %10.6f %10.6f %8.3f %8.3f %8.3f %8.3f %8.3f\n", c.x[i], c.y[i], c.ra[i], c.de[i], c.vmag[i], c.imag[i], c.fb[i], c.fm[i], c.bg[i]); fclose (outfile); printf ("%s %8.4lf %8.4lf ", fitsfile, t.ra0, t.de0); printf ("%3d/%3d %6.1f %6.1f %6.1f\n", m, c.n, t.xrms, t.yrms, t.rms); // Add keywords if (add == 1) add_fits_keywords (t, fitsfile); else modify_fits_keywords (t, fitsfile); return 0; } // Read fits image struct image read_fits (char *filename) { int i, j, k, l, m; qfitsloader ql; char key[FITS_LINESZ + 1]; char val[FITS_LINESZ + 1]; struct image img; // Image size img.naxis = atoi (qfits_query_hdr (filename, "NAXIS")); img.naxis1 = atoi (qfits_query_hdr (filename, "NAXIS1")); img.naxis2 = atoi (qfits_query_hdr (filename, "NAXIS2")); // MJD img.mjd = (double) atof (qfits_query_hdr (filename, "MJD-OBS")); // Set parameters ql.xtnum = 0; ql.ptype = PTYPE_FLOAT; ql.filename = filename; if (img.naxis == 3) { // Number of frames img.nframes = atoi (qfits_query_hdr (filename, "NFRAMES")); // Timestamps img.dt = (float *) malloc (sizeof (float) * img.nframes); for (i = 0; i < img.nframes; i++) { sprintf (key, "DT%04d", i); strcpy (val, qfits_query_hdr (filename, key)); sscanf (val + 1, "%f", &img.dt[i]); // img.dt[i]=atof(qfits_query_hdr(filename,key)); } // Allocate image memory img.zavg = (float *) malloc (sizeof (float) * img.naxis1 * img.naxis2); img.zstd = (float *) malloc (sizeof (float) * img.naxis1 * img.naxis2); img.zmax = (float *) malloc (sizeof (float) * img.naxis1 * img.naxis2); img.znum = (float *) malloc (sizeof (float) * img.naxis1 * img.naxis2); // Loop over planes for (k = 0; k < 4; k++) { ql.pnum = k;; // Initialize load if (qfitsloader_init (&ql) != 0) printf ("Error initializing data loading\n"); // Test load if (qfits_loadpix (&ql) != 0) printf ("Error loading actual data\n"); // Fill z array for (i = 0, l = 0; i < img.naxis1; i++) { for (j = 0; j < img.naxis2; j++) { if (k == 0) img.zavg[l] = ql.fbuf[l]; if (k == 1) img.zstd[l] = ql.fbuf[l]; if (k == 2) img.zmax[l] = ql.fbuf[l]; if (k == 3) img.znum[l] = ql.fbuf[l]; l++; } } } } else { // Allocate image memory img.zavg = (float *) malloc (sizeof (float) * img.naxis1 * img.naxis2); ql.pnum = 0; // Initialize load if (qfitsloader_init (&ql) != 0) printf ("Error initializing data loading\n"); // Test load if (qfits_loadpix (&ql) != 0) printf ("Error loading actual data\n"); // Fill z array for (i = 0, l = 0; i < img.naxis1; i++) { for (j = 0; j < img.naxis2; j++) { img.zavg[l] = ql.fbuf[l]; l++; } } } return img; } // Greenwich Mean Sidereal Time double gmst (double mjd) { double t, gmst; t = (mjd - 51544.5) / 36525.0; gmst = modulo (280.46061837 + 360.98564736629 * (mjd - 51544.5) + t * t * (0.000387933 - t / 38710000), 360.0); return gmst; } // Return x modulo y [0,y) double modulo (double x, double y) { x = fmod (x, y); if (x < 0.0) x += y; return x; } // Read a line of maximum length int lim from file FILE into string s int fgetline (FILE * file, char *s, int lim) { int c, i = 0; while (--lim > 0 && (c = fgetc (file)) != EOF && c != '\n') s[i++] = c; if (c == '\n') s[i++] = c; s[i] = '\0'; return i; } // Match catalogs struct catalog match_catalogs (char *pixcat, char *astcat, struct transformation t, struct image img, float rmax, float mmin) { int i = 0, imin, j, k, np; FILE *file; char line[LIM]; struct star s; double rx, ry, d, dx, dy; int usage[NMAX]; float xp[NMAX], yp[NMAX], mp[NMAX], x, y, fb[NMAX], fm[NMAX], bg[NMAX]; struct catalog c; float r, rmin; // Read pixel catalog file = fopen (pixcat, "r"); if (file == NULL) { printf ("pixel catalog not found\n"); exit (-1); } while (fgetline (file, line, LIM) > 0) { if (strstr (line, "#") != NULL) continue; sscanf (line, "%f %f %f %f %f %f", &xp[i], &yp[i], &mp[i], &fb[i], &fm[i], &bg[i]); usage[i] = 1; i++; } fclose (file); np = i; // Denominator d = t.a[1] * t.b[2] - t.a[2] * t.b[1]; // Read astrometric catalog file = fopen (astcat, "rb"); if (file == NULL) { printf ("astrometric catalog not found\n"); exit (-1); } j = 0; while (!feof (file)) { fread (&s, sizeof (struct star), 1, file); if (s.mag > mmin) continue; r = acos (sin (t.de0 * D2R) * sin (s.de * D2R) + cos (t.de0 * D2R) * cos (s.de * D2R) * cos ((t.ra0 - s.ra) * D2R)) * R2D; if (r > 90.0) continue; forward (t.ra0, t.de0, s.ra, s.de, &rx, &ry); dx = rx - t.a[0]; dy = ry - t.b[0]; x = (t.b[2] * dx - t.a[2] * dy) / d + t.x0; y = (t.a[1] * dy - t.b[1] * dx) / d + t.y0; // On image if (x > 0.0 && x < img.naxis1 && y > 0.0 && y < img.naxis2) { // Loop over pixel catalog for (i = 0; i < np; i++) { r = sqrt (pow (xp[i] - x, 2) + pow (yp[i] - y, 2)); if (i == 0 || r < rmin) { rmin = r; imin = i; } } // Select if (rmin < rmax && usage[imin] == 1) { c.x[j] = xp[imin] - t.x0; c.y[j] = yp[imin] - t.y0; c.imag[j] = mp[imin]; c.fb[j] = fb[imin]; c.fm[j] = fm[imin]; c.bg[j] = bg[imin]; c.ra[j] = s.ra; c.de[j] = s.de; c.vmag[j] = s.mag; c.usage[j] = 1; usage[imin] = 0; j++; } } } fclose (file); c.n = j; return c; } // Plot astrometric catalog void plot_astrometric_catalog (struct transformation t, struct image img, float mmin) { int i = 0; FILE *file; struct star s; double rx, ry, d, r; double ra, de; float x, y; char *env, starfile[128]; // Environment variables env = getenv ("ST_DATADIR"); sprintf (starfile, "%s/data/tycho2.dat", env); d = t.a[1] * t.b[2] - t.a[2] * t.b[1]; file = fopen (starfile, "rb"); while (!feof (file)) { fread (&s, sizeof (struct star), 1, file); if (s.mag > mmin) continue; r = acos (sin (t.de0 * D2R) * sin (s.de * D2R) + cos (t.de0 * D2R) * cos (s.de * D2R) * cos ((t.ra0 - s.ra) * D2R)) * R2D; if (r > 90.0) continue; forward (t.ra0, t.de0, s.ra, s.de, &rx, &ry); x = (t.b[2] * rx - t.a[2] * ry) / d + t.x0; y = (t.a[1] * ry - t.b[1] * rx) / d + t.y0; if (x > 0.0 && x < img.naxis1 && y > 0.0 && y < img.naxis2) cpgpt1 (x, y, 24); } fclose (file); return; } // Plot pixel catalog void plot_pixel_catalog (char *filename) { int i = 0; FILE *file; char line[LIM]; float x, y, mag; // Read catalog file = fopen (filename, "r"); while (fgetline (file, line, LIM) > 0) { if (strstr (line, "#") != NULL) continue; sscanf (line, "%f %f %f", &x, &y, &mag); cpgpt1 (x, y, 4); i++; } fclose (file); return; } // Linear 2D fit void lfit2d (float *x, float *y, float *z, int n, float *a) { int i, j, m; double chisq; gsl_matrix *X, *cov; gsl_vector *yy, *w, *c; X = gsl_matrix_alloc (n, 3); yy = gsl_vector_alloc (n); w = gsl_vector_alloc (n); c = gsl_vector_alloc (3); cov = gsl_matrix_alloc (3, 3); // Fill matrices for (i = 0; i < n; i++) { gsl_matrix_set (X, i, 0, 1.0); gsl_matrix_set (X, i, 1, x[i]); gsl_matrix_set (X, i, 2, y[i]); gsl_vector_set (yy, i, z[i]); gsl_vector_set (w, i, 1.0); } // Do fit gsl_multifit_linear_workspace *work = gsl_multifit_linear_alloc (n, 3); gsl_multifit_wlinear (X, w, yy, c, cov, &chisq, work); gsl_multifit_linear_free (work); // Save parameters for (i = 0; i < 3; i++) a[i] = gsl_vector_get (c, (i)); gsl_matrix_free (X); gsl_vector_free (yy); gsl_vector_free (w); gsl_vector_free (c); gsl_matrix_free (cov); return; } // Add FITS keywords void add_fits_keywords (struct transformation t, char *filename) { int i, j, k, l, m; int naxis1, naxis2, naxis3; qfits_header *qh; qfitsdumper qd; qfitsloader ql; char key[FITS_LINESZ + 1]; char val[FITS_LINESZ + 1]; char com[FITS_LINESZ + 1]; char lin[FITS_LINESZ + 1]; FILE *file; float *fbuf; naxis1 = atoi (qfits_query_hdr (filename, "NAXIS1")); naxis2 = atoi (qfits_query_hdr (filename, "NAXIS2")); naxis3 = atoi (qfits_query_hdr (filename, "NAXIS3")); fbuf = malloc (sizeof (float) * naxis1 * naxis2 * naxis3); // Read header qh = qfits_header_read (filename); ql.xtnum = 0; ql.ptype = PTYPE_FLOAT; ql.filename = filename; for (k = 0, l = 0; k < naxis3; k++) { ql.pnum = k; // Initialize load if (qfitsloader_init (&ql) != 0) printf ("Error initializing data loading\n"); // Test load if (qfits_loadpix (&ql) != 0) printf ("Error loading actual data\n"); for (i = 0, m = 0; i < naxis1; i++) { for (j = 0; j < naxis2; j++) { fbuf[l] = ql.fbuf[m]; l++; m++; } } } sprintf (val, "%e", t.yrms / 3600.0); qfits_header_add_after (qh, "MJD-OBS", "CRRES2", val, " ", NULL); sprintf (val, "%e", t.xrms / 3600.0); qfits_header_add_after (qh, "MJD-OBS", "CRRES1", val, " ", NULL); qfits_header_add_after (qh, "MJD-OBS", "CUNIT2", "'deg'", " ", NULL); qfits_header_add_after (qh, "MJD-OBS", "CUNIT1", "'deg'", " ", NULL); qfits_header_add_after (qh, "MJD-OBS", "CTYPE2", "'DEC--TAN'", " ", NULL); qfits_header_add_after (qh, "MJD-OBS", "CTYPE1", "'RA---TAN'", " ", NULL); sprintf (val, "%e", t.b[2] / 3600.0); qfits_header_add_after (qh, "MJD-OBS", "CD2_2", val, " ", NULL); sprintf (val, "%e", t.b[1] / 3600.0); qfits_header_add_after (qh, "MJD-OBS", "CD2_1", val, " ", NULL); sprintf (val, "%e", t.a[2] / 3600.0); qfits_header_add_after (qh, "MJD-OBS", "CD1_2", val, " ", NULL); sprintf (val, "%e", t.a[1] / 3600.0); qfits_header_add_after (qh, "MJD-OBS", "CD1_1", val, " ", NULL); sprintf (val, "%f", t.de0); qfits_header_add_after (qh, "MJD-OBS", "CRVAL2", val, " ", NULL); sprintf (val, "%f", t.ra0); qfits_header_add_after (qh, "MJD-OBS", "CRVAL1", val, " ", NULL); sprintf (val, "%f", t.y0); qfits_header_add_after (qh, "MJD-OBS", "CRPIX2", val, " ", NULL); sprintf (val, "%f", t.x0); qfits_header_add_after (qh, "MJD-OBS", "CRPIX1", val, " ", NULL); file = fopen (filename, "w"); qfits_header_dump (qh, file); fclose (file); qfits_header_destroy (qh); qd.filename = filename; qd.npix = naxis1 * naxis2 * naxis3; qd.ptype = PTYPE_FLOAT; qd.fbuf = fbuf; qd.out_ptype = -32; qfits_pixdump (&qd); free (fbuf); return; } // Modify FITS keywords void modify_fits_keywords (struct transformation t, char *filename) { char card[FITS_LINESZ + 1]; char key[FITS_LINESZ + 1]; char val[FITS_LINESZ + 1]; char com[FITS_LINESZ + 1]; sprintf (val, "%f", t.x0); keytuple2str (card, "CRPIX1", val, ""); qfits_replace_card (filename, "CRPIX1", card); sprintf (val, "%f", t.y0); keytuple2str (card, "CRPIX2", val, ""); qfits_replace_card (filename, "CRPIX2", card); sprintf (val, "%f", t.ra0); keytuple2str (card, "CRVAL1", val, ""); qfits_replace_card (filename, "CRVAL1", card); sprintf (val, "%f", t.de0); keytuple2str (card, "CRVAL2", val, ""); qfits_replace_card (filename, "CRVAL2", card); sprintf (val, "%e", t.a[1] / 3600.0); keytuple2str (card, "CD1_1", val, ""); qfits_replace_card (filename, "CD1_1", card); sprintf (val, "%e", t.a[2] / 3600.0); keytuple2str (card, "CD1_2", val, ""); qfits_replace_card (filename, "CD1_2", card); sprintf (val, "%e", t.b[1] / 3600.0); keytuple2str (card, "CD2_1", val, ""); qfits_replace_card (filename, "CD2_1", card); sprintf (val, "%e", t.b[2] / 3600.0); keytuple2str (card, "CD2_2", val, ""); qfits_replace_card (filename, "CD2_2", card); sprintf (val, "%e", t.xrms / 3600.0); keytuple2str (card, "CRRES1", val, ""); qfits_replace_card (filename, "CRRES1", card); sprintf (val, "%e", t.yrms / 3600.0); keytuple2str (card, "CRRES2", val, ""); qfits_replace_card (filename, "CRRES2", card); return; } // Precess a celestial position void precess (double mjd0, double ra0, double de0, double mjd, double *ra, double *de) { double t0, t; double zeta, z, theta; double a, b, c; // Angles in radians ra0 *= D2R; de0 *= D2R; // Time in centuries t0 = (mjd0 - 51544.5) / 36525.0; t = (mjd - mjd0) / 36525.0; // Precession angles zeta = (2306.2181 + 1.39656 * t0 - 0.000139 * t0 * t0) * t; zeta += (0.30188 - 0.000344 * t0) * t * t + 0.017998 * t * t * t; zeta *= D2R / 3600.0; z = (2306.2181 + 1.39656 * t0 - 0.000139 * t0 * t0) * t; z += (1.09468 + 0.000066 * t0) * t * t + 0.018203 * t * t * t; z *= D2R / 3600.0; theta = (2004.3109 - 0.85330 * t0 - 0.000217 * t0 * t0) * t; theta += -(0.42665 + 0.000217 * t0) * t * t - 0.041833 * t * t * t; theta *= D2R / 3600.0; a = cos (de0) * sin (ra0 + zeta); b = cos (theta) * cos (de0) * cos (ra0 + zeta) - sin (theta) * sin (de0); c = sin (theta) * cos (de0) * cos (ra0 + zeta) + cos (theta) * sin (de0); *ra = (atan2 (a, b) + z) * R2D; *de = asin (c) * R2D; if (*ra < 360.0) *ra += 360.0; if (*ra > 360.0) *ra -= 360.0; return; }