CR2RE Pipeline Reference Manual 1.6.8
irplib_sdp_spectrum.c
1/*
2 * This file is part of the ESO Common Pipeline Library
3 * Copyright (C) 2014 European Southern Observatory
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#ifdef HAVE_CONFIG_H
21# include <config.h>
22#endif
23
24/*-----------------------------------------------------------------------------
25 Includes
26 -----------------------------------------------------------------------------*/
27
28#include <complex.h>
29#include <string.h>
30#include <assert.h>
31#include <stdlib.h>
32#include <limits.h>
33#include <sys/types.h>
34#include <regex.h>
35#include <ctype.h>
36#include "irplib_sdp_spectrum.h"
37
38#ifdef IRPLIB_USE_FITS_UPDATE_CHECKSUM
39# include <fitsio2.h>
40#endif
41
42/*-----------------------------------------------------------------------------
43 Defines
44 -----------------------------------------------------------------------------*/
45
46/* Manually setup the following while this code remains outside CPL proper: */
47#ifndef cpl_error_set_regex
48
49cpl_error_code
50cpl_error_set_regex_macro(const char *, cpl_error_code, int,
51 const regex_t *, const char *,
52 unsigned, const char *, ...) CPL_ATTR_PRINTF(7,8);
53
54#define cpl_error_set_regex(code, regcode, preg, ...) \
55 cpl_error_set_regex_macro(cpl_func, code, regcode, preg, \
56 __FILE__, __LINE__, __VA_ARGS__)
57
58#endif /* cpl_error_set_regex */
59
60
61#define KEY_ARCFILE "ARCFILE"
62#define KEY_ORIGFILE "ORIGFILE"
63#define KEY_RA "RA"
64#define KEY_RA_COMMENT "[deg] Spectroscopic target position (J2000)"
65#define KEY_DEC "DEC"
66#define KEY_DEC_COMMENT "[deg] Spectroscopic target position (J2000)"
67#define KEY_EXPTIME "EXPTIME"
68#define KEY_EXPTIME_COMMENT "[s] Total integration time per pixel"
69#define KEY_TEXPTIME "TEXPTIME"
70#define KEY_TEXPTIME_COMMENT "[s] Total integration time of all exposures"
71#define KEY_TIMESYS "TIMESYS"
72#define KEY_TIMESYS_COMMENT "Time system used"
73#define KEY_MJDOBS "MJD-OBS"
74#define KEY_MJDOBS_COMMENT "[d] Start of observations (days)"
75#define KEY_MJDEND "MJD-END"
76#define KEY_MJDEND_COMMENT "[d] End of observations (days)"
77#define KEY_PRODLVL "PRODLVL"
78#define KEY_PRODLVL_VALUE 2
79#define KEY_PRODLVL_COMMENT "Phase 3 product level: 1-raw, 2-science grade, 3-advanced"
80#define KEY_PROCSOFT "PROCSOFT"
81#define KEY_PROCSOFT_COMMENT "ESO pipeline version"
82#define KEY_PRODCATG "PRODCATG"
83#define KEY_PRODCATG_COMMENT "Data product category"
84#define KEY_ORIGIN "ORIGIN"
85#define KEY_ORIGIN_VALUE "ESO"
86#define KEY_ORIGIN_COMMENT "European Southern Observatory"
87#define KEY_EXT_OBJ "EXT_OBJ"
88#define KEY_EXT_OBJ_COMMENT "TRUE if extended"
89#define KEY_DISPELEM "DISPELEM"
90#define KEY_DISPELEM_COMMENT "Dispersive element name"
91#define KEY_SPECSYS "SPECSYS"
92#define KEY_SPECSYS_VALUE "TOPOCENT"
93#define KEY_SPECSYS_COMMENT "Reference frame for spectral coordinates"
94#define KEY_PROG_ID "PROG_ID"
95#define KEY_PROG_ID_COMMENT "ESO programme identification"
96#define KEY_OBID "OBID"
97#define KEY_OBID_COMMENT "Observation block ID"
98#define KEY_M_EPOCH "M_EPOCH"
99#define KEY_M_EPOCH_COMMENT "TRUE if resulting from multiple epochs"
100#define KEY_OBSTECH "OBSTECH"
101#define KEY_OBSTECH_COMMENT "Technique for observation"
102#define KEY_FLUXCAL "FLUXCAL"
103#define KEY_FLUXCAL_COMMENT "Type of flux calibration (ABSOLUTE or UNCALIBRATED)"
104#define KEY_CONTNORM "CONTNORM"
105#define KEY_CONTNORM_COMMENT "TRUE if normalised to the continuum"
106#define KEY_WAVELMIN "WAVELMIN"
107#define KEY_WAVELMIN_COMMENT "[nm] Minimum wavelength"
108#define KEY_WAVELMAX "WAVELMAX"
109#define KEY_WAVELMAX_COMMENT "[nm] Maximum wavelength"
110#define KEY_SPEC_BIN "SPEC_BIN"
111#define KEY_SPEC_BIN_COMMENT "[nm] Wavelength bin size"
112#define KEY_TOT_FLUX "TOT_FLUX"
113#define KEY_TOT_FLUX_COMMENT "TRUE if photometric conditions and all source flux is captured"
114#define KEY_FLUXERR "FLUXERR"
115#define KEY_FLUXERR_VALUE -2
116#define KEY_FLUXERR_COMMENT "Uncertainty in flux scale (%)"
117#define KEY_REFERENC "REFERENC"
118#define KEY_REFERENC_VALUE " "
119#define KEY_REFERENC_COMMENT "Reference publication"
120#define KEY_SPEC_RES "SPEC_RES"
121#define KEY_SPEC_RES_COMMENT "Reference spectral resolving power"
122#define KEY_SPEC_ERR "SPEC_ERR"
123#define KEY_SPEC_ERR_COMMENT "[nm] Statistical error in spectral coordinate"
124#define KEY_SPEC_SYE "SPEC_SYE"
125#define KEY_SPEC_SYE_COMMENT "[nm] Systematic error in spectral coordinate"
126#define KEY_LAMNLIN "LAMNLIN"
127#define KEY_LAMNLIN_COMMENT "Number of arc lines used for the wavel. solution"
128#define KEY_LAMRMS "LAMRMS"
129#define KEY_LAMRMS_COMMENT "[nm] RMS of the residuals of the wavel. solution"
130#define KEY_GAIN "GAIN"
131#define KEY_GAIN_COMMENT "Conversion factor (e-/ADU) electrons per data unit"
132#define KEY_DETRON "DETRON"
133#define KEY_DETRON_COMMENT "Readout noise per output (e-)"
134#define KEY_EFFRON "EFFRON"
135#define KEY_EFFRON_COMMENT "Median effective readout noise (e-)"
136#define KEY_SNR "SNR"
137#define KEY_SNR_COMMENT "Median signal to noise ratio per order"
138#define KEY_NCOMBINE "NCOMBINE"
139#define KEY_NCOMBINE_COMMENT "No. of combined raw science data files"
140#define KEY_PROV "PROV"
141#define KEY_PROV_COMMENT "Originating raw science file"
142#define KEY_ASSON "ASSON"
143#define KEY_ASSON_COMMENT "Associated file name"
144#define KEY_ASSOC "ASSOC"
145#define KEY_ASSOC_COMMENT "Associated file category"
146#define KEY_ASSOM "ASSOM"
147#define KEY_ASSOM_COMMENT "Associated file md5sum"
148#define KEY_VOCLASS "VOCLASS"
149#define KEY_VOCLASS_VALUE "SPECTRUM V2.0"
150#define KEY_VOCLASS_COMMENT "VO Data Model"
151#define KEY_VOPUB "VOPUB"
152#define KEY_VOPUB_VALUE "ESO/SAF"
153#define KEY_VOPUB_COMMENT "VO Publishing Authority"
154#define KEY_TITLE "TITLE"
155#define KEY_TITLE_COMMENT "Dataset title"
156#define KEY_OBJECT "OBJECT"
157#define KEY_OBJECT_COMMENT "Target designation"
158#define KEY_OBJECT_PHDU_COMMENT "Original target."
159#define KEY_APERTURE "APERTURE"
160#define KEY_APERTURE_COMMENT "[deg] Aperture diameter"
161#define KEY_TELAPSE "TELAPSE"
162#define KEY_TELAPSE_COMMENT "[s] Total elapsed time"
163#define KEY_TMID "TMID"
164#define KEY_TMID_COMMENT "[d] MJD mid exposure"
165#define KEY_SPEC_VAL "SPEC_VAL"
166#define KEY_SPEC_VAL_COMMENT "[nm] Mean wavelength"
167#define KEY_SPEC_BW "SPEC_BW"
168#define KEY_SPEC_BW_COMMENT "[nm] Bandpass width = Wmax - Wmin"
169#define KEY_TDMIN(n) "TDMIN"#n
170#define KEY_TDMIN1_COMMENT "Start in spectral coordinate"
171#define KEY_TDMAX(n) "TDMAX"#n
172#define KEY_TDMAX1_COMMENT "Stop in spectral coordinate"
173#define KEY_TUTYP "TUTYP"
174#define KEY_TUTYP_COMMENT "IVOA data model element for field "
175#define KEY_TUCD "TUCD"
176#define KEY_TUCD_COMMENT "UCD for field "
177#define KEY_TCOMM "TCOMM"
178#define KEY_TCOMM_COMMENT "Description for field "
179#define KEY_NELEM "NELEM"
180#define KEY_NELEM_COMMENT "Length of the data arrays"
181#define KEY_EXTNAME "EXTNAME"
182#define KEY_EXTNAME_VALUE "SPECTRUM"
183#define KEY_EXTNAME_COMMENT "Extension name"
184#define KEY_INHERIT "INHERIT"
185#define KEY_INHERIT_VALUE CPL_TRUE
186#define KEY_INHERIT_COMMENT "Primary header keywords are inherited"
187
188/* A regular expression to select all keywords relevant to a spectrum class. */
189#define ALL_KEYS_REGEXP \
190 "^(" KEY_RA "|" \
191 KEY_DEC "|" \
192 KEY_EXPTIME "|" \
193 KEY_TEXPTIME "|" \
194 KEY_TIMESYS "|" \
195 KEY_MJDOBS "|" \
196 KEY_MJDEND "|" \
197 KEY_PRODLVL "|" \
198 KEY_PROCSOFT "|" \
199 KEY_PRODCATG "|" \
200 KEY_ORIGIN "|" \
201 KEY_EXT_OBJ "|" \
202 KEY_DISPELEM "|" \
203 KEY_SPECSYS "|" \
204 KEY_PROG_ID "|" \
205 KEY_OBID "[0-9]+|" \
206 KEY_M_EPOCH "|" \
207 KEY_OBSTECH "|" \
208 KEY_FLUXCAL "|" \
209 KEY_CONTNORM "|" \
210 KEY_WAVELMIN "|" \
211 KEY_WAVELMAX "|" \
212 KEY_SPEC_BIN "|" \
213 KEY_TOT_FLUX "|" \
214 KEY_FLUXERR "|" \
215 KEY_REFERENC "|" \
216 KEY_SPEC_RES "|" \
217 KEY_SPEC_ERR "|" \
218 KEY_SPEC_SYE "|" \
219 KEY_LAMNLIN "|" \
220 KEY_LAMRMS "|" \
221 KEY_GAIN "|" \
222 KEY_DETRON "|" \
223 KEY_EFFRON "|" \
224 KEY_SNR "|" \
225 KEY_NCOMBINE "|" \
226 KEY_PROV "[0-9]+|" \
227 KEY_ASSON "[0-9]+|" \
228 KEY_ASSOC "[0-9]+|" \
229 KEY_ASSOM "[0-9]+|" \
230 KEY_VOCLASS "|" \
231 KEY_VOPUB "|" \
232 KEY_TITLE "|" \
233 KEY_OBJECT "|" \
234 KEY_APERTURE "|" \
235 KEY_TELAPSE "|" \
236 KEY_TMID "|" \
237 KEY_SPEC_VAL "|" \
238 KEY_SPEC_BW "|" \
239 KEY_TDMIN(1) "|" \
240 KEY_TDMAX(1) "|" \
241 KEY_TUTYP "[0-9]+|" \
242 KEY_TUCD "[0-9]+|" \
243 KEY_TCOMM "[0-9]+|" \
244 KEY_NELEM "|" \
245 KEY_EXTNAME "|" \
246 KEY_INHERIT ")$"
247
248/* A regular expression to select keywords from all explicit SDP spectrum
249 * keywords that should land up in the primary HDU. */
250#define PRIMARY_HDU_KEYS_REGEXP \
251 "^(" KEY_RA "|" \
252 KEY_DEC "|" \
253 KEY_EXPTIME "|" \
254 KEY_TEXPTIME "|" \
255 KEY_TIMESYS "|" \
256 KEY_MJDOBS "|" \
257 KEY_MJDEND "|" \
258 KEY_PRODLVL "|" \
259 KEY_PROCSOFT "|" \
260 KEY_PRODCATG "|" \
261 KEY_ORIGIN "|" \
262 KEY_EXT_OBJ "|" \
263 KEY_DISPELEM "|" \
264 KEY_SPECSYS "|" \
265 KEY_PROG_ID "|" \
266 KEY_OBID "[0-9]+|" \
267 KEY_M_EPOCH "|" \
268 KEY_OBSTECH "|" \
269 KEY_FLUXCAL "|" \
270 KEY_CONTNORM "|" \
271 KEY_WAVELMIN "|" \
272 KEY_WAVELMAX "|" \
273 KEY_SPEC_BIN "|" \
274 KEY_TOT_FLUX "|" \
275 KEY_FLUXERR "|" \
276 KEY_REFERENC "|" \
277 KEY_SPEC_RES "|" \
278 KEY_SPEC_ERR "|" \
279 KEY_SPEC_SYE "|" \
280 KEY_LAMNLIN "|" \
281 KEY_LAMRMS "|" \
282 KEY_GAIN "|" \
283 KEY_DETRON "|" \
284 KEY_EFFRON "|" \
285 KEY_SNR "|" \
286 KEY_NCOMBINE "|" \
287 KEY_PROV "[0-9]+|" \
288 KEY_ASSON "[0-9]+|" \
289 KEY_ASSOC "[0-9]+|" \
290 KEY_ASSOM "[0-9]+|" \
291 KEY_OBJECT ")$"
292
293/* A regular expression to select keywords from all explicit SDP spectrum
294 * keywords that should land up in the extension HDU. */
295#define EXTENSION_HDU_KEYS_REGEXP \
296 "^(" KEY_RA "|" \
297 KEY_DEC "|" \
298 KEY_VOCLASS "|" \
299 KEY_VOPUB "|" \
300 KEY_TITLE "|" \
301 KEY_OBJECT "|" \
302 KEY_APERTURE "|" \
303 KEY_TELAPSE "|" \
304 KEY_TMID "|" \
305 KEY_SPEC_VAL "|" \
306 KEY_SPEC_BW "|" \
307 KEY_TDMIN(1) "|" \
308 KEY_TDMAX(1) "|" \
309 KEY_TUTYP "[0-9]+|" \
310 KEY_TUCD "[0-9]+|" \
311 KEY_TCOMM "[0-9]+|" \
312 KEY_NELEM "|" \
313 KEY_EXTNAME "|" \
314 KEY_INHERIT ")$"
315
316
317#define GET_SET_METHODS(param, keyname, type, rettype, defaultval, comment) \
318 rettype irplib_sdp_spectrum_get_##param(const irplib_sdp_spectrum *self) \
319 { \
320 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, defaultval); \
321 assert(self->proplist != NULL); \
322 if (cpl_propertylist_has(self->proplist, keyname)) { \
323 return cpl_propertylist_get_##type(self->proplist, keyname); \
324 } else { \
325 return defaultval; \
326 } \
327 } \
328 cpl_error_code irplib_sdp_spectrum_reset_##param(irplib_sdp_spectrum *self) \
329 { \
330 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
331 assert(self->proplist != NULL); \
332 (void) cpl_propertylist_erase(self->proplist, keyname); \
333 return CPL_ERROR_NONE; \
334 } \
335 cpl_error_code irplib_sdp_spectrum_set_##param(irplib_sdp_spectrum *self, \
336 rettype value) \
337 { \
338 cpl_error_code error; \
339 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
340 assert(self->proplist != NULL); \
341 if (cpl_propertylist_has(self->proplist, keyname)) { \
342 error = cpl_propertylist_set_##type(self->proplist, keyname, value); \
343 } else { \
344 error = cpl_propertylist_append_##type(self->proplist, keyname, value); \
345 if (! error) { \
346 error = cpl_propertylist_set_comment(self->proplist, keyname, comment);\
347 if (error) { \
348 /* Delete entry if we could not set the comment to maintain a */ \
349 /* consistent state. */ \
350 cpl_errorstate prestate = cpl_errorstate_get(); \
351 (void) cpl_propertylist_erase(self->proplist, keyname); \
352 cpl_errorstate_set(prestate); \
353 } \
354 } \
355 } \
356 return error; \
357 } \
358 cpl_error_code irplib_sdp_spectrum_copy_##param(irplib_sdp_spectrum *self, \
359 const cpl_propertylist *plist, const char *name) \
360 { \
361 /* Note: check for plist or name equal NULL is done in the CPL calls. */ \
362 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
363 assert(self->proplist != NULL); \
364 if (cpl_propertylist_has(plist, name)) { \
365 cpl_errorstate prestate = cpl_errorstate_get(); \
366 rettype value = cpl_propertylist_get_##type(plist, name); \
367 if (cpl_errorstate_is_equal(prestate)) { \
368 return irplib_sdp_spectrum_set_##param(self, value); \
369 } else { \
370 return cpl_error_set_message(cpl_func, cpl_error_get_code(), \
371 "Could not set '%s'. Likely the source '%s' keyword has a" \
372 " different format or type.", keyname, name); \
373 } \
374 } else { \
375 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, \
376 "Could not set '%s' since the '%s' keyword was not found.", \
377 keyname, name); \
378 } \
379 }
380
381#define GET_SET_ARRAY_METHODS(param, keyname, type, rettype, defaultval, \
382 comment) \
383 rettype irplib_sdp_spectrum_get_##param(const irplib_sdp_spectrum *self, \
384 cpl_size index) \
385 { \
386 char *name; \
387 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, defaultval); \
388 assert(self->proplist != NULL); \
389 name = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyname, index); \
390 rettype result = defaultval; \
391 if (cpl_propertylist_has(self->proplist, name)) { \
392 result = cpl_propertylist_get_##type(self->proplist, name); \
393 } \
394 cpl_free(name); \
395 return result; \
396 } \
397 cpl_error_code irplib_sdp_spectrum_reset_##param(irplib_sdp_spectrum *self, \
398 cpl_size index) \
399 { \
400 char *name; \
401 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
402 assert(self->proplist != NULL); \
403 name = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyname, index); \
404 (void) cpl_propertylist_erase(self->proplist, name); \
405 cpl_free(name); \
406 return CPL_ERROR_NONE; \
407 } \
408 cpl_error_code irplib_sdp_spectrum_set_##param(irplib_sdp_spectrum *self, \
409 cpl_size index, rettype value)\
410 { \
411 cpl_error_code error; \
412 char *name; \
413 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
414 assert(self->proplist != NULL); \
415 name = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyname, index); \
416 if (cpl_propertylist_has(self->proplist, name)) { \
417 error = cpl_propertylist_set_##type(self->proplist, name, value); \
418 } else { \
419 error = cpl_propertylist_append_##type(self->proplist, name, value); \
420 if (! error) { \
421 error = cpl_propertylist_set_comment(self->proplist, name, comment);\
422 if (error) { \
423 /* Delete entry if we could not set the comment to maintain a */ \
424 /* consistent state. */ \
425 cpl_errorstate prestate = cpl_errorstate_get(); \
426 (void) cpl_propertylist_erase(self->proplist, name); \
427 cpl_errorstate_set(prestate); \
428 } \
429 } \
430 } \
431 cpl_free(name); \
432 return error; \
433 } \
434 cpl_error_code irplib_sdp_spectrum_copy_##param(\
435 irplib_sdp_spectrum *self, cpl_size index, \
436 const cpl_propertylist *plist, const char *name) \
437 { \
438 /* Note: check for plist or name equal NULL is done in the CPL calls. */ \
439 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT); \
440 assert(self->proplist != NULL); \
441 if (cpl_propertylist_has(plist, name)) { \
442 cpl_errorstate prestate = cpl_errorstate_get(); \
443 rettype value = cpl_propertylist_get_##type(plist, name); \
444 if (cpl_errorstate_is_equal(prestate)) { \
445 return irplib_sdp_spectrum_set_##param(self, index, value); \
446 } else { \
447 return cpl_error_set_message(cpl_func, cpl_error_get_code(), \
448 "Could not set '%s%"CPL_SIZE_FORMAT"'. Likely the source" \
449 " '%s' keyword has a different format or type.", \
450 keyname, index, name); \
451 } \
452 } else { \
453 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND, \
454 "Could not set '%s%"CPL_SIZE_FORMAT"' since the '%s'" \
455 " keyword was not found.", keyname, index, name); \
456 } \
457 }
458
459
460#define GET_SET_METHODS_TYPE_BOOL(param, keyname, comment) \
461 GET_SET_METHODS(param, keyname, bool, cpl_boolean, CPL_FALSE, comment)
462
463#define GET_SET_METHODS_TYPE_DOUBLE(param, keyname, comment) \
464 GET_SET_METHODS(param, keyname, double, double, NAN, comment)
465
466#define GET_SET_METHODS_TYPE_INT(param, keyname, comment) \
467 GET_SET_METHODS(param, keyname, int, int, -1, comment)
468
469#define GET_SET_METHODS_TYPE_STRING(param, keyname, comment) \
470 GET_SET_METHODS(param, keyname, string, const char *, NULL, comment)
471
472#define GET_SET_ARRAY_METHODS_TYPE_INT(param, keyname, comment) \
473 GET_SET_ARRAY_METHODS(param, keyname, int, int, -1, comment)
474
475#define GET_SET_ARRAY_METHODS_TYPE_STRING(param, keyname, comment) \
476 GET_SET_ARRAY_METHODS(param, keyname, string, const char *, NULL, comment)
477
478
479#define IRPLIB_TYPE_NELEM CPL_TYPE_LONG_LONG | CPL_TYPE_UNSPECIFIED
480
481/*----------------------------------------------------------------------------*/
488/*----------------------------------------------------------------------------*/
489
492/*-----------------------------------------------------------------------------
493 Type definition
494 -----------------------------------------------------------------------------*/
495
500struct _irplib_sdp_spectrum_ {
501 /* Indicates the number of data points of the spectrum. */
502 cpl_size nelem;
503
504 /* Stores all the SDP keywords for the primary header and table extension. */
505 cpl_propertylist *proplist;
506
507 /* The table for the spectrum data points. */
508 cpl_table *table;
509};
510
511
516typedef struct _irplib_keyword_record_ {
517 /* The name of the keyword. */
518 const char *name;
519
520 /* The keyword's default comment. */
521 const char *comment;
522
523 /* The keyword's type code. */
524 cpl_type type;
525
526 /* Is the keyword an array keyword or not (e.g. PROVi). */
527 cpl_boolean is_array_key;
528
529} irplib_keyword_record;
530
531/*-----------------------------------------------------------------------------
532 Internal function prototypes
533 -----------------------------------------------------------------------------*/
534
535static cpl_boolean
536_irplib_property_equal(const cpl_property *a, const cpl_property *b);
537
538static cpl_boolean
539_irplib_array_equal(const cpl_array *a, const cpl_array *b, cpl_size n);
540
541static cpl_boolean
542_irplib_table_column_equal(const cpl_table *a, const cpl_table *b,
543 const char *name, cpl_boolean only_intersect);
544
545static cpl_error_code
546_irplib_sdp_spectrum_copy_column(irplib_sdp_spectrum *self, const char *to_name,
547 const cpl_table* table, const char *from_name);
548
549static cpl_size
550_irplib_sdp_spectrum_count_keywords(const irplib_sdp_spectrum *self,
551 const char *regexp);
552
553static cpl_size
554_irplib_sdp_spectrum_get_column_index(const irplib_sdp_spectrum *self,
555 const char *name);
556
557static const char *
558_irplib_sdp_spectrum_get_column_keyword(const irplib_sdp_spectrum *self,
559 const char *name, const char *keyword);
560
561static cpl_error_code
562_irplib_sdp_spectrum_set_column_keyword(irplib_sdp_spectrum *self,
563 const char *name,
564 const char *value,
565 const char *keyword,
566 const char *comment);
567
568static void
569_irplib_sdp_spectrum_erase_column_keywords(irplib_sdp_spectrum *self,
570 const char *name);
571
572static char * _irplib_make_regexp(const cpl_propertylist *plist,
573 const char *extra);
574
575#ifndef NDEBUG
576static cpl_boolean _irplib_keyword_table_is_sorted(
577 const irplib_keyword_record *table, size_t entries);
578#endif
579
580static const irplib_keyword_record *
581_irplib_sdp_spectrum_get_keyword_record(const char *name);
582
583/*-----------------------------------------------------------------------------
584 Function codes
585 -----------------------------------------------------------------------------*/
586
587/*----------------------------------------------------------------------------*/
591/*----------------------------------------------------------------------------*/
592irplib_sdp_spectrum * irplib_sdp_spectrum_new(void)
593{
594 irplib_sdp_spectrum * obj = cpl_malloc(sizeof(irplib_sdp_spectrum));
595 obj->nelem = 0;
596 obj->proplist = cpl_propertylist_new();
597 obj->table = cpl_table_new(1);
598 return obj;
599}
600
601/*----------------------------------------------------------------------------*/
605/*----------------------------------------------------------------------------*/
607irplib_sdp_spectrum_duplicate(const irplib_sdp_spectrum *other)
608{
610
611 cpl_ensure(other != NULL, CPL_ERROR_NULL_INPUT, NULL);
612
613 assert(other->proplist != NULL);
614 assert(other->table != NULL);
615
616 obj = cpl_malloc(sizeof(irplib_sdp_spectrum));
617 obj->nelem = other->nelem;
618 obj->proplist = cpl_propertylist_duplicate(other->proplist);
619 obj->table = cpl_table_duplicate(other->table);
620 return obj;
621}
622
623/*----------------------------------------------------------------------------*/
627/*----------------------------------------------------------------------------*/
628void irplib_sdp_spectrum_delete(irplib_sdp_spectrum *self)
629{
630 if (self != NULL) {
631 assert(self->proplist != NULL);
632 assert(self->table != NULL);
633 cpl_propertylist_delete(self->proplist);
634 cpl_table_delete(self->table);
635 cpl_free(self);
636 }
637}
638
639/*----------------------------------------------------------------------------*/
644/*----------------------------------------------------------------------------*/
645static cpl_boolean
646_irplib_property_equal(const cpl_property *a, const cpl_property *b)
647{
648 int value_not_equal;
649 cpl_type type;
650 const char *sa, *sb;
651
652 assert(a != NULL);
653 assert(b != NULL);
654
655 /* Check the types are the same. */
656 type = cpl_property_get_type(a);
657 if (cpl_property_get_type(b) != type) return CPL_FALSE;
658
659 /* Check that the values are the same. */
660 switch (type) {
661 case CPL_TYPE_CHAR:
662 value_not_equal = cpl_property_get_char(a) != cpl_property_get_char(b);
663 break;
664 case CPL_TYPE_BOOL:
665 value_not_equal = cpl_property_get_bool(a) != cpl_property_get_bool(b);
666 break;
667 case CPL_TYPE_INT:
668 value_not_equal = cpl_property_get_int(a) != cpl_property_get_int(b);
669 break;
670 case CPL_TYPE_LONG:
671 value_not_equal = cpl_property_get_long(a) != cpl_property_get_long(b);
672 break;
673 case CPL_TYPE_LONG_LONG:
674 value_not_equal =
675 cpl_property_get_long_long(a) != cpl_property_get_long_long(b);
676 break;
677 case CPL_TYPE_FLOAT:
678 value_not_equal = cpl_property_get_float(a) != cpl_property_get_float(b);
679 break;
680 case CPL_TYPE_DOUBLE:
681 value_not_equal = cpl_property_get_double(a) != cpl_property_get_double(b);
682 break;
683 case CPL_TYPE_STRING:
684 sa = cpl_property_get_string(a);
685 sb = cpl_property_get_string(b);
686 if (sa == NULL && sb == NULL) {
687 value_not_equal = 0;
688 } else if (sa != NULL && sb != NULL) {
689 value_not_equal = strcmp(sa, sb) != 0;
690 } else {
691 return CPL_FALSE;
692 }
693 break;
694#ifdef _Complex_I
695 case CPL_TYPE_FLOAT_COMPLEX:
696 value_not_equal =
697 cpl_property_get_float_complex(a) != cpl_property_get_float_complex(b);
698 break;
699 case CPL_TYPE_DOUBLE_COMPLEX:
700 value_not_equal =
701 cpl_property_get_double_complex(a) != cpl_property_get_double_complex(b);
702 break;
703#endif
704 default:
705 cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
706 "Unsupported data type found in property '%s'.",
707 cpl_property_get_name(a));
708 return CPL_FALSE;
709 }
710 if (value_not_equal) return CPL_FALSE;
711
712 /* If we got here then the type and value must be equal so return true. */
713 return CPL_TRUE;
714}
715
716
717/*----------------------------------------------------------------------------*/
728/*----------------------------------------------------------------------------*/
729static cpl_boolean
730_irplib_array_equal(const cpl_array *a, const cpl_array *b, cpl_size n)
731{
732 cpl_type type;
733
734 assert(a != NULL);
735 assert(b != NULL);
736 assert(n <= cpl_array_get_size(a));
737 assert(n <= cpl_array_get_size(b));
738
739 type = cpl_array_get_type(a);
740 if (type != cpl_array_get_type(b)) return CPL_FALSE;
741
742 if (type == CPL_TYPE_STRING) {
743 /* Handle strings: */
744 cpl_size i;
745 const char **stra = cpl_array_get_data_string_const(a);
746 const char **strb = cpl_array_get_data_string_const(b);
747 cpl_error_ensure(stra != NULL && strb != NULL, cpl_error_get_code(),
748 return CPL_FALSE, "Failed to get %s data for array.",
749 cpl_type_get_name(type));
750 for (i = 0; i < n; ++i) {
751 if (stra[i] == NULL && strb[i] == NULL) continue;
752 if (stra[i] == NULL || strb[i] == NULL) return CPL_FALSE;
753 if (strcmp(stra[i], strb[i]) != 0) return CPL_FALSE;
754 }
755
756 } else {
757 /* Handle fundamental types: */
758 cpl_size size, i;
759 const void *va, *vb;
760
761 switch (type) {
762 case CPL_TYPE_INT:
763 size = sizeof(int);
764 va = cpl_array_get_data_int_const(a);
765 vb = cpl_array_get_data_int_const(b);
766 break;
767 case CPL_TYPE_LONG_LONG:
768 size = sizeof(long long);
769 va = cpl_array_get_data_long_long_const(a);
770 vb = cpl_array_get_data_long_long_const(b);
771 break;
772 case CPL_TYPE_FLOAT:
773 size = sizeof(float);
774 va = cpl_array_get_data_float_const(a);
775 vb = cpl_array_get_data_float_const(b);
776 break;
777 case CPL_TYPE_DOUBLE:
778 size = sizeof(double);
779 va = cpl_array_get_data_double_const(a);
780 vb = cpl_array_get_data_double_const(b);
781 break;
782#ifdef _Complex_I
783 case CPL_TYPE_FLOAT_COMPLEX:
784 size = sizeof(_Complex float);
785 va = cpl_array_get_data_float_complex_const(a);
786 vb = cpl_array_get_data_float_complex_const(b);
787 break;
788 case CPL_TYPE_DOUBLE_COMPLEX:
789 size = sizeof(_Complex double);
790 va = cpl_array_get_data_double_complex_const(a);
791 vb = cpl_array_get_data_double_complex_const(b);
792 break;
793#endif
794 default:
795 cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
796 "Unsupported data type.");
797 return CPL_FALSE;
798 }
799 cpl_error_ensure(va != NULL && vb != NULL, cpl_error_get_code(),
800 return CPL_FALSE, "Failed to get %s data for array.",
801 cpl_type_get_name(type));
802 for (i = 0; i < n; ++i) {
803 int valid_a = cpl_array_is_valid(a, i);
804 int valid_b = cpl_array_is_valid(b, i);
805 if (! valid_a && ! valid_b) continue;
806 if (! valid_a || ! valid_b) return CPL_FALSE;
807 const void *vai = (const char *)va + (size * i);
808 const void *vbi = (const char *)vb + (size * i);
809 if (memcmp(vai, vbi, size) != 0) return CPL_FALSE;
810 }
811 }
812
813 /* If we get here then the first n elements of the arrays are equal. */
814 return TRUE;
815}
816
817/*----------------------------------------------------------------------------*/
830/*----------------------------------------------------------------------------*/
831static cpl_boolean
832_irplib_table_column_equal(const cpl_table *a, const cpl_table *b,
833 const char *name, cpl_boolean only_intersect)
834{
835 cpl_type type;
836 cpl_size nrows, na, nb, i;
837 const char *sa, *sb;
838
839 assert(a != NULL);
840 assert(b != NULL);
841
842 nrows = cpl_table_get_nrow(a);
843 if (only_intersect) {
844 cpl_size nrows2 = cpl_table_get_nrow(b);
845 if (nrows2 < nrows) nrows = nrows2;
846 } else {
847 if (cpl_table_get_nrow(b) != nrows) return CPL_FALSE;
848 }
849
850 /* Column types must be the same. */
851 type = cpl_table_get_column_type(a, name);
852 if (cpl_table_get_column_type(b, name) != type) return CPL_FALSE;
853
854 /* Column dimensions must be the same. */
855 na = cpl_table_get_column_dimensions(a, name);
856 nb = cpl_table_get_column_dimensions(b, name);
857 if (na != nb) return CPL_FALSE;
858
859 /* Check that the column unit is the same. */
860 sa = cpl_table_get_column_unit(a, name);
861 sb = cpl_table_get_column_unit(b, name);
862 cpl_error_ensure(sa != NULL && sb != NULL, cpl_error_get_code(),
863 return CPL_FALSE,
864 "Failed to get unit strings for column '%s'.", name);
865 if (strcmp(sa, sb) != 0) return CPL_FALSE;
866
867 /* Check that the values are the same. For arrays we check that the parts of
868 * the arrays that overlap are at least the same. */
869 if (type & CPL_TYPE_POINTER) {
870 cpl_errorstate prestate;
871 /* Handle array cells: */
872 const cpl_array **va = cpl_table_get_data_array_const(a, name);
873 const cpl_array **vb = cpl_table_get_data_array_const(b, name);
874 cpl_error_ensure(va != NULL && vb != NULL,
875 cpl_error_get_code(), return CPL_FALSE,
876 "Failed to get %s data for column '%s'.",
877 cpl_type_get_name(type), name);
878 if (only_intersect) {
879 for (i = 0; i < nrows; ++i) {
880 /* If both arrays are NULL then they are equal,
881 * but not if only one is NULL. */
882 if (va[i] == NULL && vb[i] == NULL) continue;
883 if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
884 prestate = cpl_errorstate_get();
885 cpl_size n1 = cpl_array_get_size(va[i]);
886 cpl_size n2 = cpl_array_get_size(vb[i]);
887 cpl_size n = n1 < n2 ? n1 : n2;
888 if (! _irplib_array_equal(va[i], vb[i], n)) return CPL_FALSE;
889 cpl_error_ensure(cpl_errorstate_is_equal(prestate),
890 cpl_error_get_code(), return CPL_FALSE,
891 "Failed when trying to match %s data for column '%s'.",
892 cpl_type_get_name(type), name);
893 }
894 } else {
895 for (i = 0; i < nrows; ++i) {
896 /* If both arrays are NULL then they are equal,
897 * but not if only one is NULL. */
898 if (va[i] == NULL && vb[i] == NULL) continue;
899 if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
900 prestate = cpl_errorstate_get();
901 cpl_size n = cpl_array_get_size(va[i]);
902 if (n != cpl_array_get_size(vb[i])) return CPL_FALSE;
903 if (! _irplib_array_equal(va[i], vb[i], n)) return CPL_FALSE;
904 cpl_error_ensure(cpl_errorstate_is_equal(prestate),
905 cpl_error_get_code(), return CPL_FALSE,
906 "Failed when trying to match %s data for column '%s'.",
907 cpl_type_get_name(type), name);
908 }
909 }
910
911 } else if (type == CPL_TYPE_STRING) {
912 /* Handle strings: */
913 const char **va = cpl_table_get_data_string_const(a, name);
914 const char **vb = cpl_table_get_data_string_const(b, name);
915 cpl_error_ensure(va != NULL && vb != NULL,
916 cpl_error_get_code(), return CPL_FALSE,
917 "Failed to get %s data for column '%s'.",
918 cpl_type_get_name(type), name);
919 if (only_intersect) {
920 for (i = 0; i < nrows; ++i) {
921 if (va[i] == NULL && vb[i] == NULL) continue;
922 if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
923 size_t n1 = strlen(va[i]);
924 size_t n2 = strlen(vb[i]);
925 size_t n = n1 < n2 ? n1 : n2;
926 if (strncmp(va[i], vb[i], (cpl_size)n) != 0) return CPL_FALSE;
927 }
928 } else {
929 for (i = 0; i < nrows; ++i) {
930 if (va[i] == NULL && vb[i] == NULL) continue;
931 if (va[i] == NULL || vb[i] == NULL) return CPL_FALSE;
932 if (strcmp(va[i], vb[i]) != 0) return CPL_FALSE;
933 }
934 }
935
936 } else {
937 /* Handle fundamental types and strings: */
938 cpl_size size;
939 const void *va, *vb;
940
941 switch (type) {
942 case CPL_TYPE_INT:
943 size = sizeof(int);
944 va = cpl_table_get_data_int_const(a, name);
945 vb = cpl_table_get_data_int_const(b, name);
946 break;
947 case CPL_TYPE_LONG_LONG:
948 size = sizeof(long long);
949 va = cpl_table_get_data_long_long_const(a, name);
950 vb = cpl_table_get_data_long_long_const(b, name);
951 break;
952 case CPL_TYPE_FLOAT:
953 size = sizeof(float);
954 va = cpl_table_get_data_float_const(a, name);
955 vb = cpl_table_get_data_float_const(b, name);
956 break;
957 case CPL_TYPE_DOUBLE:
958 size = sizeof(double);
959 va = cpl_table_get_data_double_const(a, name);
960 vb = cpl_table_get_data_double_const(b, name);
961 break;
962#ifdef _Complex_I
963 case CPL_TYPE_FLOAT_COMPLEX:
964 size = sizeof(_Complex float);
965 va = cpl_table_get_data_float_complex_const(a, name);
966 vb = cpl_table_get_data_float_complex_const(b, name);
967 break;
968 case CPL_TYPE_DOUBLE_COMPLEX:
969 size = sizeof(_Complex double);
970 va = cpl_table_get_data_double_complex_const(a, name);
971 vb = cpl_table_get_data_double_complex_const(b, name);
972 break;
973#endif
974 default:
975 cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
976 "Unsupported data type found in column '%s'.", name);
977 return CPL_FALSE;
978 }
979 cpl_error_ensure(va != NULL && vb != NULL,
980 cpl_error_get_code(), return CPL_FALSE,
981 "Failed to get %s data for column '%s'.",
982 cpl_type_get_name(type), name);
983 for (i = 0; i < nrows; ++i) {
984 int valid_a = cpl_table_is_valid(a, name, i);
985 int valid_b = cpl_table_is_valid(b, name, i);
986 if (! valid_a && ! valid_b) continue;
987 if (! valid_a || ! valid_b) return CPL_FALSE;
988 const void *vai = (const char *)va + (size * i);
989 const void *vbi = (const char *)vb + (size * i);
990 if (memcmp(vai, vbi, size) != 0) return CPL_FALSE;
991 }
992 }
993
994 /* If we got here then the columns must be equal so return true. */
995 return CPL_TRUE;
996}
997
998/*----------------------------------------------------------------------------*/
1014/*----------------------------------------------------------------------------*/
1015cpl_boolean irplib_sdp_spectrum_equal(const irplib_sdp_spectrum *a,
1016 const irplib_sdp_spectrum *b,
1017 cpl_boolean only_intersect)
1018{
1019 cpl_errorstate prestate;
1020 cpl_size na, i;
1021 cpl_boolean no_match = CPL_FALSE;
1022 cpl_array *names;
1023 const char *name;
1024 const cpl_property *pa, *pb;
1025
1026 cpl_ensure(a != NULL && b != NULL, CPL_ERROR_NULL_INPUT, CPL_FALSE);
1027
1028 assert(a->proplist != NULL);
1029 assert(a->table != NULL);
1030 assert(b->proplist != NULL);
1031 assert(b->table != NULL);
1032
1033 na = cpl_propertylist_get_size(a->proplist);
1034
1035 if (only_intersect) {
1036 /* Check that the values are the same if the keywords are in both property
1037 * lists. (Ignore comments) */
1038 for (i = 0; i < na; ++i) {
1039 pa = cpl_propertylist_get_const(a->proplist, i);
1040 cpl_error_ensure(pa != NULL, cpl_error_get_code(), return CPL_FALSE,
1041 "Failed to get property structure %"CPL_SIZE_FORMAT".", i);
1042 name = cpl_property_get_name(pa);
1043 cpl_error_ensure(name != NULL, cpl_error_get_code(), return CPL_FALSE,
1044 "Failed to get the name for property %"CPL_SIZE_FORMAT".", i);
1045 pb = cpl_propertylist_get_property_const(b->proplist, name);
1046 if (pb != NULL) {
1047 prestate = cpl_errorstate_get();
1048 if (! _irplib_property_equal(pa, pb)) return CPL_FALSE;
1049 if (! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1050 }
1051 }
1052
1053 /* Check that the columns with the same names in both tables are identical
1054 * for the parts of the data arrays that overlap. */
1055 prestate = cpl_errorstate_get();
1056 na = cpl_table_get_ncol(a->table);
1057 names = cpl_table_get_column_names(a->table);
1058 for (i = 0; i < na; ++i) {
1059 name = cpl_array_get_string(names, i);
1060 cpl_error_ensure(name != NULL, cpl_error_get_code(), break,
1061 "Failed to get the name for column %"CPL_SIZE_FORMAT".", i);
1062 if (cpl_table_has_column(b->table, name)) {
1063 if (! _irplib_table_column_equal(a->table, b->table, name, CPL_TRUE)) {
1064 no_match = CPL_TRUE;
1065 break;
1066 }
1067 }
1068 }
1069 cpl_array_delete(names);
1070 /* Check that no errors occurred and all columns were processed. */
1071 if (no_match || ! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1072
1073 } else { /* not only_intersect */
1074 cpl_size nb;
1075 if (a->nelem != b->nelem) return CPL_FALSE;
1076
1077 /* Check that the property lists are identical. (Ignore comments) */
1078 nb = cpl_propertylist_get_size(b->proplist);
1079 if (na != nb) return CPL_FALSE;
1080 for (i = 0; i < na; ++i) {
1081 pa = cpl_propertylist_get_const(a->proplist, i);
1082 cpl_error_ensure(pa != NULL, cpl_error_get_code(), return CPL_FALSE,
1083 "Failed to get property structure %"CPL_SIZE_FORMAT".", i);
1084 name = cpl_property_get_name(pa);
1085 cpl_error_ensure(name != NULL, cpl_error_get_code(), return CPL_FALSE,
1086 "Failed to get the name for property %"CPL_SIZE_FORMAT".", i);
1087 pb = cpl_propertylist_get_property_const(b->proplist, name);
1088 if (pb == NULL) return CPL_FALSE;
1089 prestate = cpl_errorstate_get();
1090 if (! _irplib_property_equal(pa, pb)) return CPL_FALSE;
1091 if (! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1092 }
1093
1094 /* Check that the tables are identical. */
1095 prestate = cpl_errorstate_get();
1096 na = cpl_table_get_ncol(a->table);
1097 nb = cpl_table_get_ncol(b->table);
1098 if (na != nb) return CPL_FALSE;
1099 names = cpl_table_get_column_names(a->table);
1100 for (i = 0; i < na; ++i) {
1101 name = cpl_array_get_string(names, i);
1102 cpl_error_ensure(name != NULL, cpl_error_get_code(), break,
1103 "Failed to get the name for column %"CPL_SIZE_FORMAT".", i);
1104 if (! cpl_table_has_column(b->table, name)
1105 || ! _irplib_table_column_equal(a->table, b->table, name, CPL_FALSE))
1106 {
1107 no_match = CPL_TRUE;
1108 break;
1109 }
1110 }
1111 cpl_array_delete(names);
1112 /* Check that no errors occurred and all columns were processed. */
1113 if (no_match || ! cpl_errorstate_is_equal(prestate)) return CPL_FALSE;
1114 }
1115
1116 /* If we got to this point then all checks passed, so return true (a == b). */
1117 return CPL_TRUE;
1118}
1119
1120/*----------------------------------------------------------------------------*/
1125/*----------------------------------------------------------------------------*/
1126static cpl_size
1127_irplib_sdp_spectrum_count_keywords(const irplib_sdp_spectrum *self,
1128 const char *regexp)
1129{
1130 cpl_error_code error;
1131 cpl_size result = 0;
1132 cpl_propertylist *list = cpl_propertylist_new();
1133
1134 assert(self != NULL);
1135 assert(self->proplist != NULL);
1136
1137 error = cpl_propertylist_copy_property_regexp(list, self->proplist, regexp,
1138 CPL_FALSE);
1139 if (! error) {
1140 result = cpl_propertylist_get_size(list);
1141 }
1142 cpl_propertylist_delete(list);
1143 return result;
1144}
1145
1146
1147cpl_size irplib_sdp_spectrum_count_obid(const irplib_sdp_spectrum *self)
1148{
1149 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1150 return _irplib_sdp_spectrum_count_keywords(self, "^OBID[0-9]+$");
1151}
1152
1153cpl_size irplib_sdp_spectrum_count_prov(const irplib_sdp_spectrum *self)
1154{
1155 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1156 return _irplib_sdp_spectrum_count_keywords(self, "^PROV[0-9]+$");
1157}
1158
1159cpl_size irplib_sdp_spectrum_count_asson(const irplib_sdp_spectrum *self)
1160{
1161 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1162 return _irplib_sdp_spectrum_count_keywords(self, "^ASSON[0-9]+$");
1163}
1164
1165cpl_size irplib_sdp_spectrum_count_assoc(const irplib_sdp_spectrum *self)
1166{
1167 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1168 return _irplib_sdp_spectrum_count_keywords(self, "^ASSOC[0-9]+$");
1169}
1170
1171cpl_size irplib_sdp_spectrum_count_assom(const irplib_sdp_spectrum *self)
1172{
1173 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1174 return _irplib_sdp_spectrum_count_keywords(self, "^ASSOM[0-9]+$");
1175}
1176
1177
1178#ifndef NDEBUG
1179
1184static cpl_boolean _irplib_keyword_table_is_sorted(
1185 const irplib_keyword_record *table, size_t entries)
1186{
1187 size_t i;
1188 if (entries < 2) return CPL_TRUE;
1189 for (i = 0; i < entries-1; ++i) {
1190 if (strcmp(table[i].name, table[i+1].name) >= 0) {
1191 return CPL_FALSE;
1192 }
1193 }
1194 return CPL_TRUE;
1195}
1196
1197#endif /* NDEBUG */
1198
1199
1200static const irplib_keyword_record *
1201_irplib_sdp_spectrum_get_keyword_record(const char *name)
1202{
1203 /* The following table should contain all valid SDP spectrum keywords being
1204 * handled. NOTE: this table must be kept sorted since we perform a binary
1205 * search on the first column (i.e. the keyword name). */
1206 static const irplib_keyword_record keyword_table[] = {
1207 /* Name Comment Type Is an array key */
1208 {KEY_APERTURE, KEY_APERTURE_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1209 {KEY_ASSOC, KEY_ASSOC_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1210 {KEY_ASSOM, KEY_ASSOM_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1211 {KEY_ASSON, KEY_ASSON_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1212 {KEY_CONTNORM, KEY_CONTNORM_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1213 {KEY_DEC, KEY_DEC_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1214 {KEY_DETRON, KEY_DETRON_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1215 {KEY_DISPELEM, KEY_DISPELEM_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1216 {KEY_EFFRON, KEY_EFFRON_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1217 {KEY_EXPTIME, KEY_EXPTIME_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1218 {KEY_EXTNAME, KEY_EXTNAME_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1219 {KEY_EXT_OBJ, KEY_EXT_OBJ_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1220 {KEY_FLUXCAL, KEY_FLUXCAL_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1221 {KEY_FLUXERR, KEY_FLUXERR_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1222 {KEY_GAIN, KEY_GAIN_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1223 {KEY_INHERIT, KEY_INHERIT_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1224 {KEY_LAMNLIN, KEY_LAMNLIN_COMMENT, CPL_TYPE_INT, CPL_FALSE},
1225 {KEY_LAMRMS, KEY_LAMRMS_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1226 {KEY_MJDEND, KEY_MJDEND_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1227 {KEY_MJDOBS, KEY_MJDOBS_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1228 {KEY_M_EPOCH, KEY_M_EPOCH_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1229 {KEY_NCOMBINE, KEY_NCOMBINE_COMMENT, CPL_TYPE_INT, CPL_FALSE},
1230 {KEY_NELEM, KEY_NELEM_COMMENT, IRPLIB_TYPE_NELEM, CPL_FALSE},
1231 {KEY_OBID, KEY_OBID_COMMENT, CPL_TYPE_INT, CPL_TRUE},
1232 {KEY_OBJECT, KEY_OBJECT_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1233 {KEY_OBSTECH, KEY_OBSTECH_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1234 {KEY_ORIGIN, KEY_ORIGIN_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1235 {KEY_PROCSOFT, KEY_PROCSOFT_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1236 {KEY_PRODCATG, KEY_PRODCATG_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1237 {KEY_PRODLVL, KEY_PRODLVL_COMMENT, CPL_TYPE_INT, CPL_FALSE},
1238 {KEY_PROG_ID, KEY_PROG_ID_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1239 {KEY_PROV, KEY_PROV_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1240 {KEY_RA, KEY_RA_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1241 {KEY_REFERENC, KEY_REFERENC_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1242 {KEY_SNR, KEY_SNR_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1243 {KEY_SPECSYS, KEY_SPECSYS_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1244 {KEY_SPEC_BIN, KEY_SPEC_BIN_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1245 {KEY_SPEC_BW, KEY_SPEC_BW_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1246 {KEY_SPEC_ERR, KEY_SPEC_ERR_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1247 {KEY_SPEC_RES, KEY_SPEC_RES_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1248 {KEY_SPEC_SYE, KEY_SPEC_SYE_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1249 {KEY_SPEC_VAL, KEY_SPEC_VAL_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1250 {KEY_TCOMM, KEY_TCOMM_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1251 {KEY_TDMAX(1), KEY_TDMAX1_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1252 {KEY_TDMIN(1), KEY_TDMIN1_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1253 {KEY_TELAPSE, KEY_TELAPSE_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1254 {KEY_TEXPTIME, KEY_TEXPTIME_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1255 {KEY_TIMESYS, KEY_TIMESYS_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1256 {KEY_TITLE, KEY_TITLE_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1257 {KEY_TMID, KEY_TMID_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1258 {KEY_TOT_FLUX, KEY_TOT_FLUX_COMMENT, CPL_TYPE_BOOL, CPL_FALSE},
1259 {KEY_TUCD, KEY_TUCD_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1260 {KEY_TUTYP, KEY_TUTYP_COMMENT, CPL_TYPE_STRING, CPL_TRUE},
1261 {KEY_VOCLASS, KEY_VOCLASS_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1262 {KEY_VOPUB, KEY_VOPUB_COMMENT, CPL_TYPE_STRING, CPL_FALSE},
1263 {KEY_WAVELMAX, KEY_WAVELMAX_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE},
1264 {KEY_WAVELMIN, KEY_WAVELMIN_COMMENT, CPL_TYPE_DOUBLE, CPL_FALSE}
1265 };
1266
1267 static const size_t tablesize =
1268 sizeof(keyword_table) / sizeof(irplib_keyword_record);
1269 size_t low = 0; /* Low end of search region. */
1270 size_t high = tablesize-1; /* High end of search region. */
1271 const irplib_keyword_record *record = NULL;
1272
1273 assert(_irplib_keyword_table_is_sorted(keyword_table, tablesize));
1274 assert(name != NULL);
1275
1276 /* Binary search for the keyword record who's name forms the prefix of the
1277 * 'name' string passed to this function or is equal to that string. We cannot
1278 * just check if they equal since the OBIDi, PROVi, ASSONi, ASSOCi, ASSOMi,
1279 * TUTYPi and TUCDi keywords all have a number suffix that needs to be dealt
1280 * with. */
1281 do {
1282 size_t mid = (low + high) >> 1; /* Find mid point between low and high. */
1283 size_t keylen = strlen(keyword_table[mid].name);
1284 int result = strncmp(name, keyword_table[mid].name, keylen);
1285 if (result == 0) {
1286 record = &keyword_table[mid];
1287 break;
1288 } else if (result < 0) {
1289 if (mid >= 1) {
1290 high = mid - 1;
1291 } else {
1292 return NULL;
1293 }
1294 } else {
1295 low = mid + 1;
1296 if (low > high) return NULL;
1297 }
1298 } while (1);
1299
1300 assert(record != NULL);
1301
1302 if (strlen(record->name) != strlen(name)) {
1303 if (! record->is_array_key) return NULL;
1304 /* Have to check if the keyword format is correct. Should only have digits
1305 * following the name prefix. */
1306 const char *c = name + strlen(record->name);
1307 while (*c != '\0') {
1308 if (! isdigit(*c)) return NULL;
1309 ++c;
1310 }
1311 }
1312
1313 return record;
1314}
1315
1316
1317cpl_error_code irplib_sdp_spectrum_copy_keyword(irplib_sdp_spectrum *self,
1318 const cpl_propertylist *plist,
1319 const char *name)
1320{
1321 const irplib_keyword_record *key;
1322 cpl_errorstate prestate = cpl_errorstate_get();
1323 cpl_boolean spectrum_has_keyword;
1324
1325 cpl_ensure_code(self != NULL && plist != NULL && name != NULL,
1326 CPL_ERROR_NULL_INPUT);
1327
1328 assert(self->proplist != NULL);
1329
1330 if (! cpl_propertylist_has(plist, name)) {
1331 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1332 "Could not set '%s' since the keyword was not found in the"
1333 " source list.", name);
1334 }
1335
1336 key = _irplib_sdp_spectrum_get_keyword_record(name);
1337 if (key == NULL) {
1338 return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
1339 "The keyword name '%s' is not valid for an SPD spectrum.",
1340 name);
1341 }
1342
1343 spectrum_has_keyword = cpl_propertylist_has(self->proplist, name);
1344
1345 switch ((int) key->type) {
1346 case CPL_TYPE_BOOL:
1347 {
1348 /* Note: we update with the following functions rather than using the
1349 * cpl_propertylist_copy_property function since this way we get basic
1350 * typecasting functionality. e.g. floats get converted to doubles. */
1351 cpl_boolean value = cpl_propertylist_get_bool(plist, name);
1352 cpl_propertylist_update_bool(self->proplist, name, value);
1353 }
1354 break;
1355 case CPL_TYPE_INT:
1356 {
1357 int value = cpl_propertylist_get_int(plist, name);
1358 cpl_propertylist_update_int(self->proplist, name, value);
1359 }
1360 break;
1361 case CPL_TYPE_DOUBLE:
1362 {
1363 double value = cpl_propertylist_get_double(plist, name);
1364 cpl_propertylist_update_double(self->proplist, name, value);
1365 }
1366 break;
1367 case CPL_TYPE_STRING:
1368 {
1369 const char *value = cpl_propertylist_get_string(plist, name);
1370 cpl_propertylist_update_string(self->proplist, name, value);
1371 }
1372 break;
1373 case IRPLIB_TYPE_NELEM:
1374 {
1375 /* Special case where we update the nelem field. */
1376 spectrum_has_keyword = CPL_TRUE; /* Skip trying to set comment. */
1377 cpl_size value = (cpl_size) cpl_propertylist_get_long_long(plist, name);
1378 if (cpl_errorstate_is_equal(prestate)) {
1379 irplib_sdp_spectrum_set_nelem(self, value);
1380 }
1381 }
1382 break;
1383 default:
1384 return cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
1385 "Cannot handle type '%s'.", cpl_type_get_name(key->type));
1386 }
1387
1388 if (! spectrum_has_keyword) {
1389 cpl_propertylist_set_comment(self->proplist, name, key->comment);
1390 }
1391
1392 if (! cpl_errorstate_is_equal(prestate)) {
1393 if (! spectrum_has_keyword) {
1394 /* Make sure the keyword is removed if we have an error and it was not
1395 * there to begin with. */
1396 prestate = cpl_errorstate_get();
1397 (void) cpl_propertylist_erase(self->proplist, name);
1398 cpl_errorstate_set(prestate);
1399 }
1400 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1401 "Could not set '%s'. Likely the keyword from the source list"
1402 " has a different format or type.", name);
1403 }
1404
1405 return CPL_ERROR_NONE;
1406}
1407
1408
1409cpl_error_code irplib_sdp_spectrum_copy_property(irplib_sdp_spectrum *self,
1410 const cpl_property *prop)
1411{
1412 const char *name;
1413 const irplib_keyword_record *key;
1414 cpl_errorstate prestate = cpl_errorstate_get();
1415 cpl_boolean spectrum_has_keyword;
1416
1417 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1418
1419 assert(self->proplist != NULL);
1420
1421 name = cpl_property_get_name(prop);
1422 if (name == NULL) return cpl_error_get_code();
1423
1424 key = _irplib_sdp_spectrum_get_keyword_record(name);
1425 if (key == NULL) {
1426 return cpl_error_set_message(cpl_func, CPL_ERROR_ILLEGAL_INPUT,
1427 "The keyword name '%s' is not valid for an SPD spectrum.",
1428 name);
1429 }
1430
1431 spectrum_has_keyword = cpl_propertylist_has(self->proplist, name);
1432
1433 switch ((int) key->type) {
1434 case CPL_TYPE_BOOL:
1435 {
1436 cpl_boolean value = cpl_property_get_bool(prop);
1437 cpl_propertylist_update_bool(self->proplist, name, value);
1438 }
1439 break;
1440 case CPL_TYPE_INT:
1441 {
1442 int value = cpl_property_get_int(prop);
1443 cpl_propertylist_update_int(self->proplist, name, value);
1444 }
1445 break;
1446 case CPL_TYPE_DOUBLE:
1447 {
1448 double value = cpl_property_get_double(prop);
1449 cpl_propertylist_update_double(self->proplist, name, value);
1450 }
1451 break;
1452 case CPL_TYPE_STRING:
1453 {
1454 const char *value = cpl_property_get_string(prop);
1455 cpl_propertylist_update_string(self->proplist, name, value);
1456 }
1457 break;
1458 case IRPLIB_TYPE_NELEM:
1459 {
1460 /* Special case where we update the nelem field. */
1461 spectrum_has_keyword = CPL_TRUE; /* Skip trying to set comment. */
1462 cpl_size value = (cpl_size) cpl_property_get_long_long(prop);
1463 if (cpl_errorstate_is_equal(prestate)) {
1464 irplib_sdp_spectrum_set_nelem(self, value);
1465 }
1466 }
1467 break;
1468 default:
1469 return cpl_error_set_message(cpl_func, CPL_ERROR_INVALID_TYPE,
1470 "Cannot handle type '%s'.", cpl_type_get_name(key->type));
1471 }
1472
1473 if (! spectrum_has_keyword) {
1474 cpl_propertylist_set_comment(self->proplist, name, key->comment);
1475 }
1476
1477 if (! cpl_errorstate_is_equal(prestate)) {
1478 if (! spectrum_has_keyword) {
1479 /* Make sure the keyword is removed if we have an error and it was not
1480 * there to begin with. */
1481 prestate = cpl_errorstate_get();
1482 (void) cpl_propertylist_erase(self->proplist, name);
1483 cpl_errorstate_set(prestate);
1484 }
1485 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1486 "Could not set '%s'. Likely the source property has a"
1487 " different format or type.", name);
1488 }
1489
1490 return CPL_ERROR_NONE;
1491}
1492
1493
1494cpl_error_code irplib_sdp_spectrum_copy_property_regexp(
1495 irplib_sdp_spectrum *self,
1496 const cpl_propertylist *plist,
1497 const char *regexp,
1498 int invert)
1499{
1500 cpl_propertylist *sublist = NULL;
1501 cpl_propertylist *origlist = NULL;
1502 cpl_errorstate prestate = cpl_errorstate_get();
1503 cpl_size i;
1504
1505 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1506
1507 assert(self->proplist != NULL);
1508
1509 sublist = cpl_propertylist_new();
1510 origlist = cpl_propertylist_new();
1511 cpl_propertylist_copy_property_regexp(origlist, self->proplist, regexp,
1512 invert);
1513 cpl_propertylist_copy_property_regexp(sublist, plist, regexp, invert);
1514 if (cpl_propertylist_has(sublist, KEY_NELEM)) {
1515 /* Move the NELEM key to the end of the list so that rollback on error is
1516 * easier. */
1517 cpl_propertylist_erase(sublist, KEY_NELEM);
1518 cpl_propertylist_copy_property(sublist, plist, KEY_NELEM);
1519 }
1520 if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
1521
1522 for (i = 0; i < cpl_propertylist_get_size(sublist); ++i) {
1523 const cpl_property *p = cpl_propertylist_get_const(sublist, i);
1524 const char *name = cpl_property_get_name(p);
1525 irplib_sdp_spectrum_copy_keyword(self, sublist, name);
1526 if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
1527 }
1528
1529 cpl_propertylist_delete(sublist);
1530 cpl_propertylist_delete(origlist);
1531 return CPL_ERROR_NONE;
1532
1533cleanup:
1534 /* Cleanup here if an error occurred, by restoring the keywords to the
1535 * original values. */
1536 prestate = cpl_errorstate_get();
1537 (void) cpl_propertylist_copy_property_regexp(self->proplist, origlist,
1538 ".*", 0);
1539 cpl_errorstate_set(prestate);
1540 cpl_propertylist_delete(sublist);
1541 cpl_propertylist_delete(origlist);
1542 return cpl_error_get_code();
1543}
1544
1545
1546GET_SET_METHODS_TYPE_DOUBLE(ra, KEY_RA, KEY_RA_COMMENT)
1547GET_SET_METHODS_TYPE_DOUBLE(dec, KEY_DEC, KEY_DEC_COMMENT)
1548GET_SET_METHODS_TYPE_DOUBLE(exptime, KEY_EXPTIME, KEY_EXPTIME_COMMENT)
1549GET_SET_METHODS_TYPE_DOUBLE(texptime, KEY_TEXPTIME, KEY_TEXPTIME_COMMENT)
1550GET_SET_METHODS_TYPE_STRING(timesys, KEY_TIMESYS, KEY_TIMESYS_COMMENT)
1551GET_SET_METHODS_TYPE_DOUBLE(mjdobs, KEY_MJDOBS, KEY_MJDOBS_COMMENT)
1552GET_SET_METHODS_TYPE_DOUBLE(mjdend, KEY_MJDEND, KEY_MJDEND_COMMENT)
1553GET_SET_METHODS_TYPE_INT(prodlvl, KEY_PRODLVL, KEY_PRODLVL_COMMENT)
1554GET_SET_METHODS_TYPE_STRING(procsoft, KEY_PROCSOFT, KEY_PROCSOFT_COMMENT)
1555GET_SET_METHODS_TYPE_STRING(prodcatg, KEY_PRODCATG, KEY_PRODCATG_COMMENT)
1556GET_SET_METHODS_TYPE_STRING(origin, KEY_ORIGIN, KEY_ORIGIN_COMMENT)
1557GET_SET_METHODS_TYPE_BOOL(extobj, KEY_EXT_OBJ, KEY_EXT_OBJ_COMMENT)
1558GET_SET_METHODS_TYPE_STRING(dispelem, KEY_DISPELEM, KEY_DISPELEM_COMMENT)
1559GET_SET_METHODS_TYPE_STRING(specsys, KEY_SPECSYS, KEY_SPECSYS_COMMENT)
1560GET_SET_METHODS_TYPE_STRING(progid, KEY_PROG_ID, KEY_PROG_ID_COMMENT)
1561GET_SET_ARRAY_METHODS_TYPE_INT(obid, KEY_OBID, KEY_OBID_COMMENT)
1562GET_SET_METHODS_TYPE_BOOL(mepoch, KEY_M_EPOCH, KEY_M_EPOCH_COMMENT)
1563GET_SET_METHODS_TYPE_STRING(obstech, KEY_OBSTECH, KEY_OBSTECH_COMMENT)
1564GET_SET_METHODS_TYPE_STRING(fluxcal, KEY_FLUXCAL, KEY_FLUXCAL_COMMENT)
1565GET_SET_METHODS_TYPE_BOOL(contnorm, KEY_CONTNORM, KEY_CONTNORM_COMMENT)
1566GET_SET_METHODS_TYPE_DOUBLE(wavelmin, KEY_WAVELMIN, KEY_WAVELMIN_COMMENT)
1567GET_SET_METHODS_TYPE_DOUBLE(wavelmax, KEY_WAVELMAX, KEY_WAVELMAX_COMMENT)
1568GET_SET_METHODS_TYPE_DOUBLE(specbin, KEY_SPEC_BIN, KEY_SPEC_BIN_COMMENT)
1569GET_SET_METHODS_TYPE_BOOL(totflux, KEY_TOT_FLUX, KEY_TOT_FLUX_COMMENT)
1570GET_SET_METHODS_TYPE_DOUBLE(fluxerr, KEY_FLUXERR, KEY_FLUXERR_COMMENT)
1571GET_SET_METHODS_TYPE_STRING(referenc, KEY_REFERENC, KEY_REFERENC_COMMENT)
1572GET_SET_METHODS_TYPE_DOUBLE(specres, KEY_SPEC_RES, KEY_SPEC_RES_COMMENT)
1573GET_SET_METHODS_TYPE_DOUBLE(specerr, KEY_SPEC_ERR, KEY_SPEC_ERR_COMMENT)
1574GET_SET_METHODS_TYPE_DOUBLE(specsye, KEY_SPEC_SYE, KEY_SPEC_SYE_COMMENT)
1575GET_SET_METHODS_TYPE_INT(lamnlin, KEY_LAMNLIN, KEY_LAMNLIN_COMMENT)
1576GET_SET_METHODS_TYPE_DOUBLE(lamrms, KEY_LAMRMS, KEY_LAMRMS_COMMENT)
1577GET_SET_METHODS_TYPE_DOUBLE(gain, KEY_GAIN, KEY_GAIN_COMMENT)
1578GET_SET_METHODS_TYPE_DOUBLE(detron, KEY_DETRON, KEY_DETRON_COMMENT)
1579GET_SET_METHODS_TYPE_DOUBLE(effron, KEY_EFFRON, KEY_EFFRON_COMMENT)
1580GET_SET_METHODS_TYPE_DOUBLE(snr, KEY_SNR, KEY_SNR_COMMENT)
1581GET_SET_METHODS_TYPE_INT(ncombine, KEY_NCOMBINE, KEY_NCOMBINE_COMMENT)
1582GET_SET_ARRAY_METHODS_TYPE_STRING(prov, KEY_PROV, KEY_PROV_COMMENT)
1583GET_SET_ARRAY_METHODS_TYPE_STRING(asson, KEY_ASSON, KEY_ASSON_COMMENT)
1584GET_SET_ARRAY_METHODS_TYPE_STRING(assoc, KEY_ASSOC, KEY_ASSOC_COMMENT)
1585GET_SET_ARRAY_METHODS_TYPE_STRING(assom, KEY_ASSOM, KEY_ASSOM_COMMENT)
1586GET_SET_METHODS_TYPE_STRING(voclass, KEY_VOCLASS, KEY_VOCLASS_COMMENT)
1587GET_SET_METHODS_TYPE_STRING(vopub, KEY_VOPUB, KEY_VOPUB_COMMENT)
1588GET_SET_METHODS_TYPE_STRING(title, KEY_TITLE, KEY_TITLE_COMMENT)
1589GET_SET_METHODS_TYPE_STRING(object, KEY_OBJECT, KEY_OBJECT_COMMENT)
1590GET_SET_METHODS_TYPE_DOUBLE(aperture, KEY_APERTURE, KEY_APERTURE_COMMENT)
1591GET_SET_METHODS_TYPE_DOUBLE(telapse, KEY_TELAPSE, KEY_TELAPSE_COMMENT)
1592GET_SET_METHODS_TYPE_DOUBLE(tmid, KEY_TMID, KEY_TMID_COMMENT)
1593GET_SET_METHODS_TYPE_DOUBLE(specval, KEY_SPEC_VAL, KEY_SPEC_VAL_COMMENT)
1594GET_SET_METHODS_TYPE_DOUBLE(specbw, KEY_SPEC_BW, KEY_SPEC_BW_COMMENT)
1595GET_SET_METHODS_TYPE_STRING(extname, KEY_EXTNAME, KEY_EXTNAME_COMMENT)
1596GET_SET_METHODS_TYPE_BOOL(inherit, KEY_INHERIT, KEY_INHERIT_COMMENT)
1597GET_SET_METHODS_TYPE_DOUBLE(tdmin, KEY_TDMIN(1), KEY_TDMIN1_COMMENT)
1598GET_SET_METHODS_TYPE_DOUBLE(tdmax, KEY_TDMAX(1), KEY_TDMAX1_COMMENT)
1599
1600
1601#if 0
1602/*----------------------------------------------------------------------------*/
1617/*----------------------------------------------------------------------------*/
1618
1619cpl_error_code
1620irplib_sdp_spectrum_replace_comment(irplib_sdp_spectrum *self,
1621 const char *keyword,
1622 const char *comment)
1623{
1624 cpl_ensure_code((self != NULL) && (keyword != NULL) && (comment != NULL),
1625 CPL_ERROR_NULL_INPUT);
1626 cpl_ensure_code((self->proplist != NULL), CPL_ERROR_ILLEGAL_INPUT);
1627
1628 if (!cpl_propertylist_has(self->proplist, keyword)) {
1629 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
1630 "Could not find '%s' keyword.", keyword);
1631 }
1632
1633 cpl_propertylist_set_comment(self->proplist, keyword, comment);
1634 return CPL_ERROR_NONE;
1635}
1636#endif
1637
1638
1639cpl_error_code irplib_sdp_spectrum_append_prov(irplib_sdp_spectrum *self,
1640 cpl_size firstindex,
1641 const cpl_frameset *frames)
1642{
1643 cpl_frameset_iterator* iter = NULL;
1644 cpl_propertylist* keywords = NULL;
1645 const cpl_frame* frame;
1646 cpl_size index = firstindex;
1647
1648 /* Note: check for NULL already done in irplib_sdp_spectrum_set_prov below. */
1649 assert(self != NULL);
1650 assert(self->proplist != NULL);
1651
1652 iter = cpl_frameset_iterator_new(frames);
1653 frame = cpl_frameset_iterator_get_const(iter);
1654 while (frame != NULL) {
1655 cpl_error_code error;
1656 const char* value = NULL;
1657
1658 /* Load the keywords from the raw frame. */
1659 const char* filename = cpl_frame_get_filename(frame);
1660 cpl_error_ensure(filename != NULL, cpl_error_get_code(), goto cleanup,
1661 "%s", cpl_error_get_message());
1662 keywords = cpl_propertylist_load(filename, 0);
1663 cpl_error_ensure(filename != NULL, cpl_error_get_code(), goto cleanup,
1664 "Could not load keywords from primary HDU in '%s'.",
1665 filename);
1666
1667 /* Try set the value to ARCFILE or ORIGFILE or just the filename, whichever
1668 * is found first in that order. */
1669 if (cpl_propertylist_has(keywords, KEY_ARCFILE)) {
1670 value = cpl_propertylist_get_string(keywords, KEY_ARCFILE);
1671 cpl_error_ensure(value != NULL, cpl_error_get_code(), goto cleanup,
1672 "Could not extract the '%s' keyword value from '%s'.",
1673 KEY_ARCFILE, filename);
1674 } else if (cpl_propertylist_has(keywords, KEY_ORIGFILE)) {
1675 value = cpl_propertylist_get_string(keywords, KEY_ORIGFILE);
1676 cpl_error_ensure(value != NULL, cpl_error_get_code(), goto cleanup,
1677 "Could not extract the '%s' keyword value from '%s'.",
1678 KEY_ORIGFILE, filename);
1679 } else {
1680 value = filename;
1681 }
1682
1683 /* Add the next PROVi keyword. */
1684 error = irplib_sdp_spectrum_set_prov(self, index, value);
1685 cpl_error_ensure(! error, error, goto cleanup,
1686 "%s", cpl_error_get_message());
1687 cpl_propertylist_delete(keywords);
1688 keywords = NULL;
1689
1690 /* Increment the iterator to the next frame. */
1691 cpl_errorstate status = cpl_errorstate_get();
1692 cpl_frameset_iterator_advance(iter, 1);
1693 if (cpl_error_get_code() == CPL_ERROR_ACCESS_OUT_OF_RANGE) {
1694 cpl_errorstate_set(status);
1695 }
1696 frame = cpl_frameset_iterator_get_const(iter);
1697 ++index;
1698 }
1699
1700 cpl_frameset_iterator_delete(iter);
1701 return CPL_ERROR_NONE;
1702
1703cleanup:
1704 /* Cleanup if an error occurs. Note: delete methods already check for NULL. */
1705 cpl_frameset_iterator_delete(iter);
1706 cpl_propertylist_delete(keywords);
1707 return cpl_error_get_code();
1708}
1709
1710
1711cpl_size irplib_sdp_spectrum_get_nelem(const irplib_sdp_spectrum *self)
1712{
1713 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1714 return self->nelem;
1715}
1716
1717
1718cpl_error_code irplib_sdp_spectrum_reset_nelem(irplib_sdp_spectrum *self)
1719{
1720 return irplib_sdp_spectrum_set_nelem(self, 0);
1721}
1722
1723
1724cpl_error_code irplib_sdp_spectrum_set_nelem(irplib_sdp_spectrum *self,
1725 cpl_size value)
1726{
1727 cpl_size ncol;
1728 cpl_error_code error = CPL_ERROR_NONE;
1729
1730 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1731
1732 assert(self->table != NULL);
1733
1734 ncol = cpl_table_get_ncol(self->table);
1735 if (ncol > 0) {
1736 /* Update all column depths. */
1737 cpl_size i;
1738 cpl_array *names = cpl_table_get_column_names(self->table);
1739 for (i = 0; i < ncol; ++i) {
1740 const char *name = cpl_array_get_string(names, i);
1741 error = cpl_table_set_column_depth(self->table, name, value);
1742 if (error) {
1743 /* If an error occurs then set the columns that were changed back to
1744 * the previous value. */
1745 cpl_size j;
1746 cpl_errorstate prestate = cpl_errorstate_get();
1747 for (j = 0; j < i; ++j) {
1748 (void) cpl_table_set_column_depth(self->table, name, self->nelem);
1749 }
1750 cpl_errorstate_set(prestate);
1751 break;
1752 }
1753 }
1754 cpl_array_delete(names);
1755 }
1756 if (! error) {
1757 self->nelem = value;
1758 }
1759 return error;
1760}
1761
1762
1763cpl_error_code irplib_sdp_spectrum_copy_nelem(irplib_sdp_spectrum *self,
1764 const cpl_propertylist *plist,
1765 const char *name)
1766{
1767 /* Note: check for plist or name == NULL is done in cpl_propertylist calls. */
1768 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1769
1770 assert(self->proplist != NULL);
1771
1772 if (cpl_propertylist_has(plist, name)) {
1773 cpl_errorstate prestate = cpl_errorstate_get();
1774 cpl_size value = (cpl_size) cpl_propertylist_get_long_long(plist, name);
1775 if (cpl_errorstate_is_equal(prestate)) {
1776 return irplib_sdp_spectrum_set_nelem(self, value);
1777 } else {
1778 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
1779 "Could not set '%s'. Likely the source '%s' keyword has a"
1780 " different format or type.", KEY_NELEM, name);
1781 }
1782 } else {
1783 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
1784 "Could not set '%s' since the '%s' keyword was not found.",
1785 KEY_NELEM, name);
1786 }
1787}
1788
1789
1790cpl_size irplib_sdp_spectrum_get_ncol(const irplib_sdp_spectrum *self)
1791{
1792 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1793 assert(self->table != NULL);
1794 return cpl_table_get_ncol(self->table);
1795}
1796
1797
1798cpl_boolean irplib_sdp_spectrum_has_column(const irplib_sdp_spectrum *self,
1799 const char* name)
1800{
1801 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, 0);
1802 assert(self->table != NULL);
1803 return cpl_table_has_column(self->table, name);
1804}
1805
1806
1807cpl_array *
1808irplib_sdp_spectrum_get_column_names(const irplib_sdp_spectrum *self)
1809{
1810 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
1811 assert(self->table != NULL);
1812 return cpl_table_get_column_names(self->table);
1813}
1814
1815
1816cpl_error_code
1817irplib_sdp_spectrum_new_column(irplib_sdp_spectrum *self, const char *name,
1818 cpl_type type)
1819{
1820 cpl_error_code error;
1821 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1822 assert(self->table != NULL);
1823 error = cpl_table_new_column_array(self->table, name, type, self->nelem);
1824 if (error) {
1825 cpl_error_set_message(cpl_func, cpl_error_get_code(),
1826 "Failed to create a new column called '%s'.", name);
1827 }
1828 return error;
1829}
1830
1831
1832cpl_error_code
1833irplib_sdp_spectrum_add_column(irplib_sdp_spectrum *self, const char *name,
1834 cpl_type type, const char *unit,
1835 const char *format, const char *tutyp,
1836 const char *tucd, const cpl_array *data)
1837{
1838 cpl_error_code error;
1839
1840 /* Note: check for name equals NULL should already be done in table calls. */
1841 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1842
1843 assert(self->table != NULL);
1844
1845 /* Setup a new array cell column and fill its properties (possibly with
1846 * defaults). */
1847 error = cpl_table_new_column_array(self->table, name, type, self->nelem);
1848 if (unit != NULL && *unit != '\0') {
1849 error |= cpl_table_set_column_unit(self->table, name, unit);
1850 } else {
1851 error |= cpl_table_set_column_unit(self->table, name, " ");
1852 }
1853 if (format != NULL) {
1854 error |= cpl_table_set_column_format(self->table, name, format);
1855 }
1856 if (tutyp != NULL) {
1857 error |= irplib_sdp_spectrum_set_column_tutyp(self, name, tutyp);
1858 } else {
1859 error |= irplib_sdp_spectrum_set_column_tutyp(self, name, "");
1860 }
1861 if (tucd != NULL) {
1862 error |= irplib_sdp_spectrum_set_column_tucd(self, name, tucd);
1863 } else {
1864 error |= irplib_sdp_spectrum_set_column_tucd(self, name, "");
1865 }
1866
1867 /* Fill the table cell with the data array if available, else add an empty
1868 * array. */
1869 if (! error) {
1870 if (data != NULL) {
1871 error = cpl_table_set_array(self->table, name, 0, data);
1872 } else {
1873 cpl_array *array = cpl_array_new(self->nelem, type);
1874 if (array != NULL) {
1875 error = cpl_table_set_array(self->table, name, 0, array);
1876 cpl_array_delete(array);
1877 } else {
1878 error = cpl_error_get_code();
1879 }
1880 }
1881 }
1882
1883 if (error) {
1884 /* Remove the column just added if there was an error. We initially save and
1885 * finally restore the error state since we might generate secondary errors
1886 * when trying to remove the partially created column. But these secondary
1887 * errors are expected and irrelevant. */
1888 cpl_errorstate prestate = cpl_errorstate_get();
1889 _irplib_sdp_spectrum_erase_column_keywords(self, name);
1890 (void) cpl_table_erase_column(self->table, name);
1891 cpl_errorstate_set(prestate);
1892 error = cpl_error_set_message(cpl_func, cpl_error_get_code(),
1893 "Failed to create a new column called '%s'.", name);
1894 }
1895
1896 return error;
1897}
1898
1899
1900cpl_error_code
1901irplib_sdp_spectrum_delete_column(irplib_sdp_spectrum *self, const char *name)
1902{
1903 cpl_errorstate prestate = cpl_errorstate_get();
1904 cpl_error_code error = CPL_ERROR_NONE;
1905
1906 cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
1907
1908 assert(self->table != NULL);
1909
1910 _irplib_sdp_spectrum_erase_column_keywords(self, name);
1911 if (! cpl_errorstate_is_equal(prestate)) {
1912 error |= cpl_error_get_code();
1913 }
1914 error |= cpl_table_erase_column(self->table, name);
1915 if (error) {
1916 return cpl_error_get_code();
1917 } else {
1918 return CPL_ERROR_NONE;
1919 }
1920}
1921
1922
1923static cpl_error_code
1924_irplib_sdp_spectrum_copy_column(irplib_sdp_spectrum *self, const char *to_name,
1925 const cpl_table* table, const char *from_name)
1926{
1927 cpl_error_code error;
1928
1929 assert(self != NULL);
1930 assert(self->table != NULL);
1931
1932 error = cpl_table_duplicate_column(self->table, to_name, table, from_name);
1933 if (error) return error;
1934 error |= irplib_sdp_spectrum_set_column_tutyp(self, to_name, "");
1935 error |= irplib_sdp_spectrum_set_column_tucd(self, to_name, "");
1936 if (error) {
1937 /* Rollback changes if an error occurred. */
1938 cpl_errorstate prestate = cpl_errorstate_get();
1939 _irplib_sdp_spectrum_erase_column_keywords(self, to_name);
1940 (void) cpl_table_erase_column(self->table, to_name);
1941 cpl_errorstate_set(prestate);
1942 return cpl_error_get_code();
1943 }
1944 return CPL_ERROR_NONE;
1945}
1946
1947
1948cpl_error_code
1949irplib_sdp_spectrum_copy_column(irplib_sdp_spectrum *self,
1950 const cpl_table* table, const char *name)
1951{
1952 /* Note: check for table == NULL || name == NULL should already be done in the
1953 * cpl_table calls. */
1954 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
1955 return _irplib_sdp_spectrum_copy_column(self, name, table, name);
1956}
1957
1958
1959cpl_error_code
1960irplib_sdp_spectrum_copy_column_regexp(irplib_sdp_spectrum *self,
1961 const cpl_table* table,
1962 const char *regexp, int invert)
1963{
1964 regex_t re;
1965 cpl_array *names = NULL;
1966 cpl_size n, i;
1967 int reg_error_code;
1968
1969 /* Note: table == NULL is checked in the cpl_table calls. */
1970 cpl_ensure_code(self != NULL && regexp != NULL, CPL_ERROR_NULL_INPUT);
1971
1972 assert(self->table != NULL);
1973
1974 reg_error_code = regcomp(&re, regexp, REG_EXTENDED | REG_NOSUB);
1975 if (reg_error_code != 0) {
1976 return cpl_error_set_regex(CPL_ERROR_ILLEGAL_INPUT, reg_error_code, &re,
1977 "regexp='%s', invert=%d", regexp, invert);
1978 }
1979
1980 /* Go through all column names in the table we are copying from and mark the
1981 * names the regular expression filters out as invalid. */
1982 names = cpl_table_get_column_names(table);
1983 n = cpl_array_get_size(names);
1984 for (i = 0; i < n; ++i) {
1985 int match;
1986 const char *namei = cpl_array_get_string(names, i);
1987 cpl_error_ensure(! cpl_table_has_column(self->table, namei),
1988 CPL_ERROR_ILLEGAL_OUTPUT, goto cleanup,
1989 "The column '%s' already exists in the spectrum.", namei);
1990 match = (regexec(&re, namei, 0, NULL, 0) == 0);
1991 if ((! match && ! invert) || (match && invert)) {
1992 cpl_array_set_invalid(names, i);
1993 }
1994 }
1995 /* Now copy only the valid columns. */
1996 for (i = 0; i < n; ++i) {
1997 if (cpl_array_is_valid(names, i)) {
1998 const char *namei = cpl_array_get_string(names, i);
1999 cpl_error_code error = _irplib_sdp_spectrum_copy_column(self, namei,
2000 table, namei);
2001 if (error) {
2002 cpl_errorstate prestate;
2003 cpl_size j;
2004 cpl_error_set_message(cpl_func, error, "Could not copy column '%s'.",
2005 namei);
2006 /* Remove any columns already added if we got an error copying any
2007 * column. */
2008 prestate = cpl_errorstate_get();
2009 for (j = 0; j < i; ++j) {
2010 namei = cpl_array_get_string(names, i);
2011 _irplib_sdp_spectrum_erase_column_keywords(self, namei);
2012 (void) cpl_table_erase_column(self->table, namei);
2013 }
2014 cpl_errorstate_set(prestate);
2015 goto cleanup;
2016 }
2017 }
2018 }
2019 cpl_array_delete(names);
2020 regfree(&re);
2021 return CPL_ERROR_NONE;
2022
2023cleanup:
2024 /* This is a cleanup section to delete objects when an error occurs. */
2025 cpl_array_delete(names);
2026 regfree(&re);
2027 return cpl_error_get_code();
2028}
2029
2030
2031cpl_error_code
2032irplib_sdp_spectrum_update_column(irplib_sdp_spectrum *self, const char *name,
2033 const cpl_table* table, const char *colname,
2034 int flags)
2035{
2036 char *orig_unit = NULL;
2037 char *orig_format = NULL;
2038 cpl_errorstate prestate = cpl_errorstate_get();
2039
2040 /* Note: check for name, colname equals NULL should already be done in the
2041 * cpl_table calls. */
2042 cpl_ensure_code(self != NULL && table != NULL, CPL_ERROR_NULL_INPUT);
2043
2044 assert(self->table != NULL);
2045
2046 if (! cpl_table_has_column(self->table, name)) {
2047 /* The column does not exist in the spectrum so just copy it. */
2048 return _irplib_sdp_spectrum_copy_column(self, name, table, colname);
2049 }
2050
2051 /* Make sure the source column exists. */
2052 if (! cpl_table_has_column(table, colname)) {
2053 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2054 "Column '%s' not found in table.", colname);
2055 }
2056
2057 /* Update the unit and format values if requested. Note, we copy the original
2058 * value to be able to restore it if an error occurs. */
2059 if (flags & IRPLIB_COLUMN_UNIT) {
2060 const char* unit = cpl_table_get_column_unit(table, colname);
2061 /* Prevent completely empty strings else cfitsio silently deletes the
2062 * keyword. */
2063 if (unit != NULL && *unit == '\0') {
2064 unit = " ";
2065 }
2066 orig_unit = cpl_strdup(cpl_table_get_column_unit(self->table, name));
2067 cpl_table_set_column_unit(self->table, name, unit);
2068 if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
2069 }
2070 if (flags & IRPLIB_COLUMN_FORMAT) {
2071 orig_format = cpl_strdup(cpl_table_get_column_format(self->table, name));
2072 cpl_table_set_column_format(self->table, name,
2073 cpl_table_get_column_format(table, colname));
2074 if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
2075 }
2076
2077 /* Update the data array. Leave this to the last task since it is normally
2078 * cheaper to rollback changes to the unit and format strings if an error
2079 * occurs. */
2080 if (flags & IRPLIB_COLUMN_DATA) {
2081 if (cpl_table_get_column_type(self->table, name) !=
2082 cpl_table_get_column_type(table, colname)) {
2083 cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
2084 "The table column '%s' and spectrum column '%s' do not"
2085 " have the same types.", colname, name);
2086 goto cleanup;
2087 }
2088 if (cpl_table_get_column_depth(self->table, name) !=
2089 cpl_table_get_column_depth(table, colname)) {
2090 cpl_error_set_message(cpl_func, CPL_ERROR_INCOMPATIBLE_INPUT,
2091 "The table column '%s' and spectrum column '%s' do not"
2092 " have the same dimensions.", colname, name);
2093 goto cleanup;
2094 }
2095 const cpl_array* data = cpl_table_get_array(table, colname, 0);
2096 if (data == NULL) goto cleanup;
2097 cpl_table_set_array(self->table, name, 0, data);
2098 if (! cpl_errorstate_is_equal(prestate)) goto cleanup;
2099 }
2100
2101 cpl_free(orig_unit);
2102 cpl_free(orig_format);
2103 return CPL_ERROR_NONE;
2104
2105cleanup:
2106 /* Cleanup if error occurred by rolling back modifications. */
2107 prestate = cpl_errorstate_get();
2108 if (orig_unit != NULL) {
2109 (void) cpl_table_set_column_unit(self->table, name, orig_unit);
2110 cpl_free(orig_unit);
2111 }
2112 if (orig_format != NULL) {
2113 (void) cpl_table_set_column_format(self->table, name, orig_format);
2114 cpl_free(orig_format);
2115 }
2116 cpl_errorstate_set(prestate);
2117 return cpl_error_get_code();
2118}
2119
2120
2121cpl_type irplib_sdp_spectrum_get_column_type(const irplib_sdp_spectrum *self,
2122 const char *name)
2123{
2124 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, CPL_TYPE_INVALID);
2125 assert(self->table != NULL);
2126 return cpl_table_get_column_type(self->table, name);
2127}
2128
2129
2130const char *
2131irplib_sdp_spectrum_get_column_unit(const irplib_sdp_spectrum *self,
2132 const char *name)
2133{
2134 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
2135 assert(self->table != NULL);
2136 return cpl_table_get_column_unit(self->table, name);
2137}
2138
2139/*----------------------------------------------------------------------------*/
2154/*----------------------------------------------------------------------------*/
2155cpl_error_code
2156irplib_sdp_spectrum_set_column_unit(irplib_sdp_spectrum *self,
2157 const char *name, const char *unit)
2158{
2159 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2160 assert(self->table != NULL);
2161 /* Prevent completely empty strings else cfitsio silently deletes the
2162 * keyword. */
2163 if (unit != NULL && *unit == '\0') {
2164 unit = " ";
2165 }
2166 return cpl_table_set_column_unit(self->table, name, unit);
2167}
2168
2169
2170cpl_error_code
2171irplib_sdp_spectrum_copy_column_unit(irplib_sdp_spectrum *self,
2172 const char *name,
2173 const cpl_propertylist *plist,
2174 const char *key)
2175{
2176 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2177
2178 assert(self->table != NULL);
2179
2180 if (cpl_propertylist_has(plist, key)) {
2181 cpl_errorstate prestate = cpl_errorstate_get();
2182 const char *value = cpl_propertylist_get_string(plist, key);
2183 if (cpl_errorstate_is_equal(prestate)) {
2184 /* Prevent completely empty strings else cfitsio silently deletes the
2185 * keyword. */
2186 if (value != NULL && *value == '\0') {
2187 value = " ";
2188 }
2189 return cpl_table_set_column_unit(self->table, name, value);
2190 } else {
2191 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2192 "Could not set the unit for column '%s'. Likely the source '%s'"
2193 " keyword is not a string.", name, key);
2194 }
2195 } else {
2196 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2197 "Could not set the unit for column '%s' since the '%s' keyword"
2198 " was not found.", name, key);
2199 }
2200}
2201
2202
2203const char *
2204irplib_sdp_spectrum_get_column_format(const irplib_sdp_spectrum *self,
2205 const char *name)
2206{
2207 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
2208 assert(self->table != NULL);
2209 return cpl_table_get_column_format(self->table, name);
2210}
2211
2212
2213cpl_error_code
2214irplib_sdp_spectrum_set_column_format(irplib_sdp_spectrum *self,
2215 const char *name, const char *format)
2216{
2217 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2218 assert(self->table != NULL);
2219 return cpl_table_set_column_format(self->table, name, format);
2220}
2221
2222
2223static cpl_size
2224_irplib_sdp_spectrum_get_column_index(const irplib_sdp_spectrum *self,
2225 const char *name)
2226{
2227 cpl_size i, n;
2228 cpl_array *names;
2229
2230 assert(self != NULL);
2231 assert(self->table != NULL);
2232 assert(name != NULL);
2233
2234 /* Try find the index number of the column. */
2235 names = cpl_table_get_column_names(self->table);
2236 n = cpl_array_get_size(names);
2237 for (i = 0; i < n; ++i) {
2238 const char *namei = cpl_array_get_string(names, i);
2239 if (strcmp(namei, name) == 0) {
2240 cpl_array_delete(names);
2241 return i;
2242 }
2243 }
2244 cpl_array_delete(names);
2245 return (cpl_size)-1;
2246}
2247
2248
2249static const char *
2250_irplib_sdp_spectrum_get_column_keyword(const irplib_sdp_spectrum *self,
2251 const char *name, const char *keyword)
2252{
2253 cpl_size index;
2254 const char *result = NULL;
2255
2256 assert(self != NULL);
2257 assert(self->proplist != NULL);
2258 assert(name != NULL);
2259 assert(keyword != NULL);
2260
2261 index = _irplib_sdp_spectrum_get_column_index(self, name);
2262 if (index != (cpl_size)-1) {
2263 /* If the index number was found then try return the property value. */
2264 char *propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyword, index+1);
2265 if (cpl_propertylist_has(self->proplist, propname)) {
2266 result = cpl_propertylist_get_string(self->proplist, propname);
2267 }
2268 cpl_free(propname);
2269 } else {
2270 cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2271 "Could not find '%s' keyword for column '%s'.", keyword, name);
2272 }
2273 return result;
2274}
2275
2276
2277static cpl_error_code
2278_irplib_sdp_spectrum_set_column_keyword(irplib_sdp_spectrum *self,
2279 const char *name,
2280 const char *value,
2281 const char *keyword,
2282 const char *comment)
2283{
2284 cpl_size index;
2285 char *propname, *pcomment;
2286
2287 assert(self != NULL);
2288 assert(self->proplist != NULL);
2289 assert(name != NULL);
2290 assert(keyword != NULL);
2291 assert(comment != NULL);
2292
2293 index = _irplib_sdp_spectrum_get_column_index(self, name);
2294 /* If the index was not found then return an error message. */
2295 if (index == (cpl_size)-1) {
2296 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2297 "Could not find '%s' keyword for column '%s'.", keyword, name);
2298 }
2299 /* Since the index number was found then try set or add the property value. */
2300 cpl_error_code error = CPL_ERROR_NONE;
2301 propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyword, index+1);
2302 pcomment = cpl_sprintf("%s%"CPL_SIZE_FORMAT, comment, index+1);
2303 if (cpl_propertylist_has(self->proplist, propname)) {
2304 if (value != NULL) {
2305 error = cpl_propertylist_set_string(self->proplist, propname, value);
2306 } else {
2307 (void) cpl_propertylist_erase(self->proplist, propname);
2308 }
2309 } else if (value != NULL) {
2310 error = cpl_propertylist_append_string(self->proplist, propname, value);
2311 if (! error) {
2312 error = cpl_propertylist_set_comment(self->proplist, propname,
2313 pcomment);
2314 if (error) {
2315 /* Delete entry if we could not set the comment to maintain a
2316 * consistent state */
2317 cpl_errorstate prestate = cpl_errorstate_get();
2318 (void) cpl_propertylist_erase(self->proplist, propname);
2319 cpl_errorstate_set(prestate);
2320 }
2321 }
2322 }
2323 cpl_free(propname);
2324 cpl_free(pcomment);
2325 return error;
2326}
2327
2328
2329static void
2330_irplib_sdp_spectrum_erase_column_keywords(irplib_sdp_spectrum *self,
2331 const char *name)
2332{
2333 cpl_size index;
2334
2335 assert(self != NULL);
2336 assert(self->proplist != NULL);
2337 assert(name != NULL);
2338
2339 index = _irplib_sdp_spectrum_get_column_index(self, name);
2340 if (index != (cpl_size)-1) {
2341 char *propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, KEY_TUTYP, index+1);
2342 cpl_propertylist_erase(self->proplist, propname);
2343 cpl_free(propname);
2344 propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, KEY_TUCD, index+1);
2345 cpl_propertylist_erase(self->proplist, propname);
2346 cpl_free(propname);
2347 propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, KEY_TCOMM, index+1);
2348 cpl_propertylist_erase(self->proplist, propname);
2349 cpl_free(propname);
2350 }
2351}
2352
2353
2354const char *
2355irplib_sdp_spectrum_get_column_tutyp(const irplib_sdp_spectrum *self,
2356 const char *name)
2357{
2358 cpl_errorstate prestate = cpl_errorstate_get();
2359 const char *result;
2360 cpl_ensure(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT, NULL);
2361 result = _irplib_sdp_spectrum_get_column_keyword(self, name, KEY_TUTYP);
2362 if (! cpl_errorstate_is_equal(prestate)) {
2363 cpl_error_set_where(cpl_func);
2364 }
2365 return result;
2366}
2367
2368
2369cpl_error_code
2370irplib_sdp_spectrum_set_column_tutyp(irplib_sdp_spectrum *self,
2371 const char *name, const char *tutyp)
2372{
2373 cpl_error_code error;
2374 cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
2375 error = _irplib_sdp_spectrum_set_column_keyword(self, name, tutyp,
2376 KEY_TUTYP, KEY_TUTYP_COMMENT);
2377 if (error) {
2378 cpl_error_set_where(cpl_func);
2379 }
2380 return error;
2381}
2382
2383
2384cpl_error_code
2385irplib_sdp_spectrum_copy_column_tutyp(irplib_sdp_spectrum *self,
2386 const char *name,
2387 const cpl_propertylist *plist,
2388 const char *key)
2389{
2390 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2391
2392 assert(self->table != NULL);
2393
2394 if (cpl_propertylist_has(plist, key)) {
2395 cpl_errorstate prestate = cpl_errorstate_get();
2396 const char *value = cpl_propertylist_get_string(plist, key);
2397 if (cpl_errorstate_is_equal(prestate)) {
2398 return irplib_sdp_spectrum_set_column_tutyp(self, name, value);
2399 } else {
2400 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2401 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2402 "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s'. Likely"
2403 " the source '%s' keyword is not a string.",
2404 KEY_TUTYP, index, name, key);
2405 }
2406 } else {
2407 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2408 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2409 "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s' since the"
2410 " '%s' keyword was not found.", KEY_TUTYP, index, name, key);
2411 }
2412}
2413
2414
2415const char *
2416irplib_sdp_spectrum_get_column_tucd(const irplib_sdp_spectrum *self,
2417 const char *name)
2418{
2419 cpl_errorstate prestate = cpl_errorstate_get();
2420 const char *result;
2421 cpl_ensure(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT, NULL);
2422 result = _irplib_sdp_spectrum_get_column_keyword(self, name, KEY_TUCD);
2423 if (! cpl_errorstate_is_equal(prestate)) {
2424 cpl_error_set_where(cpl_func);
2425 }
2426 return result;
2427}
2428
2429
2430cpl_error_code
2431irplib_sdp_spectrum_set_column_tucd(irplib_sdp_spectrum *self,
2432 const char *name, const char *tucd)
2433{
2434 cpl_error_code error;
2435 cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
2436 error = _irplib_sdp_spectrum_set_column_keyword(self, name, tucd,
2437 KEY_TUCD, KEY_TUCD_COMMENT);
2438 if (error) {
2439 cpl_error_set_where(cpl_func);
2440 }
2441 return error;
2442}
2443
2444
2445cpl_error_code
2446irplib_sdp_spectrum_copy_column_tucd(irplib_sdp_spectrum *self,
2447 const char *name,
2448 const cpl_propertylist *plist,
2449 const char *key)
2450{
2451 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2452
2453 assert(self->table != NULL);
2454
2455 if (cpl_propertylist_has(plist, key)) {
2456 cpl_errorstate prestate = cpl_errorstate_get();
2457 const char *value = cpl_propertylist_get_string(plist, key);
2458 if (cpl_errorstate_is_equal(prestate)) {
2459 return irplib_sdp_spectrum_set_column_tucd(self, name, value);
2460 } else {
2461 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2462 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2463 "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s'. Likely"
2464 " the source '%s' keyword is not a string.",
2465 KEY_TUCD, index, name, key);
2466 }
2467 } else {
2468 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2469 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2470 "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s' since the"
2471 " '%s' keyword was not found.", KEY_TUCD, index, name, key);
2472 }
2473}
2474
2475
2476const char *
2477irplib_sdp_spectrum_get_column_tcomm(const irplib_sdp_spectrum *self,
2478 const char *name)
2479{
2480 cpl_errorstate prestate = cpl_errorstate_get();
2481 const char *result;
2482 cpl_ensure(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT, NULL);
2483 result = _irplib_sdp_spectrum_get_column_keyword(self, name, KEY_TCOMM);
2484 if (! cpl_errorstate_is_equal(prestate)) {
2485 cpl_error_set_where(cpl_func);
2486 }
2487 return result;
2488}
2489
2490
2491cpl_error_code
2492irplib_sdp_spectrum_set_column_tcomm(irplib_sdp_spectrum *self,
2493 const char *name, const char *tcomm)
2494{
2495 cpl_error_code error;
2496 cpl_ensure_code(self != NULL && name != NULL, CPL_ERROR_NULL_INPUT);
2497 error = _irplib_sdp_spectrum_set_column_keyword(self, name, tcomm,
2498 KEY_TCOMM, KEY_TCOMM_COMMENT);
2499 if (error) {
2500 cpl_error_set_where(cpl_func);
2501 }
2502 return error;
2503}
2504
2505
2506cpl_error_code
2507irplib_sdp_spectrum_copy_column_tcomm(irplib_sdp_spectrum *self,
2508 const char *name,
2509 const cpl_propertylist *plist,
2510 const char *key)
2511{
2512 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2513
2514 assert(self->table != NULL);
2515
2516 if (cpl_propertylist_has(plist, key)) {
2517 cpl_errorstate prestate = cpl_errorstate_get();
2518 const char *value = cpl_propertylist_get_string(plist, key);
2519 if (cpl_errorstate_is_equal(prestate)) {
2520 return irplib_sdp_spectrum_set_column_tcomm(self, name, value);
2521 } else {
2522 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2523 return cpl_error_set_message(cpl_func, cpl_error_get_code(),
2524 "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s'. Likely"
2525 " the source '%s' keyword is not a string.",
2526 KEY_TCOMM, index, name, key);
2527 }
2528 } else {
2529 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name) + 1;
2530 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2531 "Could not set '%s%"CPL_SIZE_FORMAT"' for column '%s' since the"
2532 " '%s' keyword was not found.", KEY_TCOMM, index, name, key);
2533 }
2534}
2535
2536
2537/*----------------------------------------------------------------------------*/
2554/*----------------------------------------------------------------------------*/
2555
2556cpl_error_code
2557irplib_sdp_spectrum_replace_column_comment(irplib_sdp_spectrum *self,
2558 const char *name,
2559 const char *keyword,
2560 const char *comment)
2561{
2562 cpl_ensure_code((self != NULL), CPL_ERROR_NULL_INPUT);
2563 cpl_ensure_code((self->proplist != NULL), CPL_ERROR_ILLEGAL_INPUT);
2564 cpl_ensure_code((name != NULL) && (keyword != NULL) && (comment != NULL),
2565 CPL_ERROR_NULL_INPUT);
2566
2567 cpl_size index = _irplib_sdp_spectrum_get_column_index(self, name);
2568 if (index == (cpl_size)-1) {
2569 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2570 "Could not find column '%s'.", name);
2571 }
2572
2573 char *propname = cpl_sprintf("%s%"CPL_SIZE_FORMAT, keyword, index + 1);
2574 if (!cpl_propertylist_has(self->proplist, propname)) {
2575 cpl_free(propname);
2576 return cpl_error_set_message(cpl_func, CPL_ERROR_DATA_NOT_FOUND,
2577 "Could not find '%s' keyword for column '%s'.", keyword,
2578 name);
2579 }
2580
2581 cpl_propertylist_set_comment(self->proplist, propname, comment);
2582 cpl_free(propname);
2583
2584 return CPL_ERROR_NONE;
2585}
2586
2587
2588const cpl_array *
2589irplib_sdp_spectrum_get_column_data(const irplib_sdp_spectrum *self,
2590 const char *name)
2591{
2592 cpl_ensure(self != NULL, CPL_ERROR_NULL_INPUT, NULL);
2593 assert(self->table != NULL);
2594 return cpl_table_get_array(self->table, name, 0);
2595}
2596
2597
2598cpl_error_code
2599irplib_sdp_spectrum_set_column_data(irplib_sdp_spectrum *self,
2600 const char *name, const cpl_array *array)
2601{
2602 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2603 assert(self->table != NULL);
2604 return cpl_table_set_array(self->table, name, 0, array);
2605}
2606
2607
2608static char * _irplib_make_regexp(const cpl_propertylist *plist,
2609 const char *extra)
2610{
2611 /* Minimum number of characters required for possible "^(", "|" or ")$"
2612 * fragments and a null character to end the string. */
2613 static const cpl_size min_chars_required = 6;
2614
2615 /* Start, end and join fragments for the regular expression, to form a string
2616 * of the following form: "^(KEY1|KEY2 .. |KEYN)$" */
2617 static const char *start_fragment = "^(";
2618 static const char *end_fragment = ")$";
2619 static const char *join_fragment = "|";
2620
2621 cpl_size extra_length = (extra != NULL ? (cpl_size) strlen(extra) : 0);
2622 cpl_size regexp_size, bytesleft, nkeys, i;
2623 char *writepos;
2624 char *regexp = NULL;
2625
2626 assert(plist != NULL);
2627
2628 nkeys = cpl_propertylist_get_size(plist);
2629 if (nkeys == 0) {
2630 /* Handle special case where plist is empty. */
2631 if (extra != NULL) {
2632 return cpl_sprintf("%s%s%s", start_fragment, extra, end_fragment);
2633 } else {
2634 return cpl_strdup("");
2635 }
2636 }
2637
2638 /* Allocate enough space to store the regexp. 80 = FITS card width. */
2639 regexp_size = nkeys * 80 + min_chars_required + extra_length;
2640 regexp = cpl_malloc(regexp_size);
2641
2642 bytesleft = regexp_size;
2643 writepos = regexp;
2644 for (i = 0; i < nkeys; ++i) {
2645 cpl_size name_length, fragment_length;
2646 const char *name, *fragment;
2647
2648 /* Fetch the property name string. */
2649 const cpl_property *p = cpl_propertylist_get_const(plist, i);
2650 cpl_error_ensure(p != NULL, cpl_error_get_code(), goto cleanup,
2651 "Unexpected error accessing property structure %"CPL_SIZE_FORMAT".", i);
2652 name = cpl_property_get_name(p);
2653 cpl_error_ensure(name != NULL, cpl_error_get_code(), goto cleanup,
2654 "Unexpected error accessing the name of property %"CPL_SIZE_FORMAT".", i);
2655 name_length = (cpl_size) strlen(name);
2656
2657 /* Figure out the regexp start/join string fragment to use. */
2658 fragment = (i == 0) ? start_fragment : join_fragment;
2659 fragment_length = (cpl_size) strlen(fragment);
2660
2661 while (bytesleft <
2662 fragment_length + name_length + extra_length + min_chars_required)
2663 {
2664 /* Allocate more space if we still run out of space for the regexp.
2665 * Note: the realloc either succeeds or aborts on failure. */
2666 bytesleft += regexp_size;
2667 regexp_size += regexp_size;
2668 regexp = cpl_realloc(regexp, regexp_size);
2669 writepos = regexp + (regexp_size - bytesleft);
2670 }
2671
2672 /* Write the start/join fragment and then the key name strings. */
2673 strncpy(writepos, fragment, bytesleft);
2674 bytesleft -= fragment_length;
2675 writepos += fragment_length;
2676 strncpy(writepos, name, bytesleft);
2677 bytesleft -= name_length;
2678 writepos += name_length;
2679 }
2680
2681 /* Write the extra string and end fragment string to complete the regexp. */
2682 if (extra != NULL) {
2683 strncpy(writepos, join_fragment, bytesleft);
2684 bytesleft -= (cpl_size) strlen(join_fragment);
2685 writepos += (cpl_size) strlen(join_fragment);
2686 strncpy(writepos, extra, bytesleft);
2687 bytesleft -= extra_length;
2688 writepos += extra_length;
2689 }
2690 strncpy(writepos, end_fragment, bytesleft);
2691 /* Null terminate the string buffer for safety. */
2692 regexp[regexp_size-1] = '\0';
2693
2694 return regexp;
2695
2696cleanup:
2697 /* Cleanup in case of error: */
2698 cpl_free(regexp);
2699 return NULL;
2700}
2701
2702
2703irplib_sdp_spectrum * irplib_sdp_spectrum_load(const char *filename)
2704{
2705 cpl_error_code error;
2707 cpl_propertylist *plist = NULL;
2708 cpl_propertylist *tmpplist = NULL;
2709 cpl_table *table = NULL;
2710 cpl_array *names = NULL;
2711 cpl_array *emptyarray = NULL;
2712 cpl_size nelem, ext, i;
2713 char *regexp = NULL;
2714
2715 cpl_ensure(filename != NULL, CPL_ERROR_NULL_INPUT, NULL);
2716
2717 /* Load the property list from file, making sure the properties from the
2718 * primary HDU take precedence over those from the extension if any keywords
2719 * are duplicated. Note, we only load keywords known to the spectrum class. */
2720 plist = cpl_propertylist_load_regexp(filename, 0, ALL_KEYS_REGEXP, 0);
2721 cpl_error_ensure(plist != NULL, cpl_error_get_code(), goto cleanup,
2722 "Could not load property list from primary HDU when loading file '%s'.",
2723 filename);
2724
2725 /* We have to create a regexp to filter out keywords already loaded from the
2726 * primary HDU. */
2727 regexp = _irplib_make_regexp(plist, NULL);
2728 cpl_error_ensure(regexp != NULL, cpl_error_get_code(), goto cleanup,
2729 "Could not create regular expression to filter keywords.");
2730
2731 /* Try find the spectrum extension from which to load the table. If the
2732 * extension name cannot be found then just use the first extension. */
2733 ext = cpl_fits_find_extension(filename, KEY_EXTNAME_VALUE);
2734 cpl_error_ensure(ext != (cpl_size)-1, cpl_error_get_code(), goto cleanup,
2735 "Failed to get the extension '%s' from file '%s'.",
2736 KEY_EXTNAME_VALUE, filename);
2737 if (ext == 0) ext = 1;
2738
2739 /* Load only the SDP keywords from the extension. */
2740 tmpplist = cpl_propertylist_load_regexp(filename, ext, ALL_KEYS_REGEXP, 0);
2741 cpl_error_ensure(tmpplist != NULL, cpl_error_get_code(), goto cleanup,
2742 "Could not load property list from extension %"
2743 CPL_SIZE_FORMAT" when loading file '%s'.", ext, filename);
2744
2745 /* Append keywords to plist that are not already in plist. */
2746 error = cpl_propertylist_copy_property_regexp(plist, tmpplist, regexp, 1);
2747 cpl_error_ensure(! error, error, goto cleanup,
2748 "Failed to append keywords from file '%s' extension %"
2749 CPL_SIZE_FORMAT".", filename, ext);
2750
2751 /* Delete temporary objects that are no longer needed. */
2752 cpl_propertylist_delete(tmpplist);
2753 tmpplist = NULL;
2754 cpl_free(regexp);
2755 regexp = NULL;
2756
2757 table = cpl_table_load(filename, (int)ext, CPL_TRUE);
2758 cpl_error_ensure(table != NULL, cpl_error_get_code(), goto cleanup,
2759 "Could not load the spectrum table from extension %"
2760 CPL_SIZE_FORMAT" when loading file '%s'.", ext, filename);
2761
2762 /* Set the nelem value from the NELEM keyword if found, else work it out. */
2763 if (cpl_propertylist_has(plist, KEY_NELEM)) {
2764 cpl_errorstate prestate = cpl_errorstate_get();
2765 nelem = (cpl_size) cpl_propertylist_get_long_long(plist, KEY_NELEM);
2766 /* Remove NELEM since the value is instead stored in the nelem variable. */
2767 cpl_propertylist_erase(plist, KEY_NELEM);
2768 cpl_error_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(),
2769 goto cleanup, "Could not process the temporary '%s' keyword.",
2770 KEY_NELEM);
2771 } else {
2772 cpl_msg_warning(cpl_func,
2773 "Keyword '%s' not found in file '%s'. Possibly corrupted."
2774 " Will try find correct value from the table and continue.",
2775 KEY_NELEM, filename);
2776 nelem = 0;
2777 if (cpl_table_get_nrow(table) > 0) {
2778 names = cpl_table_get_column_names(table);
2779 if (names != NULL) {
2780 if (cpl_array_get_size(names) > 0) {
2781 const char *name = cpl_array_get_string(names, 0);
2782 nelem = cpl_table_get_column_depth(table, name);
2783 }
2784 cpl_array_delete(names);
2785 names = NULL;
2786 }
2787 }
2788 }
2789
2790 names = cpl_table_get_column_names(table);
2791 cpl_error_ensure(names != NULL, cpl_error_get_code(), goto cleanup,
2792 "Could not get table column names when loading file '%s'.", filename);
2793 for (i = 0; i < cpl_array_get_size(names); ++i) {
2794 int j;
2795 const char *name = cpl_array_get_string(names, 0);
2796 cpl_type type = cpl_table_get_column_type(table, name);
2797 if ((type & CPL_TYPE_POINTER) == 0) continue; /* Only handle array columns.*/
2798 for (j = 0; j < cpl_table_get_nrow(table); ++j) {
2799 if (cpl_table_get_array(table, name, j) != NULL) continue;
2800 emptyarray = cpl_array_new(nelem, type & (~CPL_TYPE_POINTER));
2801 cpl_error_ensure(emptyarray != NULL, cpl_error_get_code(), goto cleanup,
2802 "Could not create empty array when spectrum table from file '%s'.",
2803 filename);
2804 error = cpl_table_set_array(table, name, j, emptyarray);
2805 cpl_array_delete(emptyarray);
2806 emptyarray = NULL;
2807 }
2808 }
2809 cpl_array_delete(names);
2810
2811 /* Create new spectrum instance and return it. */
2812 obj = cpl_malloc(sizeof(irplib_sdp_spectrum));
2813 obj->nelem = nelem;
2814 obj->proplist = plist;
2815 obj->table = table;
2816 return obj;
2817
2818cleanup:
2819 /* Perform memory cleanup if an error occurred. The cpl_error_ensure macros
2820 * will send the control flow to this point when an error is detected.
2821 * Note: cpl_*_delete functions already check for NULL pointers. */
2822 cpl_propertylist_delete(plist);
2823 cpl_propertylist_delete(tmpplist);
2824 cpl_table_delete(table);
2825 cpl_array_delete(names);
2826 cpl_array_delete(emptyarray);
2827 cpl_free(regexp);
2828 return NULL;
2829}
2830
2831
2832cpl_error_code irplib_sdp_spectrum_save(const irplib_sdp_spectrum *self,
2833 const char *filename,
2834 const cpl_propertylist *extra_pheader,
2835 const cpl_propertylist *extra_theader)
2836{
2837 cpl_error_code error;
2838 cpl_propertylist *primarykeys = NULL;
2839 cpl_propertylist *tablekeys = NULL;
2840 char *regexp = NULL;
2841
2842 cpl_ensure_code(self != NULL, CPL_ERROR_NULL_INPUT);
2843
2844 assert(self->proplist != NULL);
2845 assert(self->table != NULL);
2846
2847 /* Make a regular expression to filter out all keywords found in the spectrum
2848 * object's proplist and NELEM from the extra header keywords. */
2849 regexp = _irplib_make_regexp(self->proplist, KEY_NELEM);
2850 cpl_error_ensure(regexp != NULL, cpl_error_get_code(), goto cleanup,
2851 "Could not create regular expression to filter keywords.");
2852
2853 /* Copy out keywords that should be in the primary HDU header from the full
2854 * list of keywords in proplist. */
2855 primarykeys = cpl_propertylist_new();
2856 error = cpl_propertylist_copy_property_regexp(primarykeys, self->proplist,
2857 PRIMARY_HDU_KEYS_REGEXP, 0);
2858 cpl_error_ensure(! error, error, goto cleanup,
2859 "Failed to extract keywords for primary HDU.");
2860
2861 /* Use a different comment name for OBJECT in the primary HDU to more closely
2862 * follow standard document. */
2863 if (cpl_propertylist_has(primarykeys, KEY_OBJECT)) {
2864 error = cpl_propertylist_set_comment(primarykeys, KEY_OBJECT,
2865 KEY_OBJECT_PHDU_COMMENT);
2866 cpl_error_ensure(! error, error, goto cleanup,
2867 "Could not update comment for '%s' in primary HDU.", KEY_OBJECT);
2868 }
2869
2870 /* Copy any extra keywords that are not already in the primary HDU header. */
2871 if (extra_pheader != NULL) {
2872 error = cpl_propertylist_copy_property_regexp(primarykeys, extra_pheader,
2873 regexp, 1);
2874 cpl_error_ensure(! error, error, goto cleanup,
2875 "Could not add extra keywords for primary HDU.");
2876 }
2877
2878 /* Copy out keywords for the table header from all in proplist. */
2879 tablekeys = cpl_propertylist_new();
2880 error = cpl_propertylist_copy_property_regexp(tablekeys, self->proplist,
2881 EXTENSION_HDU_KEYS_REGEXP, 0);
2882 cpl_error_ensure(! error, error, goto cleanup,
2883 "Failed to extract keywords for extension HDU.");
2884
2885 /* Add the NELEM keyword from the nelem variable. */
2886 cpl_error_ensure(self->nelem <= INT_MAX, CPL_ERROR_INCOMPATIBLE_INPUT,
2887 goto cleanup,
2888 "The value for the keyword '%s' is too big (> %d).",
2889 KEY_NELEM, INT_MAX);
2890 error = cpl_propertylist_append_int(tablekeys, KEY_NELEM,
2891 (int) self->nelem);
2892 error |= cpl_propertylist_set_comment(tablekeys, KEY_NELEM,
2893 KEY_NELEM_COMMENT);
2894 cpl_error_ensure(! error, error, goto cleanup,
2895 "Could not add keyword '%s' to primary HDU or set the comment.",
2896 KEY_NELEM);
2897
2898 /* Copy extra keywords that are not already in the extension HDU header. */
2899 if (extra_theader != NULL) {
2900 error = cpl_propertylist_copy_property_regexp(tablekeys, extra_theader,
2901 regexp, 1);
2902 cpl_error_ensure(! error, error, goto cleanup,
2903 "Could not add extra keywords for extension HDU.");
2904 }
2905
2906 cpl_free(regexp);
2907 regexp = NULL;
2908
2909 /* Add some mandatory keywords with default values that are still not found
2910 * in the primary or extension property lists, since they were not set in the
2911 * spectrum or in the extra header lists. */
2912 error = CPL_ERROR_NONE;
2913 if (! cpl_propertylist_has(primarykeys, KEY_ORIGIN)) {
2914 error |= cpl_propertylist_append_string(primarykeys, KEY_ORIGIN,
2915 KEY_ORIGIN_VALUE);
2916 error |= cpl_propertylist_set_comment(primarykeys, KEY_ORIGIN,
2917 KEY_ORIGIN_COMMENT);
2918 }
2919 if (! cpl_propertylist_has(primarykeys, KEY_PRODLVL)) {
2920 error |= cpl_propertylist_append_int(primarykeys, KEY_PRODLVL,
2921 KEY_PRODLVL_VALUE);
2922 error |= cpl_propertylist_set_comment(primarykeys, KEY_PRODLVL,
2923 KEY_PRODLVL_COMMENT);
2924 }
2925 if (! cpl_propertylist_has(primarykeys, KEY_SPECSYS)) {
2926 error |= cpl_propertylist_append_string(primarykeys, KEY_SPECSYS,
2927 KEY_SPECSYS_VALUE);
2928 error |= cpl_propertylist_set_comment(primarykeys, KEY_SPECSYS,
2929 KEY_SPECSYS_COMMENT);
2930 }
2931 if (! cpl_propertylist_has(primarykeys, KEY_FLUXERR)) {
2932 error |= cpl_propertylist_append_int(primarykeys, KEY_FLUXERR,
2933 KEY_FLUXERR_VALUE);
2934 error |= cpl_propertylist_set_comment(primarykeys, KEY_FLUXERR,
2935 KEY_FLUXERR_COMMENT);
2936 }
2937 if (! cpl_propertylist_has(tablekeys, KEY_VOCLASS)) {
2938 error |= cpl_propertylist_append_string(tablekeys, KEY_VOCLASS,
2939 KEY_VOCLASS_VALUE);
2940 error |= cpl_propertylist_set_comment(tablekeys, KEY_VOCLASS,
2941 KEY_VOCLASS_COMMENT);
2942 }
2943 if (! cpl_propertylist_has(tablekeys, KEY_VOPUB)) {
2944 error |= cpl_propertylist_append_string(tablekeys, KEY_VOPUB,
2945 KEY_VOPUB_VALUE);
2946 error |= cpl_propertylist_set_comment(tablekeys, KEY_VOPUB,
2947 KEY_VOPUB_COMMENT);
2948 }
2949 if (! cpl_propertylist_has(tablekeys, KEY_EXTNAME)) {
2950 error |= cpl_propertylist_append_string(tablekeys, KEY_EXTNAME,
2951 KEY_EXTNAME_VALUE);
2952 error |= cpl_propertylist_set_comment(tablekeys, KEY_EXTNAME,
2953 KEY_EXTNAME_COMMENT);
2954 }
2955 if (! cpl_propertylist_has(tablekeys, KEY_INHERIT)) {
2956 error |= cpl_propertylist_append_bool(tablekeys, KEY_INHERIT,
2957 KEY_INHERIT_VALUE);
2958 error |= cpl_propertylist_set_comment(tablekeys, KEY_INHERIT,
2959 KEY_INHERIT_COMMENT);
2960 }
2961 cpl_error_ensure(! error, cpl_error_get_code(), goto cleanup,
2962 "Could not set default header keywords for file '%s'.",
2963 filename);
2964
2965 error = cpl_table_save(self->table, primarykeys, tablekeys, filename,
2966 CPL_IO_CREATE);
2967 cpl_error_ensure(! error, error, goto cleanup,
2968 "Could not save the spectrum table to file '%s'.", filename);
2969
2970 cpl_propertylist_delete(primarykeys);
2971 cpl_propertylist_delete(tablekeys);
2972
2973 return CPL_ERROR_NONE;
2974
2975cleanup:
2976 /* Cleanup memory if an error occurred. Note: cpl_*_delete functions already
2977 * check for NULL pointers. */
2978 cpl_propertylist_delete(primarykeys);
2979 cpl_propertylist_delete(tablekeys);
2980 cpl_free(regexp);
2981 return cpl_error_get_code();
2982}
2983
2984
2985cpl_error_code irplib_dfs_save_spectrum(cpl_frameset * allframes,
2986 cpl_propertylist * header,
2987 const cpl_parameterlist * parlist,
2988 const cpl_frameset * usedframes,
2989 const cpl_frame * inherit,
2990 const irplib_sdp_spectrum * spectrum,
2991 const char * recipe,
2992 const cpl_propertylist * applist,
2993 const cpl_propertylist * tablelist,
2994 const char * remregexp,
2995 const char * pipe_id,
2996 const char * dict_id,
2997 const char * filename)
2998{
2999 const char * procat;
3000 cpl_propertylist * plist = NULL;
3001 cpl_frame * product_frame = NULL;
3002 cpl_error_code error;
3003
3004 cpl_ensure_code(allframes != NULL, CPL_ERROR_NULL_INPUT);
3005 cpl_ensure_code(parlist != NULL, CPL_ERROR_NULL_INPUT);
3006 cpl_ensure_code(usedframes != NULL, CPL_ERROR_NULL_INPUT);
3007 cpl_ensure_code(spectrum != NULL, CPL_ERROR_NULL_INPUT);
3008 cpl_ensure_code(recipe != NULL, CPL_ERROR_NULL_INPUT);
3009 cpl_ensure_code(applist != NULL, CPL_ERROR_NULL_INPUT);
3010 cpl_ensure_code(pipe_id != NULL, CPL_ERROR_NULL_INPUT);
3011 cpl_ensure_code(dict_id != NULL, CPL_ERROR_NULL_INPUT);
3012 cpl_ensure_code(filename != NULL, CPL_ERROR_NULL_INPUT);
3013
3014 procat = cpl_propertylist_get_string(applist, CPL_DFS_PRO_CATG);
3015 cpl_error_ensure(procat != NULL, cpl_error_get_code(), goto cleanup,
3016 "Could not find keyword '%s' in 'applist'.", CPL_DFS_PRO_CATG);
3017
3018 /* Create product frame */
3019 product_frame = cpl_frame_new();
3020 error = cpl_frame_set_filename(product_frame, filename);
3021 error |= cpl_frame_set_tag(product_frame, procat);
3022 error |= cpl_frame_set_type(product_frame, CPL_FRAME_TYPE_TABLE);
3023 error |= cpl_frame_set_group(product_frame, CPL_FRAME_GROUP_PRODUCT);
3024 error |= cpl_frame_set_level(product_frame, CPL_FRAME_LEVEL_FINAL);
3025 cpl_error_ensure(! error, cpl_error_get_code(), goto cleanup,
3026 "Failed to setup the product frame.");
3027
3028 /* Check if we should return the header information actually filled or just
3029 * create a temporary local list. */
3030 if (header != NULL) {
3031 cpl_propertylist_empty(header);
3032 plist = header;
3033 } else {
3034 plist = cpl_propertylist_new();
3035 }
3036
3037 /* Add any QC parameters here. */
3038 error = cpl_propertylist_append(plist, applist);
3039 cpl_error_ensure(! error, error, goto cleanup,
3040 "Could not append extra keywords when writing file '%s'.", filename);
3041
3042 /* Add DataFlow keywords. */
3043 error = cpl_dfs_setup_product_header(plist, product_frame, usedframes,
3044 parlist, recipe, pipe_id, dict_id,
3045 inherit);
3046 cpl_error_ensure(! error, error, goto cleanup,
3047 "Failed to setup DFS keywords when writing file '%s'.", filename);
3048
3049 /* We have to update the extra keywords again for the primary HDU to make
3050 * sure we have the ability to override what cpl_dfs_setup_product_header
3051 * sets. The reason for still having the cpl_propertylist_append above is to
3052 * make sure we use comments as given by the applist and not as found in the
3053 * raw file we inherit from. The SDP format prefers standardised comments, not
3054 * necessarily used by the raw files. */
3055 error = cpl_propertylist_copy_property_regexp(plist, applist, ".*", 0);
3056 cpl_error_ensure(! error, error, goto cleanup,
3057 "Could not update extra keywords when writing file '%s'.", filename);
3058
3059 if (remregexp != NULL) {
3060 cpl_errorstate prestate = cpl_errorstate_get();
3061 (void) cpl_propertylist_erase_regexp(plist, remregexp, 0);
3062 cpl_error_ensure(cpl_errorstate_is_equal(prestate), cpl_error_get_code(),
3063 goto cleanup,
3064 "Failed to filter keywords when writing file '%s'.",
3065 filename);
3066 }
3067
3068 error = irplib_sdp_spectrum_save(spectrum, filename, plist, tablelist);
3069 cpl_error_ensure(! error, error, goto cleanup,
3070 "Failed to save SPD spectrum to file '%s'.", filename);
3071
3072 /* Optionally return the SDP keywords that were written to the output. */
3073 if (header != NULL) {
3074 error = cpl_propertylist_copy_property_regexp(header, spectrum->proplist,
3075 ".*", 0);
3076 cpl_error_ensure(! error, error, goto cleanup,
3077 "Could not return SDP keywords in header output.");
3078 }
3079
3080 /* Insert the frame of the saved file in the input frameset. */
3081 error = cpl_frameset_insert(allframes, product_frame);
3082 cpl_error_ensure(! error, error, goto cleanup,
3083 "Failed to insert new product frame when writing file '%s'.", filename);
3084
3085 /* Delete output property list if it was only a temporary local object. */
3086 if (plist != header) cpl_propertylist_delete(plist);
3087
3088 return CPL_ERROR_NONE;
3089
3090cleanup:
3091 /* If an error occurred we come here to cleanup memory. Note that the delete
3092 * functions already check for NULL pointers. */
3093 if (header != NULL) {
3094 cpl_errorstate prestate = cpl_errorstate_get();
3095 (void) cpl_propertylist_empty(header);
3096 cpl_errorstate_set(prestate);
3097 } else {
3098 cpl_propertylist_delete(plist);
3099 }
3100 cpl_frame_delete(product_frame);
3101 return cpl_error_get_code();
3102}
3103
3104
3105void irplib_sdp_spectrum_dump(const irplib_sdp_spectrum *self, FILE *stream)
3106{
3107 if (stream == NULL) {
3108 stream = stdout;
3109 }
3110 if (self == NULL) {
3111 fprintf(stream, "NULL SDP spectrum\n\n");
3112 return;
3113 }
3114
3115 assert(self->proplist != NULL);
3116 assert(self->table != NULL);
3117
3118 fprintf(stream, "SDP spectrum at address %p\n", (void*)self);
3119 fprintf(stream, "NELEM = %"CPL_SIZE_FORMAT"\n", self->nelem);
3120 cpl_propertylist_dump(self->proplist, stream);
3121 cpl_table_dump_structure(self->table, stream);
3122 cpl_table_dump(self->table, 0, cpl_table_get_nrow(self->table), stream);
3123}
3124
3125
3126#ifdef IRPLIB_USE_FITS_UPDATE_CHECKSUM
3127
3139cpl_error_code irplib_fits_update_checksums(const char* filename)
3140{
3141 fitsfile* filehandle;
3142 int error = 0; /* must be initialised to zero before call to cfitsio. */
3143
3144 if (fits_open_diskfile(&filehandle, filename, READWRITE, &error)) {
3145 return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
3146 "Could not open file '%s' to update CHECKSUM keywords"
3147 " (error = %d).", filename, error);
3148 }
3149
3150 int i = 0;
3151 while (! fits_movabs_hdu(filehandle, ++i, NULL, &error)) {
3152 if (fits_write_chksum(filehandle, &error)) {
3153 return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
3154 "Could not update the CHECKSUM keywords in '%s' HDU %d"
3155 " (error = %d).", filename, i, error);
3156 }
3157 }
3158 /* Reset after normal error */
3159 if (error == END_OF_FILE) error = 0;
3160
3161 if (fits_close_file(filehandle, &error)) {
3162 return cpl_error_set_message(cpl_func, CPL_ERROR_FILE_IO,
3163 "There was a problem trying to close the file '%s'"
3164 " (error = %d).", filename, error);
3165 }
3166 return CPL_ERROR_NONE;
3167}
3168
3169#endif /* IRPLIB_USE_FITS_UPDATE_CHECKSUM */
struct _irplib_sdp_spectrum_ irplib_sdp_spectrum
Data type for a Science Data Product 1D spectrum.