1 /*============================================================================
2 WCSLIB 7.7 - an implementation of the FITS WCS standard.
3 Copyright (C) 1995-2021, Mark Calabretta
4
5 This file is part of WCSLIB.
6
7 WCSLIB is free software: you can redistribute it and/or modify it under the
8 terms of the GNU Lesser General Public License as published by the Free
9 Software Foundation, either version 3 of the License, or (at your option)
10 any later version.
11
12 WCSLIB is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with WCSLIB. If not, see http://www.gnu.org/licenses.
19
20 Author: Mark Calabretta, Australia Telescope National Facility, CSIRO.
21 http://www.atnf.csiro.au/people/Mark.Calabretta
22 $Id: wcshdr.c,v 7.7 2021/07/12 06:36:49 mcalabre Exp $
23 *===========================================================================*/
24
25 #include <ctype.h>
26 #include <math.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "wcserr.h"
32 #include "wcsmath.h"
33 #include "wcsutil.h"
34 #include "wcshdr.h"
35 #include "wtbarr.h"
36 #include "tab.h"
37 #include "dis.h"
38 #include "wcs.h"
39
40 extern const int WCSSET;
41
42 extern const int DIS_DOTPD;
43
44 // Map status return value to message.
45 const char *wcshdr_errmsg[] = {
46 "Success",
47 "Null wcsprm pointer passed",
48 "Memory allocation failed",
49 "Invalid column selection",
50 "Fatal error returned by Flex parser",
51 "Invalid tabular parameters"};
52
53 // Map error returns for lower-level routines.
54 const int wcshdr_taberr[] = {
55 WCSHDRERR_SUCCESS, // 0: TABERR_SUCCESS
56 WCSHDRERR_NULL_POINTER, // 1: TABERR_NULL_POINTER
57 WCSHDRERR_MEMORY, // 2: TABERR_MEMORY
58 WCSHDRERR_BAD_TABULAR_PARAMS // 3: TABERR_BAD_PARAMS
59 // 4: TABERR_BAD_X
60 // 5: TABERR_BAD_WORLD
61 };
62
63 // Convenience macro for invoking wcserr_set().
64 #define WCSHDR_ERRMSG(status) WCSERR_SET(status), wcshdr_errmsg[status]
65
66 // Internal helper functions, not for general use.
67 static void wcshdo_format(int, int, const double [], char *);
68 static void wcshdo_tpdterm(int, int, char *);
69 static void wcshdo_util(int, const char [], const char [], int, const char [],
70 int, int, int, char, int, int [], char [], const char [], int *, char **,
71 int *);
72
73 //----------------------------------------------------------------------------
74
wcstab(struct wcsprm * wcs)75 int wcstab(struct wcsprm *wcs)
76
77 {
78 static const char *function = "wcstab";
79
80 char (*PSi_0a)[72] = 0x0, (*PSi_1a)[72] = 0x0, (*PSi_2a)[72] = 0x0;
81 int *PVi_1a = 0x0, *PVi_2a = 0x0, *PVi_3a = 0x0, *tabax, *tabidx = 0x0;
82 int getcrd, i, ip, itab, itabax, j, jtabax, m, naxis, ntabax, status;
83 struct wtbarr *wtbp;
84 struct tabprm *tabp;
85 struct wcserr **err;
86
87 if (wcs == 0x0) return WCSHDRERR_NULL_POINTER;
88 err = &(wcs->err);
89
90 // Free memory previously allocated by wcstab().
91 if (wcs->flag != -1 && wcs->m_flag == WCSSET) {
92 if (wcs->wtb == wcs->m_wtb) wcs->wtb = 0x0;
93 if (wcs->tab == wcs->m_tab) wcs->tab = 0x0;
94
95 if (wcs->m_wtb) free(wcs->m_wtb);
96 if (wcs->m_tab) {
97 for (j = 0; j < wcs->ntab; j++) {
98 tabfree(wcs->m_tab + j);
99 }
100
101 free(wcs->m_tab);
102 }
103 }
104
105 wcs->ntab = 0;
106 wcs->nwtb = 0;
107 wcs->wtb = 0x0;
108 wcs->tab = 0x0;
109
110
111 // Determine the number of -TAB axes.
112 naxis = wcs->naxis;
113 if (!(tabax = calloc(naxis, sizeof(int)))) {
114 return wcserr_set(WCSHDR_ERRMSG(WCSHDRERR_MEMORY));
115 }
116
117 ntabax = 0;
118 for (i = 0; i < naxis; i++) {
119 // Null fill.
120 wcsutil_null_fill(72, wcs->ctype[i]);
121
122 if (!strcmp(wcs->ctype[i]+4, "-TAB")) {
123 tabax[i] = ntabax++;
124 } else {
125 tabax[i] = -1;
126 }
127 }
128
129 if (ntabax == 0) {
130 // No lookup tables.
131 status = 0;
132 goto cleanup;
133 }
134
135
136 // Collect information from the PSi_ma and PVi_ma keyvalues.
137 if (!((PSi_0a = calloc(ntabax, sizeof(char[72]))) &&
138 (PVi_1a = calloc(ntabax, sizeof(int))) &&
139 (PVi_2a = calloc(ntabax, sizeof(int))) &&
140 (PSi_1a = calloc(ntabax, sizeof(char[72]))) &&
141 (PSi_2a = calloc(ntabax, sizeof(char[72]))) &&
142 (PVi_3a = calloc(ntabax, sizeof(int))) &&
143 (tabidx = calloc(ntabax, sizeof(int))))) {
144 status = wcserr_set(WCSHDR_ERRMSG(WCSHDRERR_MEMORY));
145 goto cleanup;
146 }
147
148 for (itabax = 0; itabax < ntabax; itabax++) {
149 // Remember that calloc() zeroes allocated memory.
150 PVi_1a[itabax] = 1;
151 PVi_2a[itabax] = 1;
152 PVi_3a[itabax] = 1;
153 }
154
155 for (ip = 0; ip < wcs->nps; ip++) {
156 itabax = tabax[wcs->ps[ip].i - 1];
157 if (itabax >= 0) {
158 switch (wcs->ps[ip].m) {
159 case 0:
160 // EXTNAME.
161 strcpy(PSi_0a[itabax], wcs->ps[ip].value);
162 wcsutil_null_fill(72, PSi_0a[itabax]);
163 break;
164 case 1:
165 // TTYPEn for coordinate array.
166 strcpy(PSi_1a[itabax], wcs->ps[ip].value);
167 wcsutil_null_fill(72, PSi_1a[itabax]);
168 break;
169 case 2:
170 // TTYPEn for index vector.
171 strcpy(PSi_2a[itabax], wcs->ps[ip].value);
172 wcsutil_null_fill(72, PSi_2a[itabax]);
173 break;
174 }
175 }
176 }
177
178 for (ip = 0; ip < wcs->npv; ip++) {
179 itabax = tabax[wcs->pv[ip].i - 1];
180 if (itabax >= 0) {
181 switch (wcs->pv[ip].m) {
182 case 1:
183 // EXTVER.
184 PVi_1a[itabax] = (int)(wcs->pv[ip].value + 0.5);
185 break;
186 case 2:
187 // EXTLEVEL.
188 PVi_2a[itabax] = (int)(wcs->pv[ip].value + 0.5);
189 break;
190 case 3:
191 // Table axis number.
192 PVi_3a[itabax] = (int)(wcs->pv[ip].value + 0.5);
193 break;
194 }
195 }
196 }
197
198
199 // Determine the number of independent tables.
200 for (itabax = 0; itabax < ntabax; itabax++) {
201 // These have no defaults.
202 if (!PSi_0a[itabax][0] || !PSi_1a[itabax][0]) {
203 status = wcserr_set(WCSERR_SET(WCSHDRERR_BAD_TABULAR_PARAMS),
204 "Invalid tabular parameters: PSi_0a and PSi_1a must be specified");
205 goto cleanup;
206 }
207
208 tabidx[itabax] = -1;
209 for (jtabax = 0; jtabax < i; jtabax++) {
210 // EXTNAME, EXTVER, EXTLEVEL, and TTYPEn for the coordinate array
211 // must match for each axis of a multi-dimensional lookup table.
212 if (strcmp(PSi_0a[itabax], PSi_0a[jtabax]) == 0 &&
213 strcmp(PSi_1a[itabax], PSi_1a[jtabax]) == 0 &&
214 PVi_1a[itabax] == PVi_1a[jtabax] &&
215 PVi_2a[itabax] == PVi_2a[jtabax]) {
216 tabidx[itabax] = tabidx[jtabax];
217 break;
218 }
219 }
220
221 if (jtabax == itabax) {
222 tabidx[itabax] = wcs->ntab;
223 wcs->ntab++;
224 }
225 }
226
227 if (!(wcs->tab = calloc(wcs->ntab, sizeof(struct tabprm)))) {
228 status = wcserr_set(WCSHDR_ERRMSG(WCSHDRERR_MEMORY));
229 goto cleanup;
230 }
231 wcs->m_tab = wcs->tab;
232
233 // Table dimensionality; find the largest axis number.
234 for (itabax = 0; itabax < ntabax; itabax++) {
235 tabp = wcs->tab + tabidx[itabax];
236
237 // PVi_3a records the 1-relative table axis number.
238 if (PVi_3a[itabax] > tabp->M) {
239 tabp->M = PVi_3a[itabax];
240 }
241 }
242
243 for (itab = 0; itab < wcs->ntab; itab++) {
244 if ((status = tabini(1, wcs->tab[itab].M, 0, wcs->tab + itab))) {
245 status = wcserr_set(WCSHDR_ERRMSG(wcshdr_taberr[status]));
246 goto cleanup;
247 }
248 }
249
250
251 // Copy parameters into the tabprm structs.
252 for (i = 0; i < naxis; i++) {
253 if ((itabax = tabax[i]) < 0) {
254 // Not a -TAB axis.
255 continue;
256 }
257
258 // PVi_3a records the 1-relative table axis number.
259 m = PVi_3a[itabax] - 1;
260
261 tabp = wcs->tab + tabidx[itabax];
262 tabp->map[m] = i;
263 tabp->crval[m] = wcs->crval[i];
264 }
265
266 // Check for completeness.
267 for (itab = 0; itab < wcs->ntab; itab++) {
268 for (m = 0; m < wcs->tab[itab].M; m++) {
269 if (wcs->tab[itab].map[m] < 0) {
270 status = wcserr_set(WCSERR_SET(WCSHDRERR_BAD_TABULAR_PARAMS),
271 "Invalid tabular parameters: the axis mapping is undefined");
272 goto cleanup;
273 }
274 }
275 }
276
277
278 // Set up for reading the arrays; how many arrays are there?
279 for (itabax = 0; itabax < ntabax; itabax++) {
280 // Does this -TAB axis have a non-degenerate index array?
281 if (PSi_2a[itabax][0]) {
282 wcs->nwtb++;
283 }
284 }
285
286 // Add one coordinate array for each table.
287 wcs->nwtb += wcs->ntab;
288
289 // Allocate memory for structs to be returned.
290 if (!(wcs->wtb = calloc(wcs->nwtb, sizeof(struct wtbarr)))) {
291 wcs->nwtb = 0;
292
293 status = wcserr_set(WCSHDR_ERRMSG(WCSHDRERR_MEMORY));
294 goto cleanup;
295 }
296 wcs->m_wtb = wcs->wtb;
297
298 // Set pointers for the index and coordinate arrays.
299 wtbp = wcs->wtb;
300 for (itab = 0; itab < wcs->ntab; itab++) {
301 getcrd = 1;
302 for (itabax = 0; itabax < ntabax; itabax++) {
303 if (tabidx[itabax] != itab) continue;
304
305 if (getcrd) {
306 // Coordinate array.
307 wtbp->i = itabax + 1;
308 wtbp->m = PVi_3a[itabax];
309 wtbp->kind = 'c';
310
311 strcpy(wtbp->extnam, PSi_0a[itabax]);
312 wtbp->extver = PVi_1a[itabax];
313 wtbp->extlev = PVi_2a[itabax];
314 strcpy(wtbp->ttype, PSi_1a[itabax]);
315 wtbp->row = 1L;
316 wtbp->ndim = wcs->tab[itab].M + 1;
317 wtbp->dimlen = wcs->tab[itab].K;
318 wtbp->arrayp = &(wcs->tab[itab].coord);
319
320 // Signal for tabset() to take this memory.
321 wcs->tab[itab].m_coord = (double *)0x1;
322
323 wtbp++;
324 getcrd = 0;
325 }
326
327 if (PSi_2a[itabax][0]) {
328 // Index array.
329 wtbp->i = itabax + 1;
330 wtbp->m = PVi_3a[itabax];
331 wtbp->kind = 'i';
332
333 m = wtbp->m - 1;
334 strcpy(wtbp->extnam, PSi_0a[itabax]);
335 wtbp->extver = PVi_1a[itabax];
336 wtbp->extlev = PVi_2a[itabax];
337 strcpy(wtbp->ttype, PSi_2a[itabax]);
338 wtbp->row = 1L;
339 wtbp->ndim = 1;
340 wtbp->dimlen = wcs->tab[itab].K + m;
341 wtbp->arrayp = wcs->tab[itab].index + m;
342
343 // Signal for tabset() to take this memory.
344 wcs->tab[itab].m_indxs[m] = (double *)0x1;
345
346 wtbp++;
347 }
348 }
349 }
350
351 status = 0;
352
353 cleanup:
354 if (tabax) free(tabax);
355 if (tabidx) free(tabidx);
356 if (PSi_0a) free(PSi_0a);
357 if (PVi_1a) free(PVi_1a);
358 if (PVi_2a) free(PVi_2a);
359 if (PSi_1a) free(PSi_1a);
360 if (PSi_2a) free(PSi_2a);
361 if (PVi_3a) free(PVi_3a);
362
363 if (status) {
364 if (wcs->tab) free(wcs->tab);
365 if (wcs->wtb) free(wcs->wtb);
366 }
367
368 return status;
369 }
370
371 //----------------------------------------------------------------------------
372
wcsidx(int nwcs,struct wcsprm ** wcs,int alts[27])373 int wcsidx(int nwcs, struct wcsprm **wcs, int alts[27])
374
375 {
376 int a, iwcs;
377 struct wcsprm *wcsp;
378
379 for (a = 0; a < 27; a++) {
380 alts[a] = -1;
381 }
382
383 if (wcs == 0x0) {
384 return WCSHDRERR_NULL_POINTER;
385 }
386
387 wcsp = *wcs;
388 for (iwcs = 0; iwcs < nwcs; iwcs++, wcsp++) {
389 if (wcsp->colnum || wcsp->colax[0]) continue;
390
391 if (wcsp->alt[0] == ' ') {
392 a = 0;
393 } else {
394 a = wcsp->alt[0] - 'A' + 1;
395 }
396
397 alts[a] = iwcs;
398 }
399
400 return 0;
401 }
402
403 //----------------------------------------------------------------------------
404
wcsbdx(int nwcs,struct wcsprm ** wcs,int type,short alts[1000][28])405 int wcsbdx(int nwcs, struct wcsprm **wcs, int type, short alts[1000][28])
406
407 {
408 short *ip;
409 int a, i, icol, iwcs;
410 struct wcsprm *wcsp;
411
412 for (ip = alts[0]; ip < alts[0] + 28*1000; ip++) {
413 *ip = -1;
414 }
415
416 for (icol = 0; icol < 1000; icol++) {
417 alts[icol][27] = 0;
418 }
419
420 if (wcs == 0x0) {
421 return WCSHDRERR_NULL_POINTER;
422 }
423
424 wcsp = *wcs;
425 for (iwcs = 0; iwcs < nwcs; iwcs++, wcsp++) {
426 if (wcsp->alt[0] == ' ') {
427 a = 0;
428 } else {
429 a = wcsp->alt[0] - 'A' + 1;
430 }
431
432 if (type) {
433 // Pixel list.
434 if (wcsp->colax[0]) {
435 for (i = 0; i < wcsp->naxis; i++) {
436 alts[wcsp->colax[i]][a] = iwcs;
437 alts[wcsp->colax[i]][27]++;
438 }
439 } else if (!wcsp->colnum) {
440 alts[0][a] = iwcs;
441 alts[0][27]++;
442 }
443
444 } else {
445 // Binary table image array.
446 if (wcsp->colnum) {
447 alts[wcsp->colnum][a] = iwcs;
448 alts[wcsp->colnum][27]++;
449 } else if (!wcsp->colax[0]) {
450 alts[0][a] = iwcs;
451 alts[0][27]++;
452 }
453 }
454 }
455
456 return 0;
457 }
458
459 //----------------------------------------------------------------------------
460
wcsvfree(int * nwcs,struct wcsprm ** wcs)461 int wcsvfree(int *nwcs, struct wcsprm **wcs)
462
463 {
464 int a, status = 0;
465 struct wcsprm *wcsp;
466
467 if (wcs == 0x0) {
468 return WCSHDRERR_NULL_POINTER;
469 }
470
471 wcsp = *wcs;
472 for (a = 0; a < *nwcs; a++, wcsp++) {
473 status |= wcsfree(wcsp);
474 }
475
476 free(*wcs);
477
478 *nwcs = 0;
479 *wcs = 0x0;
480
481 return status;
482 }
483
484 //----------------------------------------------------------------------------
485 // Matching the definitions in dis.c.
486 #define I_DTYPE 0 // Distortion type code.
487 #define I_NIPARM 1 // Full (allocated) length of iparm[].
488 #define I_NDPARM 2 // No. of parameters in dparm[], excl. work space.
489 #define I_TPDNCO 3 // No. of TPD coefficients, forward...
490 #define I_TPDINV 4 // ...and inverse.
491 #define I_TPDAUX 5 // True if auxiliary variables are used.
492 #define I_TPDRAD 6 // True if the radial variable is used.
493
wcshdo(int ctrl,struct wcsprm * wcs,int * nkeyrec,char ** header)494 int wcshdo(int ctrl, struct wcsprm *wcs, int *nkeyrec, char **header)
495
496 // ::: CUBEFACE and STOKES handling?
497
498 {
499 static const char *function = "wcshdo";
500
501 const char axid[] = "xyxuvu", *cp;
502 const int nTPD[] = {1, 4, 7, 12, 17, 24, 31, 40, 49, 60};
503
504 char alt, comment[72], ctemp[32], *ctypei, format[16], fmt01[8],
505 keyvalue[96], keyword[16], *kp, obsg[8] = "OBSG?",
506 obsgeo[8] = "OBSGEO-?", pq, ptype, xtype, term[16], timeunit[16],
507 tpdsrc[24], xyz[] = "XYZ";
508 int *axmap, bintab, *colax, colnum, degree, direct = 0, doaux = 0, dofmt,
509 dosip, dotpd, dotpv, i, idis, idp, *iparm, j, jhat, k, kp0, kpi, m,
510 naxis, ncoeff, Nhat, p, pixlist, precision, primage, q, status = 0;
511 double *dparm, keyval;
512 struct auxprm *aux;
513 struct disprm *dis;
514 struct dpkey *keyp;
515 struct wcserr **err;
516
517 *nkeyrec = 0;
518 *header = 0x0;
519
520 if (wcs == 0x0) return WCSHDRERR_NULL_POINTER;
521 err = &(wcs->err);
522
523 if (wcs->flag != WCSSET) {
524 if ((status = wcsset(wcs))) return status;
525 }
526
527 if ((naxis = wcs->naxis) == 0) {
528 return 0;
529 }
530
531
532 // These are mainly for convenience.
533 alt = wcs->alt[0];
534 if (alt == ' ') alt = '\0';
535 colnum = wcs->colnum;
536 colax = wcs->colax;
537
538 primage = 0;
539 bintab = 0;
540 pixlist = 0;
541 if (colnum) {
542 bintab = 1;
543 } else if (colax[0]) {
544 pixlist = 1;
545 } else {
546 primage = 1;
547 }
548
549
550 // Initialize floating point format control.
551 *format = '\0';
552 if (ctrl & WCSHDO_P17) {
553 strcpy(format, "% 20.17G");
554 } else if (ctrl & WCSHDO_P16) {
555 strcpy(format, "% 20.16G");
556 } else if (ctrl & WCSHDO_P15) {
557 strcpy(format, "% 20.15G");
558 } else if (ctrl & WCSHDO_P14) {
559 strcpy(format, "% 20.14G");
560 } else if (ctrl & WCSHDO_P13) {
561 strcpy(format, "% 20.13G");
562 } else if (ctrl & WCSHDO_P12) {
563 strcpy(format, "%20.12G");
564 }
565
566 if (*format && (ctrl & WCSHDO_EFMT)) {
567 if (format[6] == 'G') {
568 format[6] = 'E';
569 } else {
570 format[7] = 'E';
571 }
572 }
573
574 dofmt = (*format == '\0');
575
576
577 // WCS dimension.
578 if (!pixlist) {
579 sprintf(keyvalue, "%20d", naxis);
580 wcshdo_util(ctrl, "WCSAXES", "WCAX", 0, 0x0, 0, 0, 0, alt, colnum, colax,
581 keyvalue, "Number of coordinate axes", nkeyrec, header, &status);
582 }
583
584 // Reference pixel coordinates.
585 if (dofmt) wcshdo_format('G', naxis, wcs->crpix, format);
586 for (j = 0; j < naxis; j++) {
587 wcsutil_double2str(keyvalue, format, wcs->crpix[j]);
588 wcshdo_util(ctrl, "CRPIX", "CRP", WCSHDO_CRPXna, "CRPX", 0, j+1, 0, alt,
589 colnum, colax, keyvalue, "Pixel coordinate of reference point", nkeyrec,
590 header, &status);
591 }
592
593 // Linear transformation matrix.
594 if (dofmt) wcshdo_format('G', naxis*naxis, wcs->pc, format);
595 k = 0;
596 for (i = 0; i < naxis; i++) {
597 for (j = 0; j < naxis; j++, k++) {
598 if (i == j) {
599 if (wcs->pc[k] == 1.0) continue;
600 } else {
601 if (wcs->pc[k] == 0.0) continue;
602 }
603
604 wcsutil_double2str(keyvalue, format, wcs->pc[k]);
605 wcshdo_util(ctrl, "PC", bintab ? "PC" : "P", WCSHDO_TPCn_ka,
606 bintab ? 0x0 : "PC", i+1, j+1, 0, alt, colnum, colax,
607 keyvalue, "Coordinate transformation matrix element",
608 nkeyrec, header, &status);
609 }
610 }
611
612 // Coordinate increment at reference point.
613 if (dofmt) wcshdo_format('G', naxis, wcs->cdelt, format);
614 for (i = 0; i < naxis; i++) {
615 wcsutil_double2str(keyvalue, format, wcs->cdelt[i]);
616 comment[0] = '\0';
617 if (wcs->cunit[i][0]) sprintf(comment, "[%s] ", wcs->cunit[i]);
618 strcat(comment, "Coordinate increment at reference point");
619 wcshdo_util(ctrl, "CDELT", "CDE", WCSHDO_CRPXna, "CDLT", i+1, 0, 0, alt,
620 colnum, colax, keyvalue, comment, nkeyrec, header, &status);
621 }
622
623 // Units of coordinate increment and reference value.
624 for (i = 0; i < naxis; i++) {
625 if (wcs->cunit[i][0] == '\0') continue;
626
627 sprintf(keyvalue, "'%s'", wcs->cunit[i]);
628 wcshdo_util(ctrl, "CUNIT", "CUN", WCSHDO_CRPXna, "CUNI", i+1, 0, 0, alt,
629 colnum, colax, keyvalue, "Units of coordinate increment and value",
630 nkeyrec, header, &status);
631 }
632
633 // May need to alter ctype for particular distortions so do basic checks
634 // now. Note that SIP, TPV, DSS, TNX, and ZPX are restricted to exactly
635 // two axes and cannot coexist with other distortion types.
636 dosip = 0;
637 dotpv = 0;
638 dotpd = 0;
639
640 if ((dis = wcs->lin.dispre)) {
641 for (i = 0; i < naxis; i++) {
642 if (strcmp(dis->dtype[i], "SIP") == 0) {
643 // Simple Imaging Polynomial (SIP). Write it in its native form
644 // if possible, unless specifically requested to write it as TPD.
645 dotpd = (dis->iparm[i][I_DTYPE] & DIS_DOTPD);
646
647 if (!dotpd) {;
648 if (alt ||
649 dis->Nhat[0] != 2 ||
650 dis->Nhat[1] != 2 ||
651 dis->axmap[0][0] != 0 ||
652 dis->axmap[0][1] != 1 ||
653 dis->axmap[1][0] != 0 ||
654 dis->axmap[1][1] != 1 ||
655 dis->offset[0][0] != wcs->crpix[0] ||
656 dis->offset[0][1] != wcs->crpix[1] ||
657 dis->offset[1][0] != wcs->crpix[0] ||
658 dis->offset[1][1] != wcs->crpix[1] ||
659 dis->scale[0][0] != 1.0 ||
660 dis->scale[0][1] != 1.0 ||
661 dis->scale[1][0] != 1.0 ||
662 dis->scale[1][1] != 1.0) {
663 // Must have been read as a 'SIP' distortion, CPDISja = 'SIP'.
664 // Cannot be written as native SIP so write it as TPD.
665 dotpd = DIS_DOTPD;
666 } else if (strncmp(wcs->ctype[0], "RA---TAN", 8) ||
667 strncmp(wcs->ctype[1], "DEC--TAN", 8)) {
668 // Must have been permuted by wcssub().
669 // Native SIP doesn't have axis mapping so write it as TPD.
670 dotpd = DIS_DOTPD;
671 }
672
673 if (dotpd) {
674 strcpy(tpdsrc, "SIP coordinates");
675 } else {
676 dosip = 1;
677 }
678 }
679
680 break;
681 }
682 }
683 }
684
685 if ((dis = wcs->lin.disseq)) {
686 for (i = 0; i < naxis; i++) {
687 if (strcmp(dis->dtype[i], "TPV") == 0) {
688 // TPV "projection". Write it in its native form if possible,
689 // unless specifically requested to write it as TPD.
690 dotpd = (dis->iparm[i][I_DTYPE] & DIS_DOTPD);
691
692 if (!dotpd) {;
693 if (dis->axmap[wcs->lng][0] != wcs->lng ||
694 dis->axmap[wcs->lng][1] != wcs->lat ||
695 dis->axmap[wcs->lat][0] != wcs->lat ||
696 dis->axmap[wcs->lat][1] != wcs->lng ||
697 dis->offset[wcs->lng][wcs->lng] != 0.0 ||
698 dis->offset[wcs->lng][wcs->lat] != 0.0 ||
699 dis->offset[wcs->lat][wcs->lng] != 0.0 ||
700 dis->offset[wcs->lat][wcs->lat] != 0.0 ||
701 dis->scale[wcs->lng][wcs->lng] != 1.0 ||
702 dis->scale[wcs->lng][wcs->lat] != 1.0 ||
703 dis->scale[wcs->lat][wcs->lng] != 1.0 ||
704 dis->scale[wcs->lat][wcs->lat] != 1.0) {
705 // Must have been read as a 'TPV' distortion, CPDISja = 'TPV'.
706 // Cannot be written as native TPV so write it as TPD.
707 dotpd = DIS_DOTPD;
708 }
709
710 if (dotpd) {
711 strcpy(tpdsrc, "TPV \"projection\"");
712 } else {
713 dotpv = 1;
714 }
715 }
716
717 break;
718
719 } else if (strcmp(dis->dtype[i], "DSS") == 0) {
720 // Always written as TPD.
721 dotpd = DIS_DOTPD;
722 strcpy(tpdsrc, dis->dtype[i]);
723
724 } else if (strncmp(dis->dtype[i], "WAT", 3) == 0) {
725 // Always written as TPD.
726 dotpd = DIS_DOTPD;
727 strcpy(tpdsrc, dis->dtype[i]+4);
728
729 if (strcmp(dis->dtype[i], "DSS") == 0) {
730 strcpy(tpdsrc, wcs->wcsname);
731 } else {
732 strcat(tpdsrc, " \"projection\"");
733 }
734
735 break;
736 }
737 }
738 }
739
740 // Coordinate type.
741 for (i = 0; i < naxis; i++) {
742 if (wcs->ctype[i][0] == '\0') continue;
743
744 sprintf(keyvalue, "'%s'", wcs->ctype[i]);
745 strcpy(comment, "Coordinate type code");
746
747 ctypei = keyvalue + 1;
748 if (i == wcs->lng || i == wcs->lat) {
749 // Alter ctype for particular distortions.
750 if (dosip) {
751 // It could have come in as CPDISja = 'SIP'.
752 strcpy(ctypei+8, "-SIP'");
753 } else if (dotpv) {
754 // Reinstate projection code edited by wcsset().
755 strcpy(ctypei+4, "-TPV'");
756 }
757
758 if (strncmp(ctypei+8, "-SIP", 4) == 0) {
759 strcpy(comment, "TAN (gnomonic) projection + SIP distortions");
760
761 } else if (strncmp(ctypei+4, "-TPV", 4) == 0) {
762 strcpy(comment, "TAN (gnomonic) projection + distortions");
763
764 } else {
765 if (strncmp(ctypei, "RA--", 4) == 0) {
766 strcpy(comment, "Right ascension, ");
767
768 } else if (strncmp(ctypei, "DEC-", 4) == 0) {
769 strcpy(comment, "Declination, ");
770
771 } else if (strncmp(ctypei+1, "LON", 3) == 0 ||
772 strncmp(ctypei+1, "LAT", 3) == 0) {
773 ctypei[0] = toupper(ctypei[0]);
774
775 switch (ctypei[0]) {
776 case 'G':
777 strcpy(comment, "galactic ");
778 break;
779 case 'E':
780 strcpy(comment, "ecliptic ");
781 break;
782 case 'H':
783 strcpy(comment, "helioecliptic ");
784 break;
785 case 'S':
786 strcpy(comment, "supergalactic ");
787 break;
788 }
789
790 if (i == wcs->lng) {
791 strcat(comment, "longitude, ");
792 } else {
793 strcat(comment, "latitude, ");
794 }
795 }
796
797 strcat(comment, wcs->cel.prj.name);
798 strcat(comment, " projection");
799 }
800
801 } else if (i == wcs->spec) {
802 spctyp(wcs->ctype[i], 0x0, 0x0, comment, 0x0, &ptype, &xtype, 0x0);
803 if (ptype == xtype) {
804 strcat(comment, " (linear)");
805 } else {
806 switch (xtype) {
807 case 'F':
808 strcat(comment, " (linear in frequency)");
809 break;
810 case 'V':
811 strcat(comment, " (linear in velocity)");
812 break;
813 case 'W':
814 strcat(comment, " (linear in wavelength)");
815 break;
816 }
817 }
818 }
819
820 wcshdo_util(ctrl, "CTYPE", "CTY", WCSHDO_CRPXna, "CTYP", i+1, 0, 0, alt,
821 colnum, colax, keyvalue, comment, nkeyrec, header, &status);
822 }
823
824 // Coordinate value at reference point.
825 for (i = 0; i < naxis; i++) {
826 if (dofmt) wcshdo_format('G', 1, wcs->crval+i, format);
827 wcsutil_double2str(keyvalue, format, wcs->crval[i]);
828 comment[0] = '\0';
829 if (wcs->cunit[i][0]) sprintf(comment, "[%s] ", wcs->cunit[i]);
830 strcat(comment, "Coordinate value at reference point");
831 wcshdo_util(ctrl, "CRVAL", "CRV", WCSHDO_CRPXna, "CRVL", i+1, 0, 0, alt,
832 colnum, colax, keyvalue, comment, nkeyrec, header, &status);
833 }
834
835 // Parameter values.
836 if (dofmt) strcpy(format, "%20.12G");
837 for (k = 0; k < wcs->npv; k++) {
838 wcsutil_double2str(keyvalue, format, (wcs->pv[k]).value);
839 if ((wcs->pv[k]).i == (wcs->lng + 1)) {
840 switch ((wcs->pv[k]).m) {
841 case 1:
842 strcpy(comment, "[deg] Native longitude of the reference point");
843 break;
844 case 2:
845 strcpy(comment, "[deg] Native latitude of the reference point");
846 break;
847 case 3:
848 if (primage) {
849 sprintf(keyword, "LONPOLE%c", alt);
850 } else if (bintab) {
851 sprintf(keyword, "LONP%d%c", colnum, alt);
852 } else {
853 sprintf(keyword, "LONP%d%c", colax[(wcs->pv[k]).i - 1], alt);
854 }
855 sprintf(comment, "[deg] alias for %s (has precedence)", keyword);
856 break;
857 case 4:
858 if (primage) {
859 sprintf(keyword, "LATPOLE%c", alt);
860 } else if (bintab) {
861 sprintf(keyword, "LATP%d%c", colnum, alt);
862 } else {
863 sprintf(keyword, "LATP%d%c", colax[(wcs->pv[k]).i - 1], alt);
864 }
865 sprintf(comment, "[deg] alias for %s (has precedence)", keyword);
866 break;
867 }
868 } else if ((wcs->pv[k]).i == (wcs->lat + 1)) {
869 sprintf(comment, "%s projection parameter", wcs->cel.prj.code);
870 } else {
871 strcpy(comment, "Coordinate transformation parameter");
872 }
873
874 wcshdo_util(ctrl, "PV", "V", WCSHDO_PVn_ma, "PV", wcs->pv[k].i, -1,
875 wcs->pv[k].m, alt, colnum, colax, keyvalue, comment,
876 nkeyrec, header, &status);
877 }
878
879 for (k = 0; k < wcs->nps; k++) {
880 sprintf(keyvalue, "'%s'", (wcs->ps[k]).value);
881 wcshdo_util(ctrl, "PS", "S", WCSHDO_PVn_ma, "PS", wcs->ps[k].i, -1,
882 wcs->ps[k].m, alt, colnum, colax, keyvalue,
883 "Coordinate transformation parameter",
884 nkeyrec, header, &status);
885 }
886
887 // Celestial and spectral transformation parameters.
888 if (!undefined(wcs->lonpole)) {
889 wcsutil_double2str(keyvalue, format, wcs->lonpole);
890 wcshdo_util(ctrl, "LONPOLE", "LONP", 0, 0x0, 0, 0, 0, alt,
891 colnum, colax, keyvalue, "[deg] Native longitude of celestial pole",
892 nkeyrec, header, &status);
893 }
894
895 if (!undefined(wcs->latpole)) {
896 wcsutil_double2str(keyvalue, format, wcs->latpole);
897 wcshdo_util(ctrl, "LATPOLE", "LATP", 0, 0x0, 0, 0, 0, alt,
898 colnum, colax, keyvalue, "[deg] Native latitude of celestial pole",
899 nkeyrec, header, &status);
900 }
901
902 if (wcs->restfrq != 0.0) {
903 wcsutil_double2str(keyvalue, format, wcs->restfrq);
904 wcshdo_util(ctrl, "RESTFRQ", "RFRQ", 0, 0x0, 0, 0, 0, alt,
905 colnum, colax, keyvalue, "[Hz] Line rest frequency",
906 nkeyrec, header, &status);
907 }
908
909 if (wcs->restwav != 0.0) {
910 wcsutil_double2str(keyvalue, format, wcs->restwav);
911 wcshdo_util(ctrl, "RESTWAV", "RWAV", 0, 0x0, 0, 0, 0, alt,
912 colnum, colax, keyvalue, "[Hz] Line rest wavelength",
913 nkeyrec, header, &status);
914 }
915
916 // - - - - - - - - - - - - - - - - - Auxiliary coordinate axis information.
917 sprintf(timeunit, "%.15s", wcs->timeunit[0] ? wcs->timeunit : "s");
918
919 // Coordinate axis title.
920 if (wcs->cname) {
921 for (i = 0; i < naxis; i++) {
922 if (wcs->cname[i][0] == '\0') continue;
923
924 sprintf(keyvalue, "'%s'", wcs->cname[i]);
925 wcshdo_util(ctrl, "CNAME", "CNA", WCSHDO_CNAMna, "CNAM", i+1, 0, 0,
926 alt, colnum, colax, keyvalue, "Axis name for labelling purposes",
927 nkeyrec, header, &status);
928 }
929 }
930
931 // Random error in coordinate.
932 if (wcs->crder) {
933 for (i = 0; i < naxis; i++) {
934 if (undefined(wcs->crder[i])) continue;
935
936 wcsutil_double2str(keyvalue, format, wcs->crder[i]);
937 comment[0] = '\0';
938 if (wcs->cunit[i][0]) sprintf(comment, "[%s] ", wcs->cunit[i]);
939 strcat(comment, "Random error in coordinate");
940 wcshdo_util(ctrl, "CRDER", "CRD", WCSHDO_CNAMna, "CRDE", i+1, 0, 0,
941 alt, colnum, colax, keyvalue, comment, nkeyrec, header, &status);
942 }
943 }
944
945 // Systematic error in coordinate.
946 if (wcs->csyer) {
947 for (i = 0; i < naxis; i++) {
948 if (undefined(wcs->csyer[i])) continue;
949
950 wcsutil_double2str(keyvalue, format, wcs->csyer[i]);
951 comment[0] = '\0';
952 if (wcs->cunit[i][0]) sprintf(comment, "[%s] ", wcs->cunit[i]);
953 strcat(comment, "Systematic error in coordinate");
954 wcshdo_util(ctrl, "CSYER", "CSY", WCSHDO_CNAMna, "CSYE", i+1, 0, 0,
955 alt, colnum, colax, keyvalue, comment, nkeyrec, header, &status);
956 }
957 }
958
959 // Time at zero point of phase axis.
960 if (wcs->czphs) {
961 for (i = 0; i < naxis; i++) {
962 if (undefined(wcs->czphs[i])) continue;
963
964 wcsutil_double2str(keyvalue, format, wcs->czphs[i]);
965 sprintf(comment, "[%s] Time at zero point of phase axis", timeunit);
966 wcshdo_util(ctrl, "CZPHS", "CZP", WCSHDO_CNAMna, "CZPH", i+1, 0, 0,
967 alt, colnum, colax, keyvalue, comment, nkeyrec, header, &status);
968 }
969 }
970
971 // Period of phase axis.
972 if (wcs->cperi) {
973 for (i = 0; i < naxis; i++) {
974 if (undefined(wcs->cperi[i])) continue;
975
976 wcsutil_double2str(keyvalue, format, wcs->cperi[i]);
977 sprintf(comment, "[%s] Period of phase axis", timeunit);
978 wcshdo_util(ctrl, "CPERI", "CPR", WCSHDO_CNAMna, "CPER", i+1, 0, 0,
979 alt, colnum, colax, keyvalue, comment, nkeyrec, header, &status);
980 }
981 }
982
983 // - - - - - - - - - - - - - - - - - - - - - - - - Coordinate system title.
984
985 // Coordinate system title.
986 if (wcs->wcsname[0]) {
987 sprintf(keyvalue, "'%s'", wcs->wcsname);
988 if (bintab) {
989 wcshdo_util(ctrl, "WCSNAME", "WCSN", 0, 0x0, 0, 0, 0, alt,
990 colnum, colax, keyvalue, "Coordinate system title",
991 nkeyrec, header, &status);
992 } else {
993 // TWCS was a mistake.
994 wcshdo_util(ctrl, "WCSNAME", "TWCS", WCSHDO_WCSNna, "WCSN", 0, 0, 0,
995 alt, colnum, colax, keyvalue, "Coordinate system title",
996 nkeyrec, header, &status);
997 }
998 }
999
1000 // - - - - - - - - - - - - - - - - - Time reference system and measurement.
1001
1002 // Time scale.
1003 if (wcs->timesys[0]) {
1004 sprintf(keyvalue, "'%s'", wcs->timesys);
1005 wcshdo_util(ctrl, "TIMESYS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1006 "Time scale", nkeyrec, header, &status);
1007 }
1008
1009 // Time reference position.
1010 if (wcs->trefpos[0]) {
1011 sprintf(keyvalue, "'%s'", wcs->trefpos);
1012 wcshdo_util(ctrl, "TREFPOS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1013 "Time reference position", nkeyrec, header, &status);
1014 }
1015
1016 // Time reference direction.
1017 if (wcs->trefdir[0]) {
1018 sprintf(keyvalue, "'%s'", wcs->trefdir);
1019 wcshdo_util(ctrl, "TREFDIR", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1020 "Time reference direction", nkeyrec, header, &status);
1021 }
1022
1023 // Ephemerides used for pathlength delay calculation.
1024 if (wcs->plephem[0]) {
1025 sprintf(keyvalue, "'%s'", wcs->plephem);
1026 wcshdo_util(ctrl, "PLEPHEM", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1027 "Ephemerides used for pathlength delays", nkeyrec, header, &status);
1028 }
1029
1030 // Time units.
1031 if (wcs->timeunit[0]) {
1032 sprintf(keyvalue, "'%s'", wcs->timeunit);
1033 wcshdo_util(ctrl, "TIMEUNIT", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1034 "Time units", nkeyrec, header, &status);
1035 }
1036
1037 // Fiducial (reference) time.
1038 if (wcs->mjdref[0] == 0.0 && wcs->mjdref[1] == 0.0) {
1039 // MJD of fiducial time (simplified if it takes its default value).
1040 wcsutil_double2str(keyvalue, format, 0.0);
1041 wcshdo_util(ctrl, "MJDREF", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1042 "[d] MJD of fiducial time", nkeyrec, header, &status);
1043
1044 } else {
1045 // ISO-8601 fiducial time.
1046 if (wcs->dateref[0]) {
1047 sprintf(keyvalue, "'%s'", wcs->dateref);
1048 wcshdo_util(ctrl, "DATEREF", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1049 keyvalue, "ISO-8601 fiducial time", nkeyrec, header, &status);
1050 }
1051
1052 if (wcs->mjdref[1] == 0.0) {
1053 // MJD of fiducial time (no fractional part).
1054 if (!undefined(wcs->mjdref[0])) {
1055 wcsutil_double2str(keyvalue, format, wcs->mjdref[0]);
1056 wcshdo_util(ctrl, "MJDREF", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1057 keyvalue, "[d] MJD of fiducial time", nkeyrec, header, &status);
1058 }
1059
1060 } else {
1061 // MJD of fiducial time, integer part.
1062 if (!undefined(wcs->mjdref[0])) {
1063 wcsutil_double2str(keyvalue, format, wcs->mjdref[0]);
1064 wcshdo_util(ctrl, "MJDREFI", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1065 keyvalue, "[d] MJD of fiducial time, integer part", nkeyrec,
1066 header, &status);
1067 }
1068
1069 // MJD of fiducial time, fractional part.
1070 if (!undefined(wcs->mjdref[1])) {
1071 wcsutil_double2str(keyvalue, format, wcs->mjdref[1]);
1072 wcshdo_util(ctrl, "MJDREFF", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1073 keyvalue, "[d] MJD of fiducial time, fractional part", nkeyrec,
1074 header, &status);
1075 }
1076 }
1077 }
1078
1079 // Clock correction.
1080 if (!undefined(wcs->timeoffs)) {
1081 wcsutil_double2str(keyvalue, format, wcs->timeoffs);
1082 sprintf(comment, "[%s] Clock correction", timeunit);
1083 wcshdo_util(ctrl, "TIMEOFFS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1084 comment, nkeyrec, header, &status);
1085 }
1086
1087 // - - - - - - - - - - - - - - - - - - - - - Data timestamps and durations.
1088
1089 // ISO-8601 time of observation.
1090 if (wcs->dateobs[0]) {
1091 sprintf(keyvalue, "'%s'", wcs->dateobs);
1092 strcpy(comment, "ISO-8601 time of observation");
1093
1094 if (ctrl & 1) {
1095 // Allow DOBSn.
1096 wcshdo_util(ctrl, "DATE-OBS", "DOBS", WCSHDO_DOBSn, 0x0, 0, 0, 0, ' ',
1097 colnum, colax, keyvalue, comment, nkeyrec, header, &status);
1098 } else {
1099 // Force DATE-OBS.
1100 wcshdo_util(ctrl, "DATE-OBS", 0x0, 0, 0x0, 0, 0, 0, ' ',
1101 0, 0x0, keyvalue, comment, nkeyrec, header, &status);
1102 }
1103 }
1104
1105 // MJD of observation.
1106 if (!undefined(wcs->mjdobs)) {
1107 wcsutil_double2str(keyvalue, format, wcs->mjdobs);
1108 wcshdo_util(ctrl, "MJD-OBS", "MJDOB", 0, 0x0, 0, 0, 0, ' ',
1109 colnum, colax, keyvalue, "[d] MJD of observation",
1110 nkeyrec, header, &status);
1111 }
1112
1113 // Julian epoch of observation.
1114 if (!undefined(wcs->jepoch)) {
1115 wcsutil_double2str(keyvalue, format, wcs->jepoch);
1116 wcshdo_util(ctrl, "JEPOCH", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1117 "[a] Julian epoch of observation", nkeyrec, header, &status);
1118 }
1119
1120 // Besselian epoch of observation.
1121 if (!undefined(wcs->bepoch)) {
1122 wcsutil_double2str(keyvalue, format, wcs->bepoch);
1123 wcshdo_util(ctrl, "BEPOCH", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1124 "[a] Besselian epoch of observation", nkeyrec, header, &status);
1125 }
1126
1127 // ISO-8601 time at start of observation.
1128 if (wcs->datebeg[0]) {
1129 sprintf(keyvalue, "'%s'", wcs->datebeg);
1130 wcshdo_util(ctrl, "DATE-BEG", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1131 "ISO-8601 time at start of observation", nkeyrec, header, &status);
1132 }
1133
1134 // MJD at start of observation.
1135 if (!undefined(wcs->mjdbeg)) {
1136 wcsutil_double2str(keyvalue, format, wcs->mjdbeg);
1137 wcshdo_util(ctrl, "MJD-BEG", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1138 "[d] MJD at start of observation", nkeyrec, header, &status);
1139 }
1140
1141 // Time elapsed at start since fiducial time.
1142 if (!undefined(wcs->tstart)) {
1143 wcsutil_double2str(keyvalue, format, wcs->tstart);
1144 sprintf(comment, "[%s] Time elapsed since fiducial time at start",
1145 timeunit);
1146 wcshdo_util(ctrl, "TSTART", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1147 comment, nkeyrec, header, &status);
1148 }
1149
1150 // ISO-8601 time at midpoint of observation.
1151 if (wcs->dateavg[0]) {
1152 sprintf(keyvalue, "'%s'", wcs->dateavg);
1153 wcshdo_util(ctrl, "DATE-AVG", "DAVG", 0, 0x0, 0, 0, 0, ' ',
1154 colnum, colax, keyvalue, "ISO-8601 time at midpoint of observation",
1155 nkeyrec, header, &status);
1156 }
1157
1158 // MJD at midpoint of observation.
1159 if (!undefined(wcs->mjdavg)) {
1160 wcsutil_double2str(keyvalue, format, wcs->mjdavg);
1161 wcshdo_util(ctrl, "MJD-AVG", "MJDA", 0, 0x0, 0, 0, 0, ' ',
1162 colnum, colax, keyvalue, "[d] MJD at midpoint of observation",
1163 nkeyrec, header, &status);
1164 }
1165
1166 // ISO-8601 time at end of observation.
1167 if (wcs->dateend[0]) {
1168 sprintf(keyvalue, "'%s'", wcs->dateend);
1169 wcshdo_util(ctrl, "DATE-END", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1170 "ISO-8601 time at end of observation", nkeyrec, header, &status);
1171 }
1172
1173 // MJD at end of observation.
1174 if (!undefined(wcs->mjdend)) {
1175 wcsutil_double2str(keyvalue, format, wcs->mjdend);
1176 wcshdo_util(ctrl, "MJD-END", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1177 "[d] MJD at end of observation", nkeyrec, header, &status);
1178 }
1179
1180 // Time elapsed at end since fiducial time.
1181 if (!undefined(wcs->tstop)) {
1182 wcsutil_double2str(keyvalue, format, wcs->tstop);
1183 sprintf(comment, "[%s] Time elapsed since fiducial time at end",
1184 timeunit);
1185 wcshdo_util(ctrl, "TSTOP", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1186 comment, nkeyrec, header, &status);
1187 }
1188
1189 // Exposure (integration) time.
1190 if (!undefined(wcs->xposure)) {
1191 wcsutil_double2str(keyvalue, format, wcs->xposure);
1192 sprintf(comment, "[%s] Exposure (integration) time", timeunit);
1193 wcshdo_util(ctrl, "XPOSURE", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1194 comment, nkeyrec, header, &status);
1195 }
1196
1197 // Elapsed time (start to stop).
1198 if (!undefined(wcs->telapse)) {
1199 wcsutil_double2str(keyvalue, format, wcs->telapse);
1200 sprintf(comment, "[%s] Elapsed time (start to stop)", timeunit);
1201 wcshdo_util(ctrl, "TELAPSE", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1202 comment, nkeyrec, header, &status);
1203 }
1204
1205 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - Timing accuracy.
1206
1207 // Systematic error in time measurements.
1208 if (!undefined(wcs->timsyer)) {
1209 wcsutil_double2str(keyvalue, format, wcs->timsyer);
1210 sprintf(comment, "[%s] Systematic error in time measurements", timeunit);
1211 wcshdo_util(ctrl, "TIMSYER", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1212 comment, nkeyrec, header, &status);
1213 }
1214
1215 // Relative error in time measurements.
1216 if (!undefined(wcs->timrder)) {
1217 wcsutil_double2str(keyvalue, format, wcs->timrder);
1218 sprintf(comment, "[%s] Relative error in time measurements", timeunit);
1219 wcshdo_util(ctrl, "TIMRDER", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1220 comment, nkeyrec, header, &status);
1221 }
1222
1223 // Time resolution.
1224 if (!undefined(wcs->timedel)) {
1225 wcsutil_double2str(keyvalue, format, wcs->timedel);
1226 sprintf(comment, "[%s] Time resolution", timeunit);
1227 wcshdo_util(ctrl, "TIMEDEL", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1228 comment, nkeyrec, header, &status);
1229 }
1230
1231 // Reference position of timestamp in binned data.
1232 if (!undefined(wcs->timepixr)) {
1233 wcsutil_double2str(keyvalue, format, wcs->timepixr);
1234 wcshdo_util(ctrl, "TIMEPIXR", "", 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1235 "Reference position of timestamp in binned data", nkeyrec, header,
1236 &status);
1237 }
1238
1239 // - - - - - - - - - - - - - - - - - - Spatial & celestial reference frame.
1240
1241 // Observatory coordinates.
1242 if (!undefined(wcs->obsgeo[0]) &&
1243 !undefined(wcs->obsgeo[1]) &&
1244 !undefined(wcs->obsgeo[2])) {
1245
1246 for (k = 0; k < 3; k++) {
1247 wcsutil_double2str(keyvalue, format, wcs->obsgeo[k]);
1248 sprintf(comment, "[m] observatory %c-coordinate", xyz[k]);
1249 obsgeo[7] = xyz[k];
1250 obsg[4] = xyz[k];
1251 wcshdo_util(ctrl, obsgeo, obsg, 0, 0x0, 0, 0, 0, ' ',
1252 colnum, colax, keyvalue, comment, nkeyrec, header, &status);
1253 }
1254
1255 } else if (
1256 !undefined(wcs->obsgeo[3]) &&
1257 !undefined(wcs->obsgeo[4]) &&
1258 !undefined(wcs->obsgeo[5])) {
1259
1260 wcsutil_double2str(keyvalue, format, wcs->obsgeo[3]);
1261 wcshdo_util(ctrl, "OBSGEO-L", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1262 "[deg] IAU(1976) observatory longitude", nkeyrec, header, &status);
1263
1264 wcsutil_double2str(keyvalue, format, wcs->obsgeo[4]);
1265 wcshdo_util(ctrl, "OBSGEO-B", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1266 "[deg] IAU(1976) observatory latitude", nkeyrec, header, &status);
1267
1268 wcsutil_double2str(keyvalue, format, wcs->obsgeo[5]);
1269 wcshdo_util(ctrl, "OBSGEO-L", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1270 "[m] IAU(1976) observatory height", nkeyrec, header, &status);
1271 }
1272
1273 // Spacecraft orbit ephemeris file.
1274 if (wcs->obsorbit[0]) {
1275 sprintf(keyvalue, "'%s'", wcs->obsorbit);
1276 wcshdo_util(ctrl, "OBSORBIT", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0, keyvalue,
1277 "Spacecraft orbit ephemeris file", nkeyrec, header, &status);
1278 }
1279
1280 // Equatorial coordinate system type.
1281 if (wcs->radesys[0]) {
1282 sprintf(keyvalue, "'%s'", wcs->radesys);
1283 wcshdo_util(ctrl, "RADESYS", "RADE", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1284 keyvalue, "Equatorial coordinate system", nkeyrec, header, &status);
1285 }
1286
1287 // Equinox of equatorial coordinate system.
1288 if (!undefined(wcs->equinox)) {
1289 wcsutil_double2str(keyvalue, format, wcs->equinox);
1290 wcshdo_util(ctrl, "EQUINOX", "EQUI", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1291 keyvalue, "[yr] Equinox of equatorial coordinates", nkeyrec, header,
1292 &status);
1293 }
1294
1295 // Reference frame of spectral coordinates.
1296 if (wcs->specsys[0]) {
1297 sprintf(keyvalue, "'%s'", wcs->specsys);
1298 wcshdo_util(ctrl, "SPECSYS", "SPEC", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1299 keyvalue, "Reference frame of spectral coordinates", nkeyrec, header,
1300 &status);
1301 }
1302
1303 // Reference frame of spectral observation.
1304 if (wcs->ssysobs[0]) {
1305 sprintf(keyvalue, "'%s'", wcs->ssysobs);
1306 wcshdo_util(ctrl, "SSYSOBS", "SOBS", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1307 keyvalue, "Reference frame of spectral observation", nkeyrec, header,
1308 &status);
1309 }
1310
1311 // Observer's velocity towards source.
1312 if (!undefined(wcs->velosys)) {
1313 wcsutil_double2str(keyvalue, format, wcs->velosys);
1314 wcshdo_util(ctrl, "VELOSYS", "VSYS", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1315 keyvalue, "[m/s] Velocity towards source", nkeyrec, header, &status);
1316 }
1317
1318 // Redshift of the source.
1319 if (!undefined(wcs->zsource)) {
1320 wcsutil_double2str(keyvalue, format, wcs->zsource);
1321 wcshdo_util(ctrl, "ZSOURCE", "ZSOU", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1322 keyvalue, "Redshift of the source", nkeyrec, header, &status);
1323 }
1324
1325 // Reference frame of source redshift.
1326 if (wcs->ssyssrc[0]) {
1327 sprintf(keyvalue, "'%s'", wcs->ssyssrc);
1328 wcshdo_util(ctrl, "SSYSSRC", "SSRC", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1329 keyvalue, "Reference frame of source redshift", nkeyrec, header,
1330 &status);
1331 }
1332
1333 // Velocity orientation angle.
1334 if (!undefined(wcs->velangl)) {
1335 wcsutil_double2str(keyvalue, format, wcs->velangl);
1336 wcshdo_util(ctrl, "VELANGL", "VANG", 0, 0x0, 0, 0, 0, alt, colnum, colax,
1337 keyvalue, "[deg] Velocity orientation angle", nkeyrec, header, &status);
1338 }
1339
1340 // - - - - - - - - - - - - - - - - - - - - Additional auxiliary parameters.
1341
1342 if ((aux = wcs->aux)) {
1343 if (!undefined(aux->rsun_ref)) {
1344 wcsutil_double2str(keyvalue, format, aux->rsun_ref);
1345 wcshdo_util(ctrl, "RSUN_REF", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1346 keyvalue, "[m] Solar radius", nkeyrec, header, &status);
1347 }
1348
1349 if (!undefined(aux->dsun_obs)) {
1350 wcsutil_double2str(keyvalue, format, aux->dsun_obs);
1351 wcshdo_util(ctrl, "DSUN_OBS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1352 keyvalue, "[m] Distance from centre of Sun to observer", nkeyrec,
1353 header, &status);
1354 }
1355
1356 if (!undefined(aux->crln_obs)) {
1357 wcsutil_double2str(keyvalue, format, aux->crln_obs);
1358 wcshdo_util(ctrl, "CRLN_OBS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1359 keyvalue, "[deg] Carrington heliographic lng of observer", nkeyrec,
1360 header, &status);
1361
1362 if (!undefined(aux->hglt_obs)) {
1363 wcsutil_double2str(keyvalue, format, aux->hglt_obs);
1364 wcshdo_util(ctrl, "CRLT_OBS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1365 keyvalue, "[deg] Heliographic latitude of observer", nkeyrec,
1366 header, &status);
1367 }
1368 }
1369
1370 if (!undefined(aux->hgln_obs)) {
1371 wcsutil_double2str(keyvalue, format, aux->hgln_obs);
1372 wcshdo_util(ctrl, "HGLN_OBS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1373 keyvalue, "[deg] Stonyhurst heliographic lng of observer", nkeyrec,
1374 header, &status);
1375
1376 if (!undefined(aux->hglt_obs)) {
1377 wcsutil_double2str(keyvalue, format, aux->hglt_obs);
1378 wcshdo_util(ctrl, "HGLT_OBS", 0x0, 0, 0x0, 0, 0, 0, ' ', 0, 0x0,
1379 keyvalue, "[deg] Heliographic latitude of observer", nkeyrec,
1380 header, &status);
1381 }
1382 }
1383 }
1384
1385 // - - - - - - - - - - - - - - - - - - - - - Distortion function parameters.
1386
1387 if (dosip) {
1388 // Simple Imaging Polynomial (SIP) is handled by translating its dpkey
1389 // records. Determine a suitable numerical precision for the
1390 // polynomial coefficients to avoid trailing zeroes common to all of
1391 // them.
1392 dis = wcs->lin.dispre;
1393 if (dofmt) {
1394 keyp = dis->dp;
1395 kp0 = 2;
1396 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1397 cp = strchr(keyp->field, '.') + 1;
1398 if (strncmp(cp, "SIP.", 4) != 0) continue;
1399 wcsutil_double2str(keyvalue, "%20.13E", dpkeyd(keyp));
1400
1401 kpi = 15;
1402 while (kp0 < kpi && keyvalue[kpi] == '0') kpi--;
1403 kp0 = kpi;
1404 }
1405
1406 precision = kp0 - 2;
1407 if (precision < 1) precision = 1;
1408 if (13 < precision) precision = 13;
1409 sprintf(format, "%%20.%dE", precision);
1410 }
1411
1412 // Ensure the coefficients are written in a human-readable sequence.
1413 for (j = 0; j <= 1; j++) {
1414 // Distortion function polynomial coefficients.
1415 wcshdo_util(ctrl, "", "", 0, 0x0, 0, 0, 0, ' ', 0, 0, "", "",
1416 nkeyrec, header, &status);
1417
1418 if (j == 0) {
1419 strcpy(keyword, "A_");
1420 } else {
1421 strcpy(keyword, "B_");
1422 }
1423
1424 ncoeff = dis->iparm[j][I_TPDNCO];
1425 for (degree = 0; degree <= 9; degree++) {
1426 if (ncoeff <= nTPD[degree]) break;
1427 }
1428
1429 strcpy(keyword+2, "ORDER");
1430 sprintf(keyvalue, "%20d", degree);
1431 sprintf(comment, "SIP polynomial degree, axis %d, pixel-to-sky", j+1);
1432 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, ' ', 0, 0,
1433 keyvalue, comment, nkeyrec, header, &status);
1434
1435 keyp = dis->dp;
1436 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1437 if (keyp->j != j+1) continue;
1438 if ((keyval = dpkeyd(keyp)) == 0.0) continue;
1439
1440 cp = strchr(keyp->field, '.') + 1;
1441 if (strncmp(cp, "SIP.FWD.", 8) != 0) continue;
1442 cp += 8;
1443 strcpy(keyword+2, cp);
1444 sscanf(cp, "%d_%d", &p, &q);
1445 strncpy(term, "xxxxxxxxx", p);
1446 strncpy(term+p, "yyyyyyyyy", q);
1447 term[p+q] = '\0';
1448
1449 wcsutil_double2str(keyvalue, format, keyval);
1450 sprintf(comment, "SIP distortion coefficient: %s", term);
1451 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, ' ', 0, 0,
1452 keyvalue, comment, nkeyrec, header, &status);
1453 }
1454
1455 if (dis->maxdis[j] != 0.0) {
1456 strcpy(keyword+2, "DMAX");
1457 wcsutil_double2str(keyvalue, "%20.3f", dis->maxdis[j]);
1458 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, ' ', 0, 0,
1459 keyvalue, "Maximum value of distortion function", nkeyrec,
1460 header, &status);
1461 }
1462
1463 // Inverse distortion function polynomial coefficients.
1464 if (dis->disx2p == 0x0) continue;
1465
1466 wcshdo_util(ctrl, "", "", 0, 0x0, 0, 0, 0, ' ', 0, 0, "", "",
1467 nkeyrec, header, &status);
1468
1469 if (j == 0) {
1470 strcpy(keyword, "AP_");
1471 } else {
1472 strcpy(keyword, "BP_");
1473 }
1474
1475 ncoeff = dis->iparm[j][I_NDPARM] - dis->iparm[j][I_TPDNCO];
1476 for (degree = 0; degree <= 9; degree++) {
1477 if (ncoeff <= nTPD[degree]) break;
1478 }
1479
1480 strcpy(keyword+3, "ORDER");
1481 sprintf(keyvalue, "%20d", degree);
1482 sprintf(comment, "SIP polynomial degree, axis %d, sky-to-pixel", j+1);
1483 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, ' ', 0, 0,
1484 keyvalue, comment, nkeyrec, header, &status);
1485
1486 keyp = dis->dp;
1487 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1488 if (keyp->j != j+1) continue;
1489 if ((keyval = dpkeyd(keyp)) == 0.0) continue;
1490
1491 cp = strchr(keyp->field, '.') + 1;
1492 if (strncmp(cp, "SIP.REV.", 8) != 0) continue;
1493 cp += 8;
1494 strcpy(keyword+3, cp);
1495 sscanf(cp, "%d_%d", &p, &q);
1496 strncpy(term, "xxxxxxxxx", p);
1497 strncpy(term+p, "yyyyyyyyy", q);
1498 term[p+q] = '\0';
1499
1500 wcsutil_double2str(keyvalue, format, keyval);
1501 sprintf(comment, "SIP inverse coefficient: %s", term);
1502 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, ' ', 0, 0,
1503 keyvalue, comment, nkeyrec, header, &status);
1504 }
1505 }
1506 }
1507
1508 for (idis = 0; idis < 2; idis++) {
1509 if (idis == 0 && (dis = wcs->lin.dispre) == 0x0) continue;
1510 if (idis == 1 && (dis = wcs->lin.disseq) == 0x0) continue;
1511
1512 for (j = 0; j < naxis; j++) {
1513 if (dis->disp2x[j] == 0x0) continue;
1514
1515 iparm = dis->iparm[j];
1516 dparm = dis->dparm[j];
1517
1518 // Identify the distortion type.
1519 if (dotpv) {
1520 // TPV "projection" is handled by translating its dpkey records,
1521 // which were originally translated from PVi_ma by wcsset(), or
1522 // possibly input directly as a CQDISia = 'TPV' distortion type.
1523 // Determine a suitable numerical precision for the polynomial
1524 // coefficients to avoid trailing zeroes common to all of them.
1525 if (dofmt) wcshdo_format('E', iparm[I_NDPARM], dparm, format);
1526 sprintf(fmt01, "%.3ss", format);
1527
1528 wcshdo_util(ctrl, "", "", 0, 0x0, 0, 0, 0, ' ', 0, 0, "", "",
1529 nkeyrec, header, &status);
1530
1531 // Distortion function polynomial coefficients.
1532 sprintf(keyword, "PV%d_", j+1);
1533 kp = keyword + strlen(keyword);
1534
1535 keyp = dis->dp;
1536 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1537 if (keyp->j != j+1) continue;
1538 if ((keyval = dpkeyd(keyp)) == 0.0) continue;
1539
1540 cp = strchr(keyp->field, '.') + 1;
1541 if (strncmp(cp, "TPV.", 4) != 0) continue;
1542 strcpy(kp, cp+4);
1543
1544 // Identify the term of the TPV polynomial for human readers.
1545 sscanf(cp+4, "%d", &m);
1546 wcshdo_tpdterm(m, j == wcs->lng, term);
1547 sprintf(comment, "TPV coefficient: %s", term);
1548
1549 if (keyval == 1.0) {
1550 sprintf(keyvalue, fmt01, "1.0");
1551 } else {
1552 wcsutil_double2str(keyvalue, format, keyval);
1553 }
1554 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1555 keyvalue, comment, nkeyrec, header, &status);
1556 }
1557
1558 } else if (strcmp(dis->dtype[j], "TPD") == 0 || dotpd ||
1559 strcmp(dis->dtype[j], "Polynomial") == 0 ||
1560 strcmp(dis->dtype[j], "Polynomial*") == 0) {
1561 // One of the Paper IV type polynomial distortions.
1562 wcshdo_util(ctrl, "", "", 0, 0x0, 0, 0, 0, ' ', 0, 0, "", "",
1563 nkeyrec, header, &status);
1564
1565 if (strcmp(dis->dtype[j], "TPD") == 0) {
1566 // Pure TPD.
1567 dotpd = 1;
1568 } else if (strncmp(dis->dtype[j], "Polynomial", 10) == 0) {
1569 // Polynomial distortion. Write it as TPD by request?
1570 dotpd = (iparm[I_DTYPE] & DIS_DOTPD);
1571 strcpy(tpdsrc, "Polynomial distortion");
1572 }
1573
1574 pq = idis ? 'Q' : 'P';
1575 Nhat = dis->Nhat[j];
1576
1577 // CPDISja/CQDISia
1578 sprintf(keyword, "C%cDIS%d", pq, j+1);
1579 if (idis == 0) {
1580 strcpy(comment, "P = prior, ");
1581 } else {
1582 strcpy(comment, "Q = sequent, ");
1583 }
1584
1585 if (dotpd) {
1586 strcpy(keyvalue, "'TPD'");
1587 strcat(comment, "Template Polynomial Distortion");
1588
1589 // For identifying terms of the TPD polynomial.
1590 axmap = dis->axmap[j];
1591 direct = 1;
1592 doaux = iparm[I_TPDAUX];
1593 if (Nhat == 2) {
1594 // Associate x with longitude, y with latitude.
1595 if (axmap[0] == wcs->lng && axmap[1] == wcs->lat) {
1596 direct = 1;
1597 } else if (axmap[0] == wcs->lat && axmap[1] == wcs->lng) {
1598 direct = 0;
1599 } else {
1600 // Non-celestial.
1601 direct = (axmap[0] < axmap[1]);
1602 }
1603 }
1604 } else {
1605 strcpy(keyvalue, "'Polynomial'");
1606 strcat(comment, "general Polynomial distortion");
1607 }
1608
1609 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1610 keyvalue, comment, nkeyrec, header, &status);
1611
1612 // NAXES.
1613 sprintf(keyword, "D%c%d", pq, j+1);
1614 sprintf(keyvalue, "'NAXES: %d'", Nhat);
1615 if (Nhat == 1) {
1616 strcpy(comment, "One independent variable");
1617 } else if (Nhat == 2) {
1618 strcpy(comment, "Two independent variables");
1619 } else {
1620 strcpy(comment, "Number of independent variables");
1621 }
1622 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1623 keyvalue, comment, nkeyrec, header, &status);
1624
1625 // AXIS.jhat
1626 for (jhat = 0; jhat < Nhat; jhat++) {
1627 axmap = dis->axmap[j];
1628 sprintf(keyvalue, "'AXIS.%d: %d'", jhat+1, axmap[jhat]+1);
1629 if (jhat == 0) {
1630 strcpy(comment, "1st");
1631 } else if (jhat == 1) {
1632 strcpy(comment, "2nd");
1633 } else if (jhat == 2) {
1634 strcpy(comment, "3rd");
1635 } else {
1636 sprintf(comment, "%dth", jhat+1);
1637 }
1638
1639 sprintf(comment+strlen(comment), " independent variable: axis %d",
1640 axmap[jhat]+1);
1641 if (dotpd) {
1642 // axid is "xyxuvu".
1643 cp = axid;
1644 if (!direct) cp++;
1645 if (doaux) cp += 3;
1646 sprintf(comment+strlen(comment), " (= %c)", cp[jhat]);
1647 }
1648
1649 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1650 keyvalue, comment, nkeyrec, header, &status);
1651 }
1652
1653 // OFFSET.jhat
1654 if (dofmt) wcshdo_format('f', Nhat, dis->offset[j], format);
1655 for (jhat = 0; jhat < Nhat; jhat++) {
1656 if (dis->offset[j][jhat] == 0.0) continue;
1657
1658 wcsutil_double2str(ctemp, format, dis->offset[j][jhat]);
1659 sprintf(keyvalue, "'OFFSET.%d: %s'", jhat+1, ctemp);
1660 sprintf(comment, "Variable %d renormalization offset", jhat+1);
1661
1662 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1663 keyvalue, comment, nkeyrec, header, &status);
1664 }
1665
1666 // SCALE.jhat
1667 if (dofmt) wcshdo_format('f', Nhat, dis->scale[j], format);
1668 for (jhat = 0; jhat < Nhat; jhat++) {
1669 if (dis->scale[j][jhat] == 1.0) continue;
1670
1671 wcsutil_double2str(ctemp, format, dis->scale[j][jhat]);
1672 sprintf(keyvalue, "'SCALE.%d: %s'", jhat+1, ctemp);
1673 sprintf(comment, "Variable %d renormalization scale", jhat+1);
1674
1675 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1676 keyvalue, comment, nkeyrec, header, &status);
1677 }
1678
1679 // Does the distortion function compute a correction?
1680 if (dis->docorr[j]) {
1681 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1682 "'DOCORR: 1'", "Distortion function computes a correction",
1683 nkeyrec, header, &status);
1684 } else {
1685 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1686 "'DOCORR: 0'", "Distortion function computes coordinates",
1687 nkeyrec, header, &status);
1688 }
1689
1690 if (dotpd) {
1691 // Template Polynomial Distortion (TPD). As it may have been
1692 // translated from SIP, TPV, DSS, TNX, ZPX, or perhaps
1693 // Polynomial, the dpkey records may not relate to TPD.
1694 // Output is therefore handled via dparm.
1695 if (dofmt) wcshdo_format('E', iparm[I_NDPARM], dparm, format);
1696 sprintf(fmt01, "%.3ss", format);
1697
1698 // AUX.jhat.COEFF.m
1699 if (doaux) {
1700 for (idp = 0; idp < 6; idp++) {
1701 if (dparm[idp] == 0.0) {
1702 sprintf(ctemp, fmt01, "0.0");
1703 } else if (dparm[idp] == 1.0) {
1704 sprintf(ctemp, fmt01, "1.0");
1705 } else {
1706 wcsutil_double2str(ctemp, format, dparm[idp]);
1707 }
1708
1709 if (idp < 3) {
1710 sprintf(keyvalue, "'AUX.1.COEFF.%d: %s'", idp%3, ctemp);
1711 sprintf(comment, "TPD: x = c0 + c1*u + c2*v");
1712 } else {
1713 sprintf(keyvalue, "'AUX.2.COEFF.%d: %s'", idp%3, ctemp);
1714 sprintf(comment, "TPD: y = d0 + d1*u + d2*v");
1715 }
1716
1717 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1718 keyvalue, comment, nkeyrec, header, &status);
1719
1720 }
1721
1722 dparm += 6;
1723 }
1724
1725 // TPD.FWD.m
1726 for (idp = 0; idp < iparm[I_TPDNCO]; idp++) {
1727 if (dparm[idp] == 0.0) continue;
1728
1729 if (dparm[idp] == 1.0) {
1730 sprintf(ctemp, fmt01, "1.0");
1731 } else {
1732 wcsutil_double2str(ctemp, format, dparm[idp]);
1733 }
1734
1735 m = idp;
1736 sprintf(keyvalue, "'TPD.FWD.%d:%s %s'", m, (m<10)?" ":"", ctemp);
1737 wcshdo_tpdterm(m, direct, term);
1738 sprintf(comment, "TPD coefficient: %s", term);
1739
1740 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1741 keyvalue, comment, nkeyrec, header, &status);
1742 }
1743
1744 // CPERRja/CQERRia
1745 if (dis->maxdis[j] != 0.0) {
1746 sprintf(keyword, "C%cERR%d", pq, j+1);
1747 sprintf(keyvalue, "%20.2f", dis->maxdis[j]);
1748 sprintf(comment, "%sMaximum absolute value of distortion",
1749 idis?"":"[pix] ");
1750 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1751 keyvalue, comment, nkeyrec, header, &status);
1752 }
1753
1754 // Inverse distortion function polynomial coefficients.
1755 if (dis->disx2p[j] == 0x0) continue;
1756
1757 wcshdo_util(ctrl, "", "", 0, 0x0, 0, 0, 0, ' ', 0, 0, "", "",
1758 nkeyrec, header, &status);
1759
1760 // TPD.REV.m
1761 sprintf(keyword, "D%c%d", pq, j+1);
1762 for (idp = iparm[I_TPDNCO]; idp < iparm[I_NDPARM]; idp++) {
1763 if (dparm[idp] == 0.0) continue;
1764
1765 wcsutil_double2str(ctemp, format, dparm[idp]);
1766 m = idp - iparm[I_TPDNCO];
1767 sprintf(keyvalue, "'TPD.REV.%d:%s %s'", m, (m<10)?" ":"", ctemp);
1768 wcshdo_tpdterm(m, direct, term);
1769 sprintf(comment, "TPD coefficient: %s", term);
1770
1771 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1772 keyvalue, comment, nkeyrec, header, &status);
1773 }
1774
1775 } else {
1776 // General polynomial distortion, handled via its dpkey records
1777 // since iparm and dparm may hold a translation to TPD.
1778
1779 // Do auxiliary variables first.
1780 keyp = dis->dp;
1781 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1782 if (keyp->j != j+1) continue;
1783
1784 cp = strchr(keyp->field, '.') + 1;
1785 if (strncmp(cp, "NAUX", 4) != 0) continue;
1786
1787 sprintf(keyvalue, "'%s: %d'", cp, dpkeyi(keyp));
1788 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1789 keyvalue, "Number of auxiliary variables", nkeyrec, header,
1790 &status);
1791
1792 keyp = dis->dp;
1793 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1794 if (keyp->j != j+1) continue;
1795
1796 keyval = dpkeyd(keyp);
1797
1798 cp = strchr(keyp->field, '.') + 1;
1799 if (strncmp(cp, "AUX.", 4) != 0) continue;
1800
1801 sscanf(cp+4, "%d", &m);
1802 sprintf(keyvalue, "'%s:", cp);
1803
1804 cp = strchr(cp+4, '.') + 1;
1805 kp = keyvalue + strlen(keyvalue);
1806
1807 if ((double)((int)keyval) == keyval) {
1808 sprintf(kp, "%4d'", (int)keyval);
1809 } else if (keyval == 0.5) {
1810 strcat(kp, " 0.5'");
1811 } else {
1812 wcsutil_double2str(kp, "%21.13E", keyval);
1813 strcat(keyvalue, "'");
1814 }
1815
1816 sscanf(cp+6, "%d", &p);
1817 if (strncmp(cp, "POWER.", 4) == 0) {
1818 if (p) {
1819 sprintf(comment, "Aux %d: var %d power", m, p);
1820 } else {
1821 sprintf(comment, "Aux %d: power of sum of terms", m);
1822 }
1823 } else {
1824 if (p) {
1825 sprintf(comment, "Aux %d: var %d coefficient", m, p);
1826 } else {
1827 sprintf(comment, "Aux %d: offset term", m);
1828 }
1829 }
1830
1831 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1832 keyvalue, comment, nkeyrec, header, &status);
1833 }
1834
1835 break;
1836 }
1837
1838 // Do polynomial terms.
1839 keyp = dis->dp;
1840 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1841 if (keyp->j != j+1) continue;
1842
1843 cp = strchr(keyp->field, '.') + 1;
1844 if (strncmp(cp, "NTERMS", 6) != 0) continue;
1845
1846 sprintf(keyvalue, "'%s: %d'", cp, dpkeyi(keyp));
1847 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1848 keyvalue, "Number of terms in the polynomial", nkeyrec, header,
1849 &status);
1850 }
1851
1852 keyp = dis->dp;
1853 for (idp = 0; idp < dis->ndp; idp++, keyp++) {
1854 if (keyp->j != j+1) continue;
1855
1856 if ((keyval = dpkeyd(keyp)) == 0.0) continue;
1857
1858 cp = strchr(keyp->field, '.') + 1;
1859 if (strncmp(cp, "TERM.", 5) != 0) continue;
1860
1861 sscanf(cp+5, "%d", &m);
1862 sprintf(keyvalue, "'%s:%s ", cp, (m<10)?" ":"");
1863
1864 cp = strchr(cp+5, '.') + 1;
1865 kp = keyvalue + strlen(keyvalue);
1866 if (strncmp(cp, "VAR.", 4) == 0) {
1867 if ((double)((int)keyval) == keyval) {
1868 sprintf(kp, "%20d", (int)keyval);
1869 } else {
1870 wcsutil_double2str(kp, "%20.13f", keyval);
1871 }
1872
1873 sscanf(cp+4, "%d", &p);
1874 if (p <= Nhat) {
1875 sprintf(comment, "Poly term %d: var %d power", m, p);
1876 } else {
1877 sprintf(comment, "Poly term %d: aux %d power", m, p-Nhat);
1878 }
1879
1880 } else {
1881 wcsutil_double2str(kp, "%20.13E", keyval);
1882 sprintf(comment, "Poly term %d: coefficient", m);
1883 }
1884 strcat(keyvalue, "'");
1885
1886 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1887 keyvalue, comment, nkeyrec, header, &status);
1888 }
1889
1890 // CPERRja/CQERRia
1891 if (dis->maxdis[j] != 0.0) {
1892 sprintf(keyword, "C%cERR%d", pq, j+1);
1893 sprintf(keyvalue, "%20.2f", dis->maxdis[j]);
1894 sprintf(comment, "%sMaximum absolute value of distortion",
1895 idis?"":"[pix] ");
1896 wcshdo_util(ctrl, keyword, "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1897 keyvalue, comment, nkeyrec, header, &status);
1898 }
1899 }
1900 }
1901 }
1902
1903 // DVERRa
1904 if (dis->totdis != 0.0) {
1905 sprintf(keyvalue, "%20.2f", dis->totdis);
1906 sprintf(comment, "Maximum combined distortion");
1907 wcshdo_util(ctrl, "DVERR", "", 0, 0x0, 0, 0, 0, alt, 0, 0,
1908 keyvalue, comment, nkeyrec, header, &status);
1909 }
1910 }
1911
1912
1913 // Add identification.
1914 wcshdo_util(ctrl, "", "", 0, 0x0, 0, 0, 0, ' ', 0, 0, "", "",
1915 nkeyrec, header, &status);
1916
1917 if (dotpd == DIS_DOTPD) {
1918 // TPD by translation.
1919 sprintf(comment, "Translated from %s to TPD by WCSLIB %s", tpdsrc,
1920 wcslib_version(0x0));
1921 } else {
1922 sprintf(comment, "WCS header keyrecords produced by WCSLIB %s",
1923 wcslib_version(0x0));
1924 }
1925
1926 wcshdo_util(ctrl, "COMMENT", "", 0, 0x0, 0, 0, 0, ' ', 0, 0,
1927 "", comment, nkeyrec, header, &status);
1928
1929
1930 if (status == WCSHDRERR_MEMORY) {
1931 wcserr_set(WCSHDR_ERRMSG(status));
1932 }
1933 return status;
1934 }
1935
1936 //----------------------------------------------------------------------------
1937 // Determine a suitable floating point format for a set of parameters.
1938
wcshdo_format(int fmt,int nval,const double val[],char * format)1939 void wcshdo_format(
1940 int fmt,
1941 int nval,
1942 const double val[],
1943 char *format)
1944
1945 {
1946 int emax = -999;
1947 int emin = +999;
1948 int precision = 0;
1949 for (int i = 0; i < nval; i++) {
1950 // Double precision has at least 15 significant digits, and up to 17:
1951 // http://en.wikipedia.org/wiki/Double-precision_floating-point_format
1952 char cval[24];
1953 wcsutil_double2str(cval, "%21.14E", val[i]);
1954
1955 int cpi = 16;
1956 while (2 < cpi && cval[cpi] == '0') cpi--;
1957
1958 // Precision for 'E' format.
1959 cpi -= 2;
1960 if (precision < cpi) precision = cpi;
1961
1962 // Range of significant digits for 'f' format.
1963 int expon;
1964 sscanf(cval+18, "%d", &expon);
1965
1966 if (emax < expon) emax = expon;
1967 expon -= cpi;
1968 if (expon < emin) emin = expon;
1969 }
1970
1971
1972 if (fmt == 'G') {
1973 // Because e.g. writing 1e4 as 10000 requires an extra digit.
1974 emax++;
1975
1976 if (emin < -15 || 15 < emax || 15 < (emax - emin)) {
1977 fmt = 'E';
1978 } else {
1979 fmt = 'f';
1980 }
1981 }
1982
1983 if (fmt == 'f') {
1984 precision = -emin;
1985 if (precision < 1) precision = 1;
1986 if (17 < precision) precision = 17;
1987 sprintf(format, "%%20.%df", precision);
1988
1989 } else {
1990 if (precision < 1) precision = 1;
1991 if (14 < precision) precision = 14;
1992 if (precision < 14) {
1993 sprintf(format, "%%20.%dE", precision);
1994 } else {
1995 sprintf(format, "%%21.%dE", precision);
1996 }
1997 }
1998 }
1999
2000 //----------------------------------------------------------------------------
2001 // Construct a string that identifies the term of a TPD or TPV polynomial.
2002
wcshdo_tpdterm(int m,int direct,char * term)2003 void wcshdo_tpdterm(
2004 int m,
2005 int direct,
2006 char *term)
2007
2008 {
2009 const int nTPD[] = {1, 4, 7, 12, 17, 24, 31, 40, 49, 60};
2010
2011 int degree, k;
2012
2013 for (degree = 0; degree <= 9; degree++) {
2014 if (m < nTPD[degree]) break;
2015 }
2016
2017 if (degree == 0) {
2018 strcpy(term, "1");
2019
2020 } else {
2021 k = degree - (m - nTPD[degree-1]);
2022
2023 if (k < 0) {
2024 memcpy(term, "rrrrrrrrr", degree);
2025 } else if (direct) {
2026 memcpy(term, "xxxxxxxxx", k);
2027 memcpy(term+k, "yyyyyyyyy", degree-k);
2028 } else {
2029 memcpy(term, "yyyyyyyyy", k);
2030 memcpy(term+k, "xxxxxxxxx", degree-k);
2031 }
2032
2033 term[degree] = '\0';
2034 }
2035 }
2036
2037 //----------------------------------------------------------------------------
2038 // Construct a keyrecord from the components given.
2039
wcshdo_util(int relax,const char pikey[],const char tbkey[],int level,const char tlkey[],int i,int j,int m,char alt,int btcol,int plcol[],char keyvalue[],const char keycomment[],int * nkeyrec,char ** header,int * status)2040 void wcshdo_util(
2041 int relax,
2042 const char pikey[],
2043 const char tbkey[],
2044 int level,
2045 const char tlkey[],
2046 int i,
2047 int j,
2048 int m,
2049 char alt,
2050 int btcol,
2051 int plcol[],
2052 char keyvalue[],
2053 const char keycomment[],
2054 int *nkeyrec,
2055 char **header,
2056 int *status)
2057
2058 {
2059 char ch0, ch1, *hptr, keyword[32], *kptr;
2060 int nbyte, nc = 47, nv;
2061
2062 if (*status) return;
2063
2064 // Reallocate memory in blocks of 2880 bytes.
2065 if ((*nkeyrec)%32 == 0) {
2066 nbyte = ((*nkeyrec)/32 + 1) * 2880;
2067 if (!(hptr = realloc(*header, nbyte))) {
2068 *status = WCSHDRERR_MEMORY;
2069 return;
2070 }
2071
2072 *header = hptr;
2073 }
2074
2075 // Construct the keyword.
2076 if (alt == ' ') alt = '\0';
2077 if (btcol) {
2078 // Binary table image array.
2079 if (i > 0 && j) {
2080 if (j > 0) {
2081 sprintf(keyword, "%d%d%s%d%c", i, j, tbkey, btcol, alt);
2082 } else {
2083 sprintf(keyword, "%d%s%d_%d%c", i, tbkey, btcol, m, alt);
2084 }
2085 } else if (i > 0) {
2086 sprintf(keyword, "%d%s%d%c", i, tbkey, btcol, alt);
2087 } else if (j > 0) {
2088 sprintf(keyword, "%d%s%d%c", j, tbkey, btcol, alt);
2089 } else {
2090 sprintf(keyword, "%s%d%c", tbkey, btcol, alt);
2091 }
2092
2093 if ((strlen(keyword) < 8) && tlkey && (relax & level)) {
2094 // Use the long form.
2095 if (i > 0 && j) {
2096 if (j > 0) {
2097 sprintf(keyword, "%d%d%s%d%c", i, j, tlkey, btcol, alt);
2098 } else {
2099 sprintf(keyword, "%d%s%d_%d%c", i, tlkey, btcol, m, alt);
2100 }
2101 } else if (i > 0) {
2102 sprintf(keyword, "%d%s%d%c", i, tlkey, btcol, alt);
2103 } else if (j > 0) {
2104 sprintf(keyword, "%d%s%d%c", j, tlkey, btcol, alt);
2105 } else {
2106 sprintf(keyword, "%s%d%c", tlkey, btcol, alt);
2107 }
2108 }
2109
2110 } else if (plcol && plcol[0]) {
2111 // Pixel list.
2112 if (i > 0 && j) {
2113 if (j > 0) {
2114 sprintf(keyword, "T%s%d_%d%c", tbkey, plcol[i-1], plcol[j-1], alt);
2115 } else {
2116 sprintf(keyword, "T%s%d_%d%c", tbkey, plcol[i-1], m, alt);
2117 }
2118 } else if (i > 0) {
2119 sprintf(keyword, "T%s%d%c", tbkey, plcol[i-1], alt);
2120 } else if (j > 0) {
2121 sprintf(keyword, "T%s%d%c", tbkey, plcol[j-1], alt);
2122 } else {
2123 sprintf(keyword, "%s%d%c", tbkey, plcol[0], alt);
2124 }
2125
2126 if ((strlen(keyword) < 8) && tlkey && (relax & level)) {
2127 // Use the long form.
2128 if (i > 0 && j) {
2129 if (j > 0) {
2130 sprintf(keyword, "T%s%d_%d%c", tlkey, plcol[i-1], plcol[j-1], alt);
2131 } else {
2132 sprintf(keyword, "T%s%d_%d%c", tlkey, plcol[i-1], m, alt);
2133 }
2134 } else if (i > 0) {
2135 sprintf(keyword, "T%s%d%c", tlkey, plcol[i-1], alt);
2136 } else if (j > 0) {
2137 sprintf(keyword, "T%s%d%c", tlkey, plcol[j-1], alt);
2138 } else {
2139 sprintf(keyword, "%s%d%c", tlkey, plcol[0], alt);
2140 }
2141 }
2142 } else {
2143 if (i > 0 && j) {
2144 if (j > 0) {
2145 sprintf(keyword, "%s%d_%d%c", pikey, i, j, alt);
2146 } else {
2147 sprintf(keyword, "%s%d_%d%c", pikey, i, m, alt);
2148 }
2149 } else if (i > 0) {
2150 sprintf(keyword, "%s%d%c", pikey, i, alt);
2151 } else if (j > 0) {
2152 sprintf(keyword, "%s%d%c", pikey, j, alt);
2153 } else {
2154 sprintf(keyword, "%s%c", pikey, alt);
2155 }
2156 }
2157
2158 // Double-up single-quotes in string keyvalues.
2159 if (*keyvalue == '\'') {
2160 hptr = keyvalue + 1;
2161 while (*hptr) {
2162 if (*hptr == '\'') {
2163 kptr = hptr++;
2164 if (*hptr) {
2165 ch0 = *kptr;
2166 while (*kptr) {
2167 ch1 = *(++kptr);
2168 *kptr = ch0;
2169 ch0 = ch1;
2170 }
2171 } else {
2172 break;
2173 }
2174 }
2175
2176 hptr++;
2177 }
2178
2179 // Check length.
2180 if (strlen(keyvalue) > 70) {
2181 // Truncate.
2182 keyvalue[69] = '\'';
2183 keyvalue[70] = '\0';
2184 }
2185
2186 } else {
2187 // Check length.
2188 if (strlen(keyvalue) > 70) {
2189 // Truncate.
2190 keyvalue[70] = '\0';
2191 }
2192 }
2193
2194 if ((nv = strlen(keyvalue) > 20)) {
2195 // Rob the keycomment to make space for the keyvalue.
2196 nc -= (nv - 20);
2197 }
2198
2199 hptr = *header + (80 * ((*nkeyrec)++));
2200 if (*keyword == '\0') {
2201 sprintf(hptr, "%80.80s", " ");
2202 } else if (strcmp(keyword, "COMMENT") == 0) {
2203 sprintf(hptr, "%-8.8s %-71.71s", keyword, keycomment);
2204 } else {
2205 sprintf(hptr, "%-8.8s= %-20s / %-*.*s", keyword, keyvalue, nc, nc,
2206 keycomment);
2207 }
2208 }
2209