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