/*============================================================================ WCSLIB 4.3 - an implementation of the FITS WCS standard. Copyright (C) 1995-2007, Mark Calabretta This file is part of WCSLIB. WCSLIB is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. WCSLIB is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with WCSLIB; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA Correspondence concerning WCSLIB may be directed to: Internet email: mcalabre@atnf.csiro.au Postal address: Dr. Mark Calabretta Australia Telescope National Facility, CSIRO PO Box 76 Epping NSW 1710 AUSTRALIA Author: Mark Calabretta, Australia Telescope National Facility http://www.atnf.csiro.au/~mcalabre/index.html $Id: HPXcvt.c,v 4.3 2007/12/27 05:50:31 cal103 Exp $ *============================================================================= * * HPXcvt reorganises HEALPix data into a 2-D FITS image. Refer to the usage * notes below. * *---------------------------------------------------------------------------*/ char usage[] = "Usage: HPXcvt [-C] [-c] [-n|-r] [-q] [-x]\n" " [ []]\n" "\n" "HPXcvt reorganises HEALPix data into a 2-D FITS image with HPX coordinate\n" "system. The input data may be stored in a FITS file as a primary image\n" "or image extension, or as a binary table extension. Both NESTED and RING\n" "pixel indices are supported. The input and output files may be omitted or\n" "specified as \"-\" to indicate stdin and stdout respectively.\n" "\n" "Options:\n" " -C Binary table column number from which to read data,\n" " default 1 (n.b. WMAP exclusion masks are in column 2).\n" "\n" " -c Specify the coordinate system to be used to label the\n" " output map if the COORDSYS keyword is absent from the input\n" " FITS header. Recognised values are g (galactic),\n" " e (ecliptic) or q (equatorial).\n" "\n" " -n|-r Assume n(ested) or r(ing) organization if the ORDERING\n" " keyword is absent from the input FITS header.\n" "\n" " -q Recentre longitude at quad(mod 4) x 90 degrees, where\n" " quad(rant) is an integer.\n" "\n" " -x Use a north-polar or south-polar layout.\n"; #include #include #include #include #include #include #include #include #define HEALPIX_NULLVAL (-1.6375e30) struct healpix { char *infile; /* Input file. */ char *outfile; /* Output file. */ int col; /* Input binary table column number. */ char crdsys; /* G(alactic), E(cliptic), or (e)Q(uatorial). */ char ordering; /* R(ing) or N(ested). */ char layout; /* Required output layout, */ /* 0: equatorial (default), */ /* 1: north, or */ /* 2: south. */ char quad; /* Recentre longitude on quadrant (modulo 4). */ int nside; /* Dimension of a base-resolution pixel. */ long npix; /* Total number of pixels in the data array. */ float *data; /* Pointer to memory allocated to hold data. */ }; int HEALPixIn(struct healpix *hpxdat); int HPXout(struct healpix *hpxdat); int NESTidx(int nside, int facet, int rotn, int row, long *healidx); int RINGidx(int nside, int facet, int rotn, int row, long *healidx); int HPXhdr(fitsfile *fptr, struct healpix *hpxdat); int main(int argc, char **argv) { int crdsys, i, layout, quad, status; struct healpix hpxdat; hpxdat.col = 1; hpxdat.crdsys = '?'; hpxdat.ordering = '?'; hpxdat.layout = 0; hpxdat.quad = 0; /* Parse options. */ for (i = 1; i < argc && argv[i][0] == '-'; i++) { if (!argv[i][1]) break; switch (argv[i][1]) { case 'C': hpxdat.col = atoi(argv[i]+2); if (hpxdat.col < 1) hpxdat.col = 1; break; case 'c': crdsys = toupper(argv[i][2]); switch (crdsys) { case 'G': case 'E': case 'Q': hpxdat.crdsys = (char)crdsys; }; break; case 'n': hpxdat.ordering = 'N'; break; case 'q': quad = atoi(argv[i]+2)%4; if (quad < 0) quad += 4; hpxdat.quad = (char)quad; break; case 'r': hpxdat.ordering = 'R'; break; case 'x': layout = toupper(argv[i][2]); switch (layout) { case 'N': hpxdat.layout = 1; break; case 'S': hpxdat.layout = 2; break; }; break; default: fprintf(stderr, "%s", usage); return 1; } } if (i < argc) { hpxdat.infile = argv[i++]; if (i < argc) { hpxdat.outfile = argv[i++]; if (i < argc) { fprintf(stderr, "%s", usage); return 1; } } else { hpxdat.outfile = "-"; } } else { hpxdat.infile = "-"; } /* Check accessibility of the input file. */ if (strcmp(hpxdat.infile, "-") && access(hpxdat.infile, R_OK) == -1) { printf("HPXcvt: Cannot access %s.\n", hpxdat.infile); return 1; } /* Get the HEALPix data as a vector. */ if ((status = HEALPixIn(&hpxdat))) { return 2; } if (hpxdat.ordering == '?') { fprintf(stderr, "WARNING: ORDERING keyword absent, assuming RING.\n"); hpxdat.ordering = 'r'; } printf("HPXcvt: Read 12 * %d^2 = %ld pixels with %s indexing.\n", hpxdat.nside, hpxdat.npix, (hpxdat.ordering == 'N') ? "nested" : "ring"); /* Map and write it out as a FITS image. */ if ((status = HPXout(&hpxdat))) return 3; if (hpxdat.data) free(hpxdat.data); return 0; } /*--------------------------------------------------------------------------*/ int HEALPixIn(struct healpix *hpxdat) { char crdsys[32], ordering[32]; int anynul, hdutype, iaxis, nfound, status; long firstpix, ipix, lastpix, naxis, *naxes = 0x0, nside = 0, repeat; float *datap, nulval; LONGLONG firstelem, irow, nelem, npix = 0, nrow = 0; fitsfile *fptr; status = 0; hpxdat->data = 0x0; /* Open the FITS file and move to the first HDU with NAXIS != 0. */ if (fits_open_data(&fptr, hpxdat->infile, READONLY, &status)) goto fitserr; /* Is this the primary HDU or an extension? */ if (fits_get_hdu_type(fptr, &hdutype, &status)) goto fitserr; if (!(hdutype == IMAGE_HDU || hdutype == BINARY_TBL)) { fprintf(stderr, "ERROR: %s does not contain HEALPix data.\n", hpxdat->infile); return 1; } /* Get the image size. */ if (fits_read_key_lng(fptr, "NAXIS", &naxis, 0x0, &status)) goto fitserr; naxes = malloc(naxis*sizeof(long)); if (fits_read_keys_lng(fptr, "NAXIS", 1, (int)naxis, naxes, &nfound, &status)) goto fitserr; if (hdutype == IMAGE_HDU) { /* Look for the first non-degenerate image axis. */ for (iaxis = 0; iaxis < nfound; iaxis++) { if (naxes[iaxis] > 1) { /* Assume for now that it is the total number of pixels. */ npix = naxes[iaxis]; break; } } } else if (hdutype == BINARY_TBL) { /* Binary tables are simpler. */ if (nfound > 1) nrow = naxes[1]; /* (Note that fits_get_coltypell() is not available in cfitsio 2.x.) */ if (fits_get_coltype(fptr, hpxdat->col, 0x0, &repeat, 0x0, &status)) { goto fitserr; } nelem = (LONGLONG)repeat; } if (!npix && !nrow) { fprintf(stderr, "ERROR: Could not determine image size.\n"); goto cleanup; } /* Number of pixels per side of each base-resolution pixel. */ if (fits_read_key_lng(fptr, "NSIDE", &nside, 0x0, &status)) { /* Some HEALPix files, e.g. SFD dust maps, don't record NSIDE. */ if (status != KEY_NO_EXIST) goto fitserr; status = 0; } /* FIRSTPIX and LASTPIX, if present, record the 0-relative pixel numbers of * the first and last pixels stored in the data. */ firstpix = -1; if (fits_read_key_lng(fptr, "FIRSTPIX", &firstpix, 0x0, &status)) { if (status != KEY_NO_EXIST) goto fitserr; status = 0; } lastpix = -1; if (fits_read_key_lng(fptr, "LASTPIX", &lastpix, 0x0, &status)) { if (status != KEY_NO_EXIST) goto fitserr; status = 0; } if (!nside) { /* Deduce NSIDE. */ if (lastpix >= 0) { /* If LASTPIX is present without NSIDE we can only assume it's npix. */ nside = (int)(sqrt((double)((lastpix+1) / 12)) + 0.5); } else if (npix) { nside = (int)(sqrt((double)(npix / 12)) + 0.5); } else if (nrow) { nside = (int)(sqrt((double)((nrow * nelem) / 12)) + 0.5); } } hpxdat->nside = (int)nside; hpxdat->npix = 12*nside*nside; /* Ensure that FIRSTPIX and LASTPIX are set. */ if (firstpix < 0) firstpix = 0; if (lastpix < 0) lastpix = hpxdat->npix - 1; /* Any sign of a coordinate system identifier? */ if (fits_read_key_str(fptr, "COORDSYS", crdsys, 0x0, &status)) { if (status != KEY_NO_EXIST) goto fitserr; status = 0; } else if (crdsys[0] == 'G') { hpxdat->crdsys = 'G'; } else if (crdsys[0] == 'E') { hpxdat->crdsys = 'E'; } else if (crdsys[0] == 'C') { /* ("celestial") */ hpxdat->crdsys = 'Q'; } /* Nested or ring ordering? */ if (fits_read_key_str(fptr, "ORDERING", ordering, 0x0, &status)) { /* Some HEALPix files, e.g. SFD dust maps, don't record ORDERING. */ if (status != KEY_NO_EXIST) goto fitserr; status = 0; } else if (strcmp(ordering, "NESTED") == 0) { hpxdat->ordering = 'N'; } else if (strcmp(ordering, "RING") == 0) { hpxdat->ordering = 'R'; } else { fprintf(stderr, "WARNING: Invalid ORDERING keyword: %s.\n", ordering); } /* Allocate memory and read the data. */ if ((hpxdat->data = malloc((hpxdat->npix)*sizeof(float))) == NULL) { perror("HPXcvt"); goto cleanup; } nulval = HEALPIX_NULLVAL; datap = hpxdat->data; for (ipix = 0; ipix < firstpix; ipix++) { *(datap++) = nulval; } firstelem = (LONGLONG)1; if (hdutype == IMAGE_HDU) { if (fits_read_img_flt(fptr, 0l, firstelem, npix, nulval, datap, &anynul, &status)) goto fitserr; } else if (hdutype == BINARY_TBL) { for (irow = 0; irow < nrow; irow++) { if (fits_read_col_flt(fptr, hpxdat->col, irow+1, firstelem, nelem, nulval, datap, &anynul, &status)) goto fitserr; datap += nelem; } } datap = hpxdat->data + (lastpix + 1); for (ipix = (lastpix+1); ipix < hpxdat->npix; ipix++) { *(datap++) = nulval; } /* Clean up. */ fits_close_file(fptr, &status); status = 0; return 0; fitserr: fits_report_error(stderr, status); cleanup: if (naxes) free(naxes); if (hpxdat->data) free(hpxdat->data); hpxdat->data = 0x0; return 1; } /*--------------------------------------------------------------------------*/ int HPXout(struct healpix *hpxdat) { /* Number of facets on a side of each layout. */ const int NFACET[] = {5, 4, 4}; /* Arrays that define the facet location and rotation for each recognised * layout. Bear in mind that these appear to be upside-down, i.e. the top * line contains facets numbers for the bottom row of the output image. * Facets numbered -1 are blank. */ /* Equatorial (diagonal) facet layout. */ const int FACETS[][5][5] = {{{ 6, 9, -1, -1, -1}, { 1, 5, 8, -1, -1}, {-1, 0, 4, 11, -1}, {-1, -1, 3, 7, 10}, {-1, -1, -1, 2, 6}}, /* North polar (X) facet layout. */ {{ 8, 4, 4, 11, -1}, { 5, 0, 3, 7, -1}, { 5, 1, 2, 7, -1}, { 9, 6, 6, 10, -1}, {-1, -1, -1, -1, -1}}, /* South polar (X) facet layout. */ {{ 1, 6, 6, 2, -1}, { 5, 9, 10, 7, -1}, { 5, 8, 11, 7, -1}, { 0, 4, 4, 3, -1}, {-1, -1, -1, -1, -1}}}; /* All facets of the equatorial layout are rotated by +45 degrees with * respect to the normal orientation, i.e. that with the equator running * horizontally. The rotation recorded for the polar facets is the number * of additional positive (anti-clockwise) 90 degree turns with respect to * the equatorial layout. */ /* Equatorial (diagonal), no facet rotation. */ const int FROTAT[][5][5] = {{{ 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}}, /* North polar (X) facet rotation. */ {{ 3, 3, 0, 0, 0}, { 3, 3, 0, 0, 0}, { 2, 2, 1, 1, 0}, { 2, 2, 1, 1, 0}, { 0, 0, 0, 0, 0}}, /* South polar (X) facet rotation. */ {{ 1, 1, 2, 2, 0}, { 1, 1, 2, 2, 0}, { 0, 0, 3, 3, 0}, { 0, 0, 3, 3, 0}, { 0, 0, 0, 0, 0}}}; /* Facet halving codes. 0: the facet is whole (or wholly blank), * 1: blanked bottom-right, 2: top-right, 3: top-left, 4: bottom-left. * Positive values mean that the diagonal is included, otherwise not. */ /* Equatorial (diagonal), no facet halving. */ const int FHALVE[][5][5] = {{{ 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}, { 0, 0, 0, 0, 0}}, /* North polar (X) facet halving. */ {{ 0, 1, -4, 0, 0}, {-3, 0, 0, 2, 0}, { 4, 0, 0, -1, 0}, { 0, -2, 3, 0, 0}, { 0, 0, 0, 0, 0}}, /* South polar (X) facet halving. */ {{ 0, 1, -4, 0, 0}, {-3, 0, 0, 2, 0}, { 4, 0, 0, -1, 0}, { 0, -2, 3, 0, 0}, { 0, 0, 0, 0, 0}}}; char history[72]; int facet, halve, i1, i2, ifacet, j, jfacet, layout, nfacet, nside, rotn, status; long *healidx = 0x0, *healp, naxes[2]; float nulval = HEALPIX_NULLVAL, *row = 0x0, *rowp; LONGLONG fpixel, group, nelem; fitsfile *fptr; nside = hpxdat->nside; layout = hpxdat->layout; nfacet = NFACET[layout]; /* Create the output FITS file. */ status = 0; naxes[0] = nfacet * nside; naxes[1] = naxes[0]; if (fits_create_file(&fptr, hpxdat->outfile, &status)) goto fitserr; if (fits_create_img(fptr, FLOAT_IMG, 2, naxes, &status)) goto fitserr; /* Write WCS keyrecords. */ if ((status = HPXhdr(fptr, hpxdat))) goto fitserr; /* Allocate arrays. */ if ((healidx = malloc(nside * sizeof(long))) == NULL || (row = malloc(nside * sizeof(float))) == NULL) { perror("HPXcvt"); goto cleanup; } /* Loop vertically facet-by-facet. */ fpixel = 1; group = 0; nelem = nside; for (jfacet = 0; jfacet < nfacet; jfacet++) { /* Loop row-by-row. */ for (j = 0; j < nside; j++) { /* Loop horizontally facet-by-facet. */ for (ifacet = 0; ifacet < nfacet; ifacet++) { facet = FACETS[layout][jfacet][ifacet]; rotn = FROTAT[layout][jfacet][ifacet]; halve = FHALVE[layout][jfacet][ifacet]; /* Recentre longitude? */ if (hpxdat->quad && facet >= 0) { if (facet <= 3) { facet += hpxdat->quad; if (facet > 3) facet -= 4; } else if (facet <= 7) { facet += hpxdat->quad; if (facet > 7) facet -= 4; } else { facet += hpxdat->quad; if (facet > 11) facet -= 4; } } /* Write out the data. */ if (facet < 0) { /* A blank facet. */ if (fits_write_img_null(fptr, group, fpixel, nelem, &status)) { goto fitserr; } } else { if (hpxdat->ordering == 'N') { /* Get nested indices. */ status = NESTidx(nside, facet, rotn, j, healidx); } else { /* Get ring indices. */ status = RINGidx(nside, facet, rotn, j, healidx); } /* Gather data into the output vector. */ healp = healidx; for (rowp = row; rowp < row + nside; rowp++) { *rowp = hpxdat->data[*(healp++)]; } /* Apply blanking to halved facets. */ if (halve) { if (abs(halve) == 1) { /* Blank bottom-right. */ i1 = j; i2 = nside; if (halve > 0) i1++; } else if (abs(halve) == 2) { /* Blank top-right. */ i1 = nside - j; i2 = nside; if (halve < 0) i1--; } else if (abs(halve) == 3) { /* Blank top-left. */ i1 = 0; i2 = j; if (halve < 0) i2++; } else { /* Blank bottom-left. */ i1 = 0; i2 = nside - j; if (halve > 0) i2--; } for (rowp = row + i1; rowp < row + i2; rowp++) { *rowp = nulval; } } /* Write out this facet's contribution to this row of the map. */ if (fits_write_imgnull_flt(fptr, group, fpixel, nelem, row, nulval, &status)) { goto fitserr; } } fpixel += nelem; } } } /* Write history. */ sprintf(history, "Original input file: %s", hpxdat->infile); fits_write_history(fptr, history, &status); sprintf(history, " Original NSIDE: %d", hpxdat->nside); fits_write_history(fptr, history, &status); sprintf(history, " Original ordering: %s", (hpxdat->ordering == 'N') ? "NESTED" : "RING"); if (hpxdat->ordering == 'r') strcat(history, " (assumed)"); fits_write_history(fptr, history, &status); /* Clean up. */ fits_close_file(fptr, &status); status = 0; return 0; fitserr: fits_report_error(stderr, status); cleanup: if (healidx) free(healidx); if (row) free(row); return 1; } /*--------------------------------------------------------------------------*/ /* (imap,jmap) are 0-relative pixel coordinates in the output map with origin * at the bottom-left corner of the specified facet which is rotated by * (45 + rotn * 90) degrees from its natural orientation; imap increases to * the right and jmap upwards. */ int NESTidx(int nside, int facet, int rotn, int jmap, long *healidx) { int h, i, imap, j, nside1, bit; long *hp; /* Nested index (0-relative) of the first pixel in this facet. */ h = facet * nside * nside; nside1 = nside - 1; hp = healidx; for (imap = 0; imap < nside; imap++, hp++) { /* (i,j) are 0-relative pixel coordinates with origin in the southern * corner of the facet; i increases to the north-east and j to the * north-west. */ if (rotn == 0) { i = nside1 - imap; j = jmap; } else if (rotn == 1) { i = nside1 - jmap; j = nside1 - imap; } else if (rotn == 2) { i = imap; j = nside1 - jmap; } else if (rotn == 3) { i = jmap; j = imap; } *hp = 0; bit = 1; while (i || j) { if (i & 1) *hp |= bit; bit <<= 1; if (j & 1) *hp |= bit; bit <<= 1; i >>= 1; j >>= 1; } *hp += h; } return 0; } /*--------------------------------------------------------------------------*/ /* (imap,jmap) pixel coordinates are as described above for NESTidx(). This * function computes the double-pixelisation index then converts it to the * regular ring index. */ int RINGidx(int nside, int facet, int rotn, int jmap, long *healidx) { const int I0[] = { 1, 3, -3, -1, 0, 2, 4, -2, 1, 3, -3, -1}; const int J0[] = { 1, 1, 1, 1, 0, 0, 0, 0, -1, -1, -1, -1}; int i, i0, imap, j, j0, n2side, n8side, npj, npole, nside1; long *hp; n2side = 2 * nside; n8side = 8 * nside; /* Double-pixelisation index of the last pixel in the north polar cap. */ npole = (n2side - 1) * (n2side - 1) - 1; /* Double-pixelisation pixel coordinates of the centre of the facet. */ i0 = nside * I0[facet]; j0 = nside * J0[facet]; nside1 = nside - 1; hp = healidx; for (imap = 0; imap < nside; imap++, hp++) { /* (i,j) are 0-relative, double-pixelisation pixel coordinates. The * origin is at the intersection of the equator and prime meridian, * i increases to the east (N.B.) and j to the north. */ if (rotn == 0) { i = i0 + nside1 - (jmap + imap); j = j0 + jmap - imap; } else if (rotn == 1) { i = i0 + imap - jmap; j = j0 + nside1 - (imap + jmap); } else if (rotn == 2) { i = i0 + (imap + jmap) - nside1; j = j0 + imap - jmap; } else if (rotn == 3) { i = i0 + jmap - imap; j = j0 + jmap + imap - nside1; } /* Convert i for counting pixels. */ if (i < 0) i += n8side; i++; if (j > nside) { /* North polar regime. */ if (j == n2side) { *hp = 0; } else { /* Number of pixels in a polar facet with this value of j. */ npj = 2 * (n2side - j); /* Index of the last pixel in the row above this. */ *hp = (npj - 1) * (npj - 1) - 1; /* Number of pixels in this row in the polar facets before this. */ *hp += npj * (i/n2side); /* Pixel number in this polar facet. */ *hp += i%n2side - (j - nside) - 1; } } else if (j >= -nside) { /* Equatorial regime. */ *hp = npole + n8side * (nside - j) + i; } else { /* South polar regime. */ *hp = 24 * nside * nside + 1; if (j > -n2side) { /* Number of pixels in a polar facet with this value of j. */ npj = 2 * (j + n2side); /* Total number of pixels in this row or below it. */ *hp -= (npj + 1) * (npj + 1); /* Number of pixels in this row in the polar facets before this. */ *hp += npj * (i/n2side); /* Pixel number in this polar facet. */ *hp += i%n2side + (nside + j) - 1; } } /* Convert double-pixelisation index to regular. */ *hp -= 1; *hp /= 2; } return 0; } /*--------------------------------------------------------------------------*/ int HPXhdr(fitsfile *fptr, struct healpix *hpxdat) { char comment[64], cval[16], *ctype1, *ctype2, *descr1, *descr2, *pcode; int status; float crpix1, crpix2, crval1, crval2; double cdelt1, cdelt2; status = 0; fits_update_key_log(fptr, "EXTEND", 0, "No FITS extensions are present", &status); fits_write_date(fptr, &status); /* Set pixel transformation parameters. */ if (hpxdat->layout == 0) { crpix1 = (5 * hpxdat->nside + 1) / 2.0f; } else { crpix1 = (4 * hpxdat->nside + 1) / 2.0f; } crpix2 = crpix1; fits_write_key(fptr, TFLOAT, "CRPIX1", &crpix1, "Coordinate reference pixel", &status); fits_write_key(fptr, TFLOAT, "CRPIX2", &crpix2, "Coordinate reference pixel", &status); if (hpxdat->layout == 0) { fits_write_key_flt(fptr, "PC1_1", 0.5f, -1, "Transformation matrix element", &status); fits_write_key_flt(fptr, "PC1_2", 0.5f, -1, "Transformation matrix element", &status); fits_write_key_flt(fptr, "PC2_1", -0.5f, -1, "Transformation matrix element", &status); fits_write_key_flt(fptr, "PC2_2", 0.5f, -1, "Transformation matrix element", &status); } cdelt1 = -90.0 / hpxdat->nside; cdelt2 = -cdelt1; fits_write_key_dbl(fptr, "CDELT1", cdelt1, -8, "[deg] Coordinate increment", &status); fits_write_key_dbl(fptr, "CDELT2", cdelt2, -8, "[deg] Coordinate increment", &status); /* Celestial transformation parameters. */ if (hpxdat->layout == 0) { pcode = "HPX"; } else { pcode = "XPH"; } if (hpxdat->crdsys == 'G') { /* Galactic. */ ctype1 = "GLON"; ctype2 = "GLAT"; descr1 = "Galactic longitude"; descr2 = "Galactic latitude"; } else if (hpxdat->crdsys == 'E') { /* Ecliptic, who-knows-what. */ ctype1 = "ELON"; ctype2 = "ELAT"; descr1 = "Ecliptic longitude"; descr2 = "Ecliptic latitude"; } else if (hpxdat->crdsys == 'Q') { /* Equatorial, who-knows-what. */ ctype1 = "RA--"; ctype2 = "DEC-"; descr1 = "Right ascension"; descr2 = "Declination"; } else { /* Unknown. */ ctype1 = "XLON"; ctype2 = "XLAT"; descr1 = "Longitude"; descr2 = " Latitude"; } sprintf(cval, "%s-%s", ctype1, pcode); sprintf(comment, "%s in an %s projection", descr1, pcode); fits_write_key_str(fptr, "CTYPE1", cval, comment, &status); sprintf(cval, "%s-%s", ctype2, pcode); sprintf(comment, "%s in an %s projection", descr2, pcode); fits_write_key_str(fptr, "CTYPE2", cval, comment, &status); crval1 = 0.0f + 90.0f * hpxdat->quad; if (hpxdat->layout == 0) { crval2 = 0.0f; } else if (hpxdat->layout == 1) { crval2 = 90.0f; } else { crval2 = -90.0f; } sprintf(comment, "[deg] %s at the reference point", descr1); fits_write_key(fptr, TFLOAT, "CRVAL1", &crval1, comment, &status); sprintf(comment, "[deg] %s at the reference point", descr2); fits_write_key(fptr, TFLOAT, "CRVAL2", &crval2, comment, &status); if (hpxdat->layout == 0) { fits_write_key_lng(fptr, "PV2_1", (LONGLONG)4, "HPX H parameter (longitude)", &status); fits_write_key_lng(fptr, "PV2_2", (LONGLONG)3, "HPX K parameter (latitude)", &status); } /* Commentary. */ fits_write_record(fptr, " ", &status); if (hpxdat->layout == 0) { fits_write_comment(fptr, "Celestial map with FITS-standard HPX coordinate system generated by", &status); } else { fits_write_comment(fptr, "Celestial map with experimental XPH coordinate system generated by", &status); } fits_write_comment(fptr, "'HPXcvt' which reorganises HEALPix data without interpolation as", &status); fits_write_comment(fptr, "described in \"Mapping on the HEALPix grid\" by Mark Calabretta and", &status); fits_write_comment(fptr, "Boud Roukema. See http://www.atnf.csiro.au/people/mcalabre", &status); return status; }