1 : /******************************************************************************
2 : * $Id: fitsdataset.cpp 25284 2012-12-03 21:07:56Z rouault $
3 : *
4 : * Project: FITS Driver
5 : * Purpose: Implement FITS raster read/write support
6 : * Author: Simon Perkins, s.perkins@lanl.gov
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2001, Simon Perkins
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 :
31 :
32 : #include "gdal_pam.h"
33 : #include "cpl_string.h"
34 : #include <string.h>
35 :
36 : CPL_CVSID("$Id: fitsdataset.cpp 25284 2012-12-03 21:07:56Z rouault $");
37 :
38 : CPL_C_START
39 : #include <fitsio.h>
40 : void GDALRegister_FITS(void);
41 : CPL_C_END
42 :
43 : /************************************************************************/
44 : /* ==================================================================== */
45 : /* FITSDataset */
46 : /* ==================================================================== */
47 : /************************************************************************/
48 :
49 : class FITSRasterBand;
50 :
51 : class FITSDataset : public GDALPamDataset {
52 :
53 : friend class FITSRasterBand;
54 :
55 : fitsfile* hFITS;
56 :
57 : GDALDataType gdalDataType; // GDAL code for the image type
58 : int fitsDataType; // FITS code for the image type
59 :
60 : bool isExistingFile;
61 : long highestOffsetWritten; // How much of image has been written
62 :
63 : FITSDataset(); // Others shouldn't call this constructor explicitly
64 :
65 : CPLErr Init(fitsfile* hFITS_, bool isExistingFile_);
66 :
67 : public:
68 : ~FITSDataset();
69 :
70 : static GDALDataset* Open(GDALOpenInfo* );
71 : static GDALDataset* Create(const char* pszFilename,
72 : int nXSize, int nYSize, int nBands,
73 : GDALDataType eType,
74 : char** papszParmList);
75 :
76 : };
77 :
78 : /************************************************************************/
79 : /* ==================================================================== */
80 : /* FITSRasterBand */
81 : /* ==================================================================== */
82 : /************************************************************************/
83 :
84 : class FITSRasterBand : public GDALPamRasterBand {
85 :
86 : friend class FITSDataset;
87 :
88 : public:
89 :
90 : FITSRasterBand(FITSDataset*, int);
91 : ~FITSRasterBand();
92 :
93 : virtual CPLErr IReadBlock( int, int, void * );
94 : virtual CPLErr IWriteBlock( int, int, void * );
95 : };
96 :
97 :
98 : /************************************************************************/
99 : /* FITSRasterBand() */
100 : /************************************************************************/
101 :
102 101 : FITSRasterBand::FITSRasterBand(FITSDataset *poDS, int nBand) {
103 :
104 101 : this->poDS = poDS;
105 101 : this->nBand = nBand;
106 101 : eDataType = poDS->gdalDataType;
107 101 : nBlockXSize = poDS->nRasterXSize;;
108 101 : nBlockYSize = 1;
109 101 : }
110 :
111 : /************************************************************************/
112 : /* ~FITSRasterBand() */
113 : /************************************************************************/
114 :
115 101 : FITSRasterBand::~FITSRasterBand() {
116 101 : FlushCache();
117 101 : }
118 :
119 : /************************************************************************/
120 : /* IReadBlock() */
121 : /************************************************************************/
122 :
123 100 : CPLErr FITSRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
124 : void* pImage ) {
125 :
126 : // A FITS block is one row (we assume BSQ formatted data)
127 100 : FITSDataset* dataset = (FITSDataset*) poDS;
128 100 : fitsfile* hFITS = dataset->hFITS;
129 100 : int status = 0;
130 :
131 : // Since a FITS block is a whole row, nBlockXOff must be zero
132 : // and the row number equals the y block offset. Also, nBlockYOff
133 : // cannot be greater than the number of rows
134 100 : CPLAssert(nBlockXOff == 0);
135 100 : CPLAssert(nBlockYOff < nRasterYSize);
136 :
137 : // Calculate offsets and read in the data. Note that FITS array offsets
138 : // start at 1...
139 : long offset = (nBand - 1) * nRasterXSize * nRasterYSize +
140 100 : nBlockYOff * nRasterXSize + 1;
141 100 : long nElements = nRasterXSize;
142 :
143 : // If we haven't written this block to the file yet, then attempting
144 : // to read causes an error, so in this case, just return zeros.
145 100 : if (!dataset->isExistingFile && offset > dataset->highestOffsetWritten) {
146 : memset(pImage, 0, nBlockXSize * nBlockYSize
147 0 : * GDALGetDataTypeSize(eDataType) / 8);
148 0 : return CE_None;
149 : }
150 :
151 : // Otherwise read in the image data
152 : fits_read_img(hFITS, dataset->fitsDataType, offset, nElements,
153 100 : 0, pImage, 0, &status);
154 100 : if (status) {
155 : CPLError(CE_Failure, CPLE_AppDefined,
156 0 : "Couldn't read image data from FITS file (%d).", status);
157 0 : return CE_Failure;
158 : }
159 :
160 100 : return CE_None;
161 : }
162 :
163 : /************************************************************************/
164 : /* IWriteBlock() */
165 : /* */
166 : /************************************************************************/
167 :
168 310 : CPLErr FITSRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff,
169 : void* pImage) {
170 :
171 310 : FITSDataset* dataset = (FITSDataset*) poDS;
172 310 : fitsfile* hFITS = dataset->hFITS;
173 310 : int status = 0;
174 :
175 : // Since a FITS block is a whole row, nBlockXOff must be zero
176 : // and the row number equals the y block offset. Also, nBlockYOff
177 : // cannot be greater than the number of rows
178 :
179 : // Calculate offsets and read in the data. Note that FITS array offsets
180 : // start at 1...
181 : long offset = (nBand - 1) * nRasterXSize * nRasterYSize +
182 310 : nBlockYOff * nRasterXSize + 1;
183 310 : long nElements = nRasterXSize;
184 : fits_write_img(hFITS, dataset->fitsDataType, offset, nElements,
185 310 : pImage, &status);
186 :
187 : // Capture special case of non-zero status due to data range
188 : // overflow Standard GDAL policy is to silently truncate, which is
189 : // what CFITSIO does, in addition to returning NUM_OVERFLOW (412) as
190 : // the status.
191 310 : if (status == NUM_OVERFLOW)
192 0 : status = 0;
193 :
194 : // Check for other errors
195 310 : if (status) {
196 : CPLError(CE_Failure, CPLE_AppDefined,
197 0 : "Error writing image data to FITS file (%d).", status);
198 0 : return CE_Failure;
199 : }
200 :
201 : // When we write a block, update the offset counter that we've written
202 310 : if (offset > dataset->highestOffsetWritten)
203 310 : dataset->highestOffsetWritten = offset;
204 :
205 310 : return CE_None;
206 : }
207 :
208 : /************************************************************************/
209 : /* ==================================================================== */
210 : /* FITSDataset */
211 : /* ==================================================================== */
212 : /************************************************************************/
213 :
214 : // Some useful utility functions
215 :
216 : // Simple static function to determine if FITS header keyword should
217 : // be saved in meta data.
218 : static const int ignorableHeaderCount = 15;
219 : static const char* ignorableFITSHeaders[ignorableHeaderCount] = {
220 : "SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "END",
221 : "XTENSION", "PCOUNT", "GCOUNT", "EXTEND", "CONTINUE",
222 : "COMMENT", "", "LONGSTRN"
223 : };
224 626 : static bool isIgnorableFITSHeader(const char* name) {
225 5100 : for (int i = 0; i < ignorableHeaderCount; ++i) {
226 5092 : if (strcmp(name, ignorableFITSHeaders[i]) == 0)
227 618 : return true;
228 : }
229 8 : return false;
230 : }
231 :
232 :
233 : /************************************************************************/
234 : /* FITSDataset() */
235 : /************************************************************************/
236 :
237 57 : FITSDataset::FITSDataset() {
238 57 : hFITS = 0;
239 57 : }
240 :
241 : /************************************************************************/
242 : /* ~FITSDataset() */
243 : /************************************************************************/
244 :
245 57 : FITSDataset::~FITSDataset() {
246 :
247 : int status;
248 57 : if (hFITS) {
249 57 : if(eAccess == GA_Update) { // Only do this if we've successfully opened the file and update capability
250 : // Write any meta data to the file that's compatible with FITS
251 30 : status = 0;
252 30 : fits_movabs_hdu(hFITS, 1, 0, &status);
253 30 : fits_write_key_longwarn(hFITS, &status);
254 30 : if (status) {
255 : CPLError(CE_Warning, CPLE_AppDefined,
256 : "Couldn't move to first HDU in FITS file %s (%d).\n",
257 0 : GetDescription(), status);
258 : }
259 30 : char** metaData = GetMetadata();
260 30 : int count = CSLCount(metaData);
261 46 : for (int i = 0; i < count; ++i) {
262 16 : const char* field = CSLGetField(metaData, i);
263 16 : if (strlen(field) == 0)
264 0 : continue;
265 : else {
266 16 : char* key = NULL;
267 16 : const char* value = CPLParseNameValue(field, &key);
268 : // FITS keys must be less than 8 chars
269 16 : if (key != NULL && strlen(key) <= 8 && !isIgnorableFITSHeader(key)) {
270 : // Although FITS provides support for different value
271 : // types, the GDAL Metadata mechanism works only with
272 : // string values. Prior to about 2003-05-02, this driver
273 : // would attempt to guess the value type from the metadata
274 : // value string amd then would use the appropriate
275 : // type-specific FITS keyword update routine. This was
276 : // found to be troublesome (e.g. a numeric version string
277 : // with leading zeros would be interpreted as a number
278 : // and might get those leading zeros stripped), and so now
279 : // the driver writes every value as a string. In practice
280 : // this is not a problem since most FITS reading routines
281 : // will convert from strings to numbers automatically, but
282 : // if you want finer control, use the underlying FITS
283 : // handle. Note: to avoid a compiler warning we copy the
284 : // const value string to a non const one...
285 2 : char* valueCpy = strdup(value);
286 2 : fits_update_key_longstr(hFITS, key, valueCpy, 0, &status);
287 2 : free(valueCpy);
288 :
289 : // Check for errors
290 2 : if (status) {
291 : CPLError(CE_Warning, CPLE_AppDefined,
292 : "Couldn't update key %s in FITS file %s (%d).",
293 0 : key, GetDescription(), status);
294 : return;
295 : }
296 : }
297 : // Must free up key
298 16 : CPLFree(key);
299 : }
300 : }
301 :
302 : // Make sure we flush the raster cache before we close the file!
303 30 : FlushCache();
304 : }
305 :
306 : // Close the FITS handle - ignore the error status
307 57 : status = 0;
308 57 : fits_close_file(hFITS, &status);
309 :
310 : }
311 0 : }
312 :
313 :
314 : /************************************************************************/
315 : /* Init() */
316 : /************************************************************************/
317 :
318 57 : CPLErr FITSDataset::Init(fitsfile* hFITS_, bool isExistingFile_) {
319 :
320 57 : hFITS = hFITS_;
321 57 : isExistingFile = isExistingFile_;
322 57 : highestOffsetWritten = 0;
323 57 : int status = 0;
324 :
325 : // Move to the primary HDU
326 57 : fits_movabs_hdu(hFITS, 1, 0, &status);
327 57 : if (status) {
328 : CPLError(CE_Failure, CPLE_AppDefined,
329 : "Couldn't move to first HDU in FITS file %s (%d).\n",
330 0 : GetDescription(), status);
331 0 : return CE_Failure;
332 : }
333 :
334 : // The cftsio driver automatically rescales data on read and write
335 : // if BSCALE and BZERO are defined as header keywords. This behavior
336 : // causes overflows with GDAL and is slightly mysterious, so we
337 : // disable this rescaling here.
338 57 : fits_set_bscale(hFITS, 1.0, 0.0, &status);
339 :
340 : // Get the image info for this dataset (note that all bands in a FITS dataset
341 : // have the same type)
342 : int bitpix;
343 : int naxis;
344 57 : const int maxdim = 3;
345 : long naxes[maxdim];
346 57 : fits_get_img_param(hFITS, maxdim, &bitpix, &naxis, naxes, &status);
347 57 : if (status) {
348 : CPLError(CE_Failure, CPLE_AppDefined,
349 : "Couldn't determine image parameters of FITS file %s (%d).",
350 0 : GetDescription(), status);
351 0 : return CE_Failure;
352 : }
353 :
354 : // Determine data type
355 57 : if (bitpix == BYTE_IMG) {
356 25 : gdalDataType = GDT_Byte;
357 25 : fitsDataType = TBYTE;
358 : }
359 32 : else if (bitpix == SHORT_IMG) {
360 8 : gdalDataType = GDT_Int16;
361 8 : fitsDataType = TSHORT;
362 : }
363 24 : else if (bitpix == LONG_IMG) {
364 8 : gdalDataType = GDT_Int32;
365 8 : fitsDataType = TINT;
366 : }
367 16 : else if (bitpix == FLOAT_IMG) {
368 8 : gdalDataType = GDT_Float32;
369 8 : fitsDataType = TFLOAT;
370 : }
371 8 : else if (bitpix == DOUBLE_IMG) {
372 8 : gdalDataType = GDT_Float64;
373 8 : fitsDataType = TDOUBLE;
374 : }
375 : else {
376 : CPLError(CE_Failure, CPLE_AppDefined,
377 0 : "FITS file %s has unknown data type: %d.", GetDescription(),
378 0 : bitpix);
379 0 : return CE_Failure;
380 : }
381 :
382 : // Determine image dimensions - we assume BSQ ordering
383 57 : if (naxis == 2) {
384 35 : nRasterXSize = naxes[0];
385 35 : nRasterYSize = naxes[1];
386 35 : nBands = 1;
387 : }
388 22 : else if (naxis == 3) {
389 22 : nRasterXSize = naxes[0];
390 22 : nRasterYSize = naxes[1];
391 22 : nBands = naxes[2];
392 : }
393 : else {
394 : CPLError(CE_Failure, CPLE_AppDefined,
395 : "FITS file %s does not have 2 or 3 dimensions.",
396 0 : GetDescription());
397 0 : return CE_Failure;
398 : }
399 :
400 : // Create the bands
401 316 : for (int i = 0; i < nBands; ++i)
402 101 : SetBand(i+1, new FITSRasterBand(this, i+1));
403 :
404 : // Read header information from file and use it to set metadata
405 : // This process understands the CONTINUE standard for long strings.
406 : // We don't bother to capture header names that duplicate information
407 : // already captured elsewhere (e.g. image dimensions and type)
408 : int keyNum;
409 : char key[100];
410 : char value[100];
411 :
412 57 : int nKeys = 0, nMoreKeys = 0;
413 57 : fits_get_hdrspace(hFITS, &nKeys, &nMoreKeys, &status);
414 681 : for(keyNum = 1; keyNum <= nKeys; keyNum++)
415 : {
416 624 : fits_read_keyn(hFITS, keyNum, key, value, 0, &status);
417 624 : if (status) {
418 : CPLError(CE_Failure, CPLE_AppDefined,
419 : "Error while reading key %d from FITS file %s (%d)",
420 0 : keyNum, GetDescription(), status);
421 0 : return CE_Failure;
422 : }
423 624 : if (strcmp(key, "END") == 0) {
424 : /* we shouldn't get here in principle since the END */
425 : /* keyword shouldn't be counted in nKeys, but who knows... */
426 0 : break;
427 : }
428 624 : else if (isIgnorableFITSHeader(key)) {
429 : // Ignore it!
430 : }
431 : else { // Going to store something, but check for long strings etc
432 : // Strip off leading and trailing quote if present
433 6 : char* newValue = value;
434 6 : if (value[0] == '\'' && value[strlen(value) - 1] == '\'') {
435 6 : newValue = value + 1;
436 6 : value[strlen(value) - 1] = '\0';
437 : }
438 : // Check for long string
439 6 : if (strrchr(newValue, '&') == newValue + strlen(newValue) - 1) {
440 : // Value string ends in "&", so use long string conventions
441 0 : char* longString = 0;
442 0 : fits_read_key_longstr(hFITS, key, &longString, 0, &status);
443 : // Note that read_key_longst already strips quotes
444 0 : if (status) {
445 : CPLError(CE_Failure, CPLE_AppDefined,
446 : "Error while reading long string for key %s from "
447 0 : "FITS file %s (%d)", key, GetDescription(), status);
448 0 : return CE_Failure;
449 : }
450 0 : SetMetadataItem(key, longString);
451 0 : free(longString);
452 : }
453 : else { // Normal keyword
454 6 : SetMetadataItem(key, newValue);
455 : }
456 : }
457 : }
458 :
459 57 : return CE_None;
460 : }
461 :
462 :
463 :
464 : /************************************************************************/
465 : /* Open() */
466 : /************************************************************************/
467 :
468 13690 : GDALDataset* FITSDataset::Open(GDALOpenInfo* poOpenInfo) {
469 :
470 13690 : const char* fitsID = "SIMPLE = T"; // Spaces important!
471 13690 : size_t fitsIDLen = strlen(fitsID); // Should be 30 chars long
472 :
473 13690 : if ((size_t)poOpenInfo->nHeaderBytes < fitsIDLen)
474 11883 : return NULL;
475 1807 : if (memcmp(poOpenInfo->pabyHeader, fitsID, fitsIDLen))
476 1779 : return NULL;
477 :
478 : // Get access mode and attempt to open the file
479 28 : int status = 0;
480 28 : fitsfile* hFITS = 0;
481 28 : if (poOpenInfo->eAccess == GA_ReadOnly)
482 27 : fits_open_file(&hFITS, poOpenInfo->pszFilename, READONLY, &status);
483 : else
484 1 : fits_open_file(&hFITS, poOpenInfo->pszFilename, READWRITE, &status);
485 28 : if (status) {
486 : CPLError(CE_Failure, CPLE_AppDefined,
487 : "Error while opening FITS file %s (%d).\n",
488 0 : poOpenInfo->pszFilename, status);
489 0 : fits_close_file(hFITS, &status);
490 0 : return NULL;
491 : }
492 :
493 : // Create a FITSDataset object and initialize it from the FITS handle
494 28 : FITSDataset* dataset = new FITSDataset();
495 28 : dataset->eAccess = poOpenInfo->eAccess;
496 :
497 : // Set up the description and initialize the dataset
498 28 : dataset->SetDescription(poOpenInfo->pszFilename);
499 28 : if (dataset->Init(hFITS, true) != CE_None) {
500 0 : delete dataset;
501 0 : return NULL;
502 : }
503 : else
504 : {
505 : /* -------------------------------------------------------------------- */
506 : /* Initialize any PAM information. */
507 : /* -------------------------------------------------------------------- */
508 28 : dataset->SetDescription( poOpenInfo->pszFilename );
509 28 : dataset->TryLoadXML();
510 :
511 : /* -------------------------------------------------------------------- */
512 : /* Check for external overviews. */
513 : /* -------------------------------------------------------------------- */
514 28 : dataset->oOvManager.Initialize( dataset, poOpenInfo->pszFilename, poOpenInfo->papszSiblingFiles );
515 :
516 28 : return dataset;
517 : }
518 : }
519 :
520 :
521 : /************************************************************************/
522 : /* Create() */
523 : /* */
524 : /* Create a new FITS file. */
525 : /************************************************************************/
526 :
527 58 : GDALDataset *FITSDataset::Create(const char* pszFilename,
528 : int nXSize, int nYSize,
529 : int nBands, GDALDataType eType,
530 : char** papszParmList) {
531 :
532 :
533 : FITSDataset* dataset;
534 : fitsfile* hFITS;
535 58 : int status = 0;
536 :
537 : // No creation options are defined. The BSCALE/BZERO options were
538 : // removed on 2002-07-02 by Simon Perkins because they introduced
539 : // excessive complications and didn't really fit into the GDAL
540 : // paradigm.
541 :
542 : // Create the file - to force creation, we prepend the name with '!'
543 58 : char* extFilename = new char[strlen(pszFilename) + 10]; // 10 for margin!
544 58 : sprintf(extFilename, "!%s", pszFilename);
545 58 : fits_create_file(&hFITS, extFilename, &status);
546 58 : delete[] extFilename;
547 58 : if (status) {
548 : CPLError(CE_Failure, CPLE_AppDefined,
549 11 : "Couldn't create FITS file %s (%d).\n", pszFilename, status);
550 11 : return NULL;
551 : }
552 :
553 : // Determine FITS type of image
554 : int bitpix;
555 47 : if (eType == GDT_Byte)
556 13 : bitpix = BYTE_IMG;
557 34 : else if (eType == GDT_Int16)
558 4 : bitpix = SHORT_IMG;
559 30 : else if (eType == GDT_Int32)
560 4 : bitpix = LONG_IMG;
561 26 : else if (eType == GDT_Float32)
562 4 : bitpix = FLOAT_IMG;
563 22 : else if (eType == GDT_Float64)
564 4 : bitpix = DOUBLE_IMG;
565 : else {
566 : CPLError(CE_Failure, CPLE_AppDefined,
567 18 : "GDALDataType (%d) unsupported for FITS", eType);
568 18 : fits_close_file(hFITS, &status);
569 18 : return NULL;
570 : }
571 :
572 : // Now create an image of appropriate size and type
573 29 : long naxes[3] = {nXSize, nYSize, nBands};
574 29 : int naxis = (nBands == 1) ? 2 : 3;
575 29 : fits_create_img(hFITS, bitpix, naxis, naxes, &status);
576 :
577 : // Check the status
578 29 : if (status) {
579 : CPLError(CE_Failure, CPLE_AppDefined,
580 : "Couldn't create image within FITS file %s (%d).",
581 0 : pszFilename, status);
582 0 : fits_close_file(hFITS, &status);
583 0 : return NULL;
584 : }
585 :
586 29 : dataset = new FITSDataset();
587 29 : dataset->nRasterXSize = nXSize;
588 29 : dataset->nRasterYSize = nYSize;
589 29 : dataset->eAccess = GA_Update;
590 29 : dataset->SetDescription(pszFilename);
591 :
592 : // Init recalculates a lot of stuff we already know, but...
593 29 : if (dataset->Init(hFITS, false) != CE_None) {
594 0 : delete dataset;
595 0 : return NULL;
596 : }
597 : else {
598 29 : return dataset;
599 : }
600 : }
601 :
602 :
603 : /************************************************************************/
604 : /* GDALRegister_FITS() */
605 : /************************************************************************/
606 :
607 582 : void GDALRegister_FITS() {
608 :
609 : GDALDriver* poDriver;
610 :
611 582 : if( GDALGetDriverByName( "FITS" ) == NULL) {
612 561 : poDriver = new GDALDriver();
613 :
614 561 : poDriver->SetDescription( "FITS" );
615 : poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
616 561 : "Flexible Image Transport System" );
617 : poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
618 561 : "frmt_various.html#FITS" );
619 : poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
620 561 : "Byte Int16 Int32 Float32 Float64" );
621 :
622 561 : poDriver->pfnOpen = FITSDataset::Open;
623 561 : poDriver->pfnCreate = FITSDataset::Create;
624 561 : poDriver->pfnCreateCopy = NULL;
625 :
626 561 : GetGDALDriverManager()->RegisterDriver(poDriver);
627 : }
628 582 : }
|