1 /*
2 # This file is part of the Astrometry.net suite.
3 # Licensed under a 3-clause BSD style license - see LICENSE
4 */
5
6 #include <math.h>
7 #include <string.h>
8
9 #include "qfits_rw.h"
10 #include "qfits_tools.h"
11
12 #include "os-features.h"
13 #include "sip_qfits.h"
14 #include "an-bool.h"
15 #include "fitsioutils.h"
16 #include "errors.h"
17 #include "log.h"
18 #include "ioutils.h"
19
20 #include "anqfits.h"
21
sip_from_string(const char * str,int slen,sip_t * dest)22 sip_t* sip_from_string(const char* str, int slen, sip_t* dest) {
23 qfits_header* hdr;
24 sip_t* rtn;
25 if (slen == 0) {
26 slen = strlen(str);
27 }
28 hdr = qfits_header_read_hdr_string((const unsigned char*)str, slen);
29 if (!hdr) {
30 ERROR("Failed to parse a FITS header from the given string");
31 return NULL;
32 }
33 rtn = sip_read_header(hdr, dest);
34 qfits_header_destroy(hdr);
35 return rtn;
36 }
37
sip_read_tan_or_sip_header_file_ext(const char * wcsfn,int ext,sip_t * dest,anbool forcetan)38 sip_t* sip_read_tan_or_sip_header_file_ext(const char* wcsfn, int ext, sip_t* dest, anbool forcetan) {
39 sip_t* rtn;
40 if (forcetan) {
41 sip_t sip;
42 memset(&sip, 0, sizeof(sip_t));
43 if (!tan_read_header_file_ext(wcsfn, ext, &(sip.wcstan))) {
44 ERROR("Failed to parse TAN header from file %s, extension %i", wcsfn, ext);
45 return NULL;
46 }
47 if (!dest)
48 dest = malloc(sizeof(sip_t));
49 memcpy(dest, &sip, sizeof(sip_t));
50 return dest;
51 } else {
52 rtn = sip_read_header_file_ext(wcsfn, ext, dest);
53 if (!rtn)
54 ERROR("Failed to parse SIP header from file %s, extension %i", wcsfn, ext);
55 return rtn;
56 }
57 }
58
sip_write_to(const sip_t * sip,FILE * fid)59 int sip_write_to(const sip_t* sip, FILE* fid) {
60 qfits_header* hdr;
61 int res;
62 if ((sip->a_order == 0) && (sip->b_order == 0) &&
63 (sip->ap_order == 0) && (sip->bp_order == 0))
64 return tan_write_to(&(sip->wcstan), fid);
65 hdr = sip_create_header(sip);
66 if (!hdr) {
67 ERROR("Failed to create FITS header from WCS");
68 return -1;
69 }
70 res = qfits_header_dump(hdr, fid);
71 qfits_header_destroy(hdr);
72 return res;
73 }
74
sip_write_to_file(const sip_t * sip,const char * fn)75 int sip_write_to_file(const sip_t* sip, const char* fn) {
76 FILE* fid;
77 int res;
78 if ((sip->a_order == 0) && (sip->b_order == 0) &&
79 (sip->ap_order == 0) && (sip->bp_order == 0))
80 return tan_write_to_file(&(sip->wcstan), fn);
81
82 fid = fopen(fn, "wb");
83 if (!fid) {
84 SYSERROR("Failed to open file \"%s\" to write WCS header", fn);
85 return -1;
86 }
87 res = sip_write_to(sip, fid);
88 if (res) {
89 ERROR("Failed to write FITS header to file \"%s\"", fn);
90 fclose(fid); //# Modified by Robert Lancaster for the StellarSolver Internal Library, to prevent leak
91 return -1;
92 }
93 if (fclose(fid)) {
94 SYSERROR("Failed to close file \"%s\" after writing WCS header", fn);
95 return -1;
96 }
97 return 0;
98 }
99
tan_write_to(const tan_t * tan,FILE * fid)100 int tan_write_to(const tan_t* tan, FILE* fid) {
101 qfits_header* hdr;
102 int res;
103 hdr = tan_create_header(tan);
104 if (!hdr) {
105 ERROR("Failed to create FITS header from WCS");
106 return -1;
107 }
108 res = qfits_header_dump(hdr, fid);
109 qfits_header_destroy(hdr);
110 return res;
111 }
112
tan_write_to_file(const tan_t * tan,const char * fn)113 int tan_write_to_file(const tan_t* tan, const char* fn) {
114 FILE* fid;
115 int res;
116 fid = fopen(fn, "wb");
117 if (!fid) {
118 SYSERROR("Failed to open file \"%s\" to write WCS header", fn);
119 return -1;
120 }
121 res = tan_write_to(tan, fid);
122 if (res) {
123 ERROR("Failed to write FITS header to file \"%s\"", fn);
124 fclose(fid); //# Modified by Robert Lancaster for the StellarSolver Internal Library, to prevent leak
125 return -1;
126 }
127 if (fclose(fid)) {
128 SYSERROR("Failed to close file \"%s\" after writing WCS header", fn);
129 return -1;
130 }
131 return 0;
132 }
133
wcs_hdr_common(qfits_header * hdr,const tan_t * tan)134 static void wcs_hdr_common(qfits_header* hdr, const tan_t* tan) {
135 qfits_header_add(hdr, "WCSAXES", "2", NULL, NULL);
136 qfits_header_add(hdr, "EQUINOX", "2000.0", "Equatorial coordinates definition (yr)", NULL);
137 qfits_header_add(hdr, "LONPOLE", "180.0", NULL, NULL);
138 qfits_header_add(hdr, "LATPOLE", "0.0", NULL, NULL);
139
140 fits_header_add_double(hdr, "CRVAL1", tan->crval[0], "RA of reference point");
141 fits_header_add_double(hdr, "CRVAL2", tan->crval[1], "DEC of reference point");
142 fits_header_add_double(hdr, "CRPIX1", tan->crpix[0], "X reference pixel");
143 fits_header_add_double(hdr, "CRPIX2", tan->crpix[1], "Y reference pixel");
144 qfits_header_add(hdr, "CUNIT1", "deg", "X pixel scale units", NULL);
145 qfits_header_add(hdr, "CUNIT2", "deg", "Y pixel scale units", NULL);
146
147 fits_header_add_double(hdr, "CD1_1", tan->cd[0][0], "Transformation matrix");
148 fits_header_add_double(hdr, "CD1_2", tan->cd[0][1], "");
149 fits_header_add_double(hdr, "CD2_1", tan->cd[1][0], "");
150 fits_header_add_double(hdr, "CD2_2", tan->cd[1][1], "");
151
152 if (tan->imagew > 0.0)
153 fits_header_add_double(hdr, "IMAGEW", tan->imagew, "Image width, in pixels.");
154 if (tan->imageh > 0.0)
155 fits_header_add_double(hdr, "IMAGEH", tan->imageh, "Image height, in pixels.");
156 }
157
sip_get_image_size(const qfits_header * hdr,int * pW,int * pH)158 int sip_get_image_size(const qfits_header* hdr, int* pW, int* pH) {
159 int W, H;
160 W = qfits_header_getint(hdr, "IMAGEW", 0);
161 debug("sip_get_image_size: IMAGEW = %i\n", W);
162 H = qfits_header_getint(hdr, "IMAGEH", 0);
163 debug("sip_get_image_size: IMAGEH = %i\n", H);
164 if (W == 0 || H == 0) {
165 // no IMAGE[WH]. Check for fpack-compressed image.
166 int eq;
167 char* str = fits_get_dupstring(hdr, "XTENSION");
168 //printf("XTENSION: '%s'\n", str);
169 // qfits_header_getstr turns the string double-quotes to single-quotes
170 eq = streq(str, "BINTABLE");
171 free(str);
172 if (eq) {
173 // ZNAXIS1 = 2046 / length of data axis 1
174 // ZNAXIS2 = 4094 / length of data axis 2
175 if (!W) {
176 W = qfits_header_getint(hdr, "ZNAXIS1", 0);
177 debug("sip_get_image_size: ZNAXIS1 = %i\n", W);
178 }
179 if (!H) {
180 H = qfits_header_getint(hdr, "ZNAXIS2", 0);
181 debug("sip_get_image_size: ZNAXIS2 = %i\n", H);
182 }
183 }
184 if (!W) {
185 W = qfits_header_getint(hdr, "NAXIS1", 0);
186 debug("sip_get_image_size: NAXIS1 = %i\n", W);
187 }
188 if (!H) {
189 H = qfits_header_getint(hdr, "NAXIS2", 0);
190 debug("sip_get_image_size: NAXIS2 = %i\n", H);
191 }
192 }
193 if (pW) *pW = W;
194 if (pH) *pH = H;
195 return 0;
196 }
197
add_polynomial(qfits_header * hdr,const char * format,int order,const double * data,int datastride)198 static void add_polynomial(qfits_header* hdr, const char* format,
199 int order, const double* data, int datastride) {
200 int i, j;
201 char key[64];
202 for (i=0; i<=order; i++)
203 for (j=0; (i+j)<=order; j++) {
204 //if (i+j < 1)
205 // continue;
206 //if (drop_linear && (i+j < 2))
207 // continue;
208 sprintf(key, format, i, j);
209 fits_header_add_double(hdr, key, data[i*datastride + j], "");
210 }
211 }
212
sip_add_to_header(qfits_header * hdr,const sip_t * sip)213 void sip_add_to_header(qfits_header* hdr, const sip_t* sip) {
214 wcs_hdr_common(hdr, &(sip->wcstan));
215 if (sip->wcstan.sin) {
216 qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--SIN-SIP", "SIN projection + SIP distortions", NULL);
217 qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---SIN-SIP", "SIN projection + SIP distortions", NULL);
218 } else {
219 qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--TAN-SIP", "TAN (gnomic) projection + SIP distortions", NULL);
220 qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---TAN-SIP", "TAN (gnomic) projection + SIP distortions", NULL);
221 }
222
223 fits_header_add_int(hdr, "A_ORDER", sip->a_order, "Polynomial order, axis 1");
224 add_polynomial(hdr, "A_%i_%i", sip->a_order, (double*)sip->a, SIP_MAXORDER);
225
226 fits_header_add_int(hdr, "B_ORDER", sip->b_order, "Polynomial order, axis 2");
227 add_polynomial(hdr, "B_%i_%i", sip->b_order, (double*)sip->b, SIP_MAXORDER);
228
229 fits_header_add_int(hdr, "AP_ORDER", sip->ap_order, "Inv polynomial order, axis 1");
230 add_polynomial(hdr, "AP_%i_%i", sip->ap_order, (double*)sip->ap, SIP_MAXORDER);
231
232 fits_header_add_int(hdr, "BP_ORDER", sip->bp_order, "Inv polynomial order, axis 2");
233 add_polynomial(hdr, "BP_%i_%i", sip->bp_order, (double*)sip->bp, SIP_MAXORDER);
234 }
235
sip_create_header(const sip_t * sip)236 qfits_header* sip_create_header(const sip_t* sip) {
237 qfits_header* hdr = qfits_table_prim_header_default();
238 sip_add_to_header(hdr, sip);
239 return hdr;
240 }
241
tan_add_to_header(qfits_header * hdr,const tan_t * tan)242 void tan_add_to_header(qfits_header* hdr, const tan_t* tan) {
243 wcs_hdr_common(hdr, tan);
244 if (tan->sin) {
245 qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--SIN", "SIN projection", NULL);
246 qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---SIN", "SIN projection", NULL);
247 } else {
248 qfits_header_add_after(hdr, "WCSAXES", "CTYPE2", "DEC--TAN", "TAN (gnomic) projection", NULL);
249 qfits_header_add_after(hdr, "WCSAXES", "CTYPE1", "RA---TAN", "TAN (gnomic) projection", NULL);
250 }
251 }
252
tan_create_header(const tan_t * tan)253 qfits_header* tan_create_header(const tan_t* tan) {
254 qfits_header* hdr = qfits_table_prim_header_default();
255 tan_add_to_header(hdr, tan);
256 return hdr;
257 }
258
read_header_file(const char * fn,int ext,anbool only,void * dest,void * (* readfunc)(const qfits_header *,void *))259 static void* read_header_file(const char* fn, int ext, anbool only, void* dest,
260 void* (*readfunc)(const qfits_header*, void*)) {
261 qfits_header* hdr;
262 void* result;
263 if (only) {
264 hdr = anqfits_get_header_only(fn, ext);
265 } else {
266 hdr = anqfits_get_header2(fn, ext);
267 }
268 if (!hdr) {
269 ERROR("Failed to read FITS header from file \"%s\" extension %i", fn, ext);
270 return NULL;
271 }
272 result = readfunc(hdr, dest);
273 if (!result) {
274 ERROR("Failed to parse WCS header from file \"%s\" extension %i", fn, ext);
275 }
276 qfits_header_destroy(hdr);
277 return result;
278 }
279
280 // silly little dispatch function to avoid casting - I like a modicum of type safety
call_sip_read_header(const qfits_header * hdr,void * dest)281 static void* call_sip_read_header(const qfits_header* hdr, void* dest) {
282 return sip_read_header(hdr, dest);
283 }
sip_read_header_file(const char * fn,sip_t * dest)284 sip_t* sip_read_header_file(const char* fn, sip_t* dest) {
285 return read_header_file(fn, 0, FALSE, dest, call_sip_read_header);
286 }
sip_read_header_file_ext(const char * fn,int ext,sip_t * dest)287 sip_t* sip_read_header_file_ext(const char* fn, int ext, sip_t* dest) {
288 return read_header_file(fn, ext, TRUE, dest, call_sip_read_header);
289 }
sip_read_header_file_ext_only(const char * fn,int ext,sip_t * dest)290 sip_t* sip_read_header_file_ext_only(const char* fn, int ext, sip_t* dest) {
291 return read_header_file(fn, ext, TRUE, dest, call_sip_read_header);
292 }
293
call_tan_read_header(const qfits_header * hdr,void * dest)294 static void* call_tan_read_header(const qfits_header* hdr, void* dest) {
295 return tan_read_header(hdr, dest);
296 }
tan_read_header_file(const char * fn,tan_t * dest)297 tan_t* tan_read_header_file(const char* fn, tan_t* dest) {
298 return read_header_file(fn, 0, FALSE, dest, call_tan_read_header);
299 }
tan_read_header_file_ext(const char * fn,int ext,tan_t * dest)300 tan_t* tan_read_header_file_ext(const char* fn, int ext, tan_t* dest) {
301 return read_header_file(fn, ext, FALSE, dest, call_tan_read_header);
302 }
tan_read_header_file_ext_only(const char * fn,int ext,tan_t * dest)303 tan_t* tan_read_header_file_ext_only(const char* fn, int ext, tan_t* dest) {
304 return read_header_file(fn, ext, TRUE, dest, call_tan_read_header);
305 }
306
307
read_polynomial(const qfits_header * hdr,const char * format,int order,double * data,int datastride,anbool skip_linear,anbool skip_zero)308 static anbool read_polynomial(const qfits_header* hdr, const char* format,
309 int order, double* data, int datastride,
310 anbool skip_linear, anbool skip_zero) {
311 int i, j;
312 char key[64];
313 double nil = -HUGE_VAL;
314 double val;
315 for (i=0; i<=order; i++)
316 for (j=0; (i+j)<=order; j++) {
317 if (skip_zero && (i+j < 1))
318 continue;
319 if (skip_linear && (i+j < 2))
320 continue;
321 sprintf(key, format, i, j);
322 val = qfits_header_getdouble(hdr, key, nil);
323 if (val == nil) {
324 // don't warn if linear terms are "missing"
325 if (i+j >= 2) {
326 ERROR("SIP: warning: key \"%s\" not found; setting to zero.", key);
327 }
328 val=0.0;
329 }
330 data[i*datastride + j] = val;
331 }
332 return TRUE;
333 }
334
sip_read_header(const qfits_header * hdr,sip_t * dest)335 sip_t* sip_read_header(const qfits_header* hdr, sip_t* dest) {
336 sip_t sip;
337 char* str;
338 const char* key;
339 const char* expect;
340 const char* expect2;
341 anbool is_sin;
342 anbool is_tan;
343 anbool skip_linear;
344 anbool skip_zero;
345 char pretty[FITS_LINESZ];
346
347 memset(&sip, 0, sizeof(sip_t));
348
349 key = "CTYPE1";
350 expect = "RA---TAN-SIP";
351 expect2 = "RA---SIN-SIP";
352 str = qfits_header_getstr(hdr, key);
353 str = qfits_pretty_string_r(str, pretty);
354 if (!str) {
355 ERROR("SIP header: no key \"%s\"", key);
356 return NULL;
357 }
358 is_tan = (strncmp(str, expect, strlen(expect)) == 0);
359 is_sin = (strncmp(str, expect2, strlen(expect2)) == 0);
360 if (!(is_tan || is_sin)) {
361 if (!tan_read_header(hdr, &(sip.wcstan))) {
362 ERROR("SIP: failed to read TAN header");
363 return NULL;
364 }
365 goto gohome;
366 }
367
368 key = "CTYPE2";
369 if (is_sin) {
370 expect = "DEC--SIN-SIP";
371 } else {
372 expect = "DEC--TAN-SIP";
373 }
374 str = qfits_header_getstr(hdr, key);
375 str = qfits_pretty_string_r(str, pretty);
376 if (!str || strncmp(str, expect, strlen(expect))) {
377 ERROR("SIP header: incorrect key \"%s\": expected \"%s\", got \"%s\"", key, expect, str);
378 return NULL;
379 }
380
381 if (!tan_read_header(hdr, &sip.wcstan)) {
382 ERROR("SIP: failed to read TAN header");
383 return NULL;
384 }
385
386 sip.a_order = qfits_header_getint(hdr, "A_ORDER", -1);
387 sip.b_order = qfits_header_getint(hdr, "B_ORDER", -1);
388 sip.ap_order = qfits_header_getint(hdr, "AP_ORDER", 0);
389 sip.bp_order = qfits_header_getint(hdr, "BP_ORDER", 0);
390
391 if ((sip.a_order == -1) ||
392 (sip.b_order == -1)) {
393 ERROR("SIP: failed to read polynomial orders (A_ORDER=%i, B_ORDER=%i, -1 means absent)\n",
394 sip.a_order, sip.b_order);
395 return NULL;
396 }
397 if ((sip.ap_order == 0) ||
398 (sip.bp_order == 0)) {
399 logverb("Warning: SIP: failed to read polynomial orders (A_ORDER=%i, B_ORDER=%i (-1 means absent), AP_ORDER=%i, BP_ORDER=%i, (0 means absent)\n",
400 sip.a_order, sip.b_order, sip.ap_order, sip.bp_order);
401 }
402
403 if ((sip.a_order > SIP_MAXORDER) ||
404 (sip.b_order > SIP_MAXORDER) ||
405 (sip.ap_order > SIP_MAXORDER) ||
406 (sip.bp_order > SIP_MAXORDER)) {
407 ERROR("SIP: polynomial orders (A=%i, B=%i, AP=%i, BP=%i) exceeds maximum of %i",
408 sip.a_order, sip.b_order, sip.ap_order, sip.bp_order, SIP_MAXORDER);
409 return NULL;
410 }
411
412 skip_linear = FALSE;
413 skip_zero = FALSE;
414
415 if (!read_polynomial(hdr, "A_%i_%i", sip.a_order, (double*)sip.a, SIP_MAXORDER, skip_linear, skip_zero) ||
416 !read_polynomial(hdr, "B_%i_%i", sip.b_order, (double*)sip.b, SIP_MAXORDER, skip_linear, skip_zero) ||
417 (sip.ap_order > 0 && !read_polynomial(hdr, "AP_%i_%i", sip.ap_order, (double*)sip.ap, SIP_MAXORDER, FALSE, FALSE)) ||
418 (sip.bp_order > 0 && !read_polynomial(hdr, "BP_%i_%i", sip.bp_order, (double*)sip.bp, SIP_MAXORDER, FALSE, FALSE))) {
419 ERROR("SIP: failed to read polynomial terms");
420 return NULL;
421 }
422
423 gohome:
424 if (!dest)
425 dest = malloc(sizeof(sip_t));
426
427 memcpy(dest, &sip, sizeof(sip_t));
428 return dest;
429 }
430
check_tan_ctypes(char * ct1,char * ct2,anbool * is_sin)431 static int check_tan_ctypes(char* ct1, char* ct2, anbool* is_sin) {
432 const char* ra = "RA---TAN";
433 const char* dec = "DEC--TAN";
434 const char* ra2 = "RA---SIN";
435 const char* dec2 = "DEC--SIN";
436 int NC = 8;
437 *is_sin = FALSE;
438 if (!ct1 || !ct2)
439 return -1;
440 if (strlen(ct1) < NC || strlen(ct2) < NC)
441 return -1;
442 if ((strncmp(ct1, ra, NC) == 0) && (strncmp(ct2, dec, NC) == 0))
443 return 0;
444 if ((strncmp(ct1, dec, NC) == 0) && (strncmp(ct2, ra, NC) == 0))
445 return 1;
446
447 if ((strncmp(ct1, ra2, NC) == 0) && (strncmp(ct2, dec2, NC) == 0)) {
448 *is_sin = TRUE;
449 return 0;
450 }
451 if ((strncmp(ct1, dec2, NC) == 0) && (strncmp(ct2, ra2, NC) == 0)) {
452 *is_sin = TRUE;
453 return 1;
454 }
455 return -1;
456 }
457
tan_read_header(const qfits_header * hdr,tan_t * dest)458 tan_t* tan_read_header(const qfits_header* hdr, tan_t* dest) {
459 tan_t tan;
460 double nil = -1e300;
461 char* ct1;
462 char* ct2;
463 int swap;
464 int W, H;
465 anbool is_sin;
466
467 memset(&tan, 0, sizeof(tan_t));
468
469 ct1 = fits_get_dupstring(hdr, "CTYPE1");
470 ct2 = fits_get_dupstring(hdr, "CTYPE2");
471 swap = check_tan_ctypes(ct1, ct2, &is_sin);
472 if (swap == -1) {
473 ERROR("TAN header: expected CTYPE1 = RA---TAN, CTYPE2 = DEC--TAN "
474 "(or vice versa), or RA---SIN, DEC--SIN or vice versa; "
475 "got CTYPE1 = \"%s\", CYTPE2 = \"%s\"\n",
476 ct1, ct2);
477 }
478 free(ct1);
479 free(ct2);
480 if (swap == -1)
481 return NULL;
482
483 sip_get_image_size(hdr, &W, &H);
484 tan.imagew = W;
485 tan.imageh = H;
486
487 {
488 const char* keys[] = { "CRVAL1", "CRVAL2", "CRPIX1", "CRPIX2",
489 "CD1_1", "CD1_2", "CD2_1", "CD2_2" };
490 double* vals[] = { &(tan.crval[0]), &(tan.crval[1]),
491 &(tan.crpix[0]), &(tan.crpix[1]),
492 &(tan.cd[0][0]), &(tan.cd[0][1]),
493 &(tan.cd[1][0]), &(tan.cd[1][1]) };
494 int i;
495 for (i=0; i<4; i++) {
496 *(vals[i]) = qfits_header_getdouble(hdr, keys[i], nil);
497 if (*(vals[i]) == nil) {
498 ERROR("TAN header: missing or invalid value for \"%s\"", keys[i]);
499 return NULL;
500 }
501 }
502 // Try CD
503 int gotcd = 1;
504 char* complaint = NULL;
505 for (i=4; i<8; i++) {
506 *(vals[i]) = qfits_header_getdouble(hdr, keys[i], nil);
507 if (*(vals[i]) == nil) {
508 asprintf_safe(&complaint, "TAN header: missing or invalid value for key \"%s\"", keys[i]);
509 gotcd = 0;
510 break;
511 }
512 }
513 if (!gotcd) {
514 double cdelt1,cdelt2;
515 // Try CDELT
516 char* key = "CDELT1";
517 cdelt1 = qfits_header_getdouble(hdr, key, nil);
518 if (cdelt1 == nil) {
519 ERROR("%s; also tried but didn't find \"%s\"", complaint, key);
520 free(complaint);
521 return NULL;
522 }
523 key = "CDELT2";
524 cdelt2 = qfits_header_getdouble(hdr, key, nil);
525 if (cdelt2 == nil) {
526 ERROR("%s; also tried but didn't find \"%s\"", complaint, key);
527 free(complaint);
528 return NULL;
529 }
530 tan.cd[0][0] = cdelt1;
531 tan.cd[0][1] = 0.0;
532 tan.cd[1][0] = 0.0;
533 tan.cd[1][1] = cdelt2;
534 }
535 }
536
537 if (swap == 1) {
538 double tmp;
539 tmp = tan.crval[0];
540 tan.crval[0] = tan.crval[1];
541 tan.crval[1] = tmp;
542 // swap CD1_1 <-> CD2_1
543 tmp = tan.cd[0][0];
544 tan.cd[0][0] = tan.cd[1][0];
545 tan.cd[1][0] = tmp;
546 // swap CD1_2 <-> CD2_2
547 tmp = tan.cd[0][1];
548 tan.cd[0][1] = tan.cd[1][1];
549 tan.cd[1][1] = tmp;
550 }
551
552 tan.sin = is_sin;
553
554 if (!dest)
555 dest = malloc(sizeof(tan_t));
556 memcpy(dest, &tan, sizeof(tan_t));
557 return dest;
558 }
559