1 : /******************************************************************************
2 : * $Id: fitsdataset.cpp 23060 2011-09-05 17:58:30Z 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 23060 2011-09-05 17:58:30Z 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 202 : FITSRasterBand::FITSRasterBand(FITSDataset *poDS, int nBand) {
103 :
104 202 : this->poDS = poDS;
105 202 : this->nBand = nBand;
106 202 : eDataType = poDS->gdalDataType;
107 202 : nBlockXSize = poDS->nRasterXSize;;
108 202 : nBlockYSize = 1;
109 202 : }
110 :
111 : /************************************************************************/
112 : /* ~FITSRasterBand() */
113 : /************************************************************************/
114 :
115 202 : FITSRasterBand::~FITSRasterBand() {
116 202 : FlushCache();
117 202 : }
118 :
119 : /************************************************************************/
120 : /* IReadBlock() */
121 : /************************************************************************/
122 :
123 200 : CPLErr FITSRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
124 : void* pImage ) {
125 :
126 : // A FITS block is one row (we assume BSQ formatted data)
127 200 : FITSDataset* dataset = (FITSDataset*) poDS;
128 200 : fitsfile* hFITS = dataset->hFITS;
129 200 : 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 200 : CPLAssert(nBlockXOff == 0);
135 200 : 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 200 : nBlockYOff * nRasterXSize + 1;
141 200 : 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 200 : 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 200 : 0, pImage, 0, &status);
154 200 : 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 200 : return CE_None;
161 : }
162 :
163 : /************************************************************************/
164 : /* IWriteBlock() */
165 : /* */
166 : /************************************************************************/
167 :
168 620 : CPLErr FITSRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff,
169 : void* pImage) {
170 :
171 620 : FITSDataset* dataset = (FITSDataset*) poDS;
172 620 : fitsfile* hFITS = dataset->hFITS;
173 620 : 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 620 : nBlockYOff * nRasterXSize + 1;
183 620 : long nElements = nRasterXSize;
184 : fits_write_img(hFITS, dataset->fitsDataType, offset, nElements,
185 620 : 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 620 : if (status == NUM_OVERFLOW)
192 0 : status = 0;
193 :
194 : // Check for other errors
195 620 : 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 620 : if (offset > dataset->highestOffsetWritten)
203 620 : dataset->highestOffsetWritten = offset;
204 :
205 620 : 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 1252 : static bool isIgnorableFITSHeader(const char* name) {
225 10200 : for (int i = 0; i < ignorableHeaderCount; ++i) {
226 10184 : if (strcmp(name, ignorableFITSHeaders[i]) == 0)
227 1236 : return true;
228 : }
229 16 : return false;
230 : }
231 :
232 :
233 : /************************************************************************/
234 : /* FITSDataset() */
235 : /************************************************************************/
236 :
237 114 : FITSDataset::FITSDataset() {
238 114 : hFITS = 0;
239 114 : }
240 :
241 : /************************************************************************/
242 : /* ~FITSDataset() */
243 : /************************************************************************/
244 :
245 114 : FITSDataset::~FITSDataset() {
246 :
247 : int status;
248 114 : if (hFITS) {
249 114 : 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 60 : status = 0;
252 60 : fits_movabs_hdu(hFITS, 1, 0, &status);
253 60 : fits_write_key_longwarn(hFITS, &status);
254 60 : 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 60 : char** metaData = GetMetadata();
260 60 : int count = CSLCount(metaData);
261 92 : for (int i = 0; i < count; ++i) {
262 32 : const char* field = CSLGetField(metaData, i);
263 32 : if (strlen(field) == 0)
264 0 : continue;
265 : else {
266 : char* key;
267 32 : const char* value = CPLParseNameValue(field, &key);
268 : // FITS keys must be less than 8 chars
269 32 : if (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 4 : char* valueCpy = strdup(value);
286 4 : fits_update_key_longstr(hFITS, key, valueCpy, 0, &status);
287 4 : free(valueCpy);
288 :
289 : // Check for errors
290 4 : 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 32 : CPLFree(key);
299 : }
300 : }
301 :
302 : // Make sure we flush the raster cache before we close the file!
303 60 : FlushCache();
304 : }
305 :
306 : // Close the FITS handle - ignore the error status
307 114 : status = 0;
308 114 : fits_close_file(hFITS, &status);
309 :
310 : }
311 0 : }
312 :
313 :
314 : /************************************************************************/
315 : /* Init() */
316 : /************************************************************************/
317 :
318 114 : CPLErr FITSDataset::Init(fitsfile* hFITS_, bool isExistingFile_) {
319 :
320 114 : hFITS = hFITS_;
321 114 : isExistingFile = isExistingFile_;
322 114 : highestOffsetWritten = 0;
323 114 : int status = 0;
324 :
325 : // Move to the primary HDU
326 114 : fits_movabs_hdu(hFITS, 1, 0, &status);
327 114 : 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 114 : 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 114 : const int maxdim = 3;
345 : long naxes[maxdim];
346 114 : fits_get_img_param(hFITS, maxdim, &bitpix, &naxis, naxes, &status);
347 114 : 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 114 : if (bitpix == BYTE_IMG) {
356 50 : gdalDataType = GDT_Byte;
357 50 : fitsDataType = TBYTE;
358 : }
359 64 : else if (bitpix == SHORT_IMG) {
360 16 : gdalDataType = GDT_Int16;
361 16 : fitsDataType = TSHORT;
362 : }
363 48 : else if (bitpix == LONG_IMG) {
364 16 : gdalDataType = GDT_Int32;
365 16 : fitsDataType = TINT;
366 : }
367 32 : else if (bitpix == FLOAT_IMG) {
368 16 : gdalDataType = GDT_Float32;
369 16 : fitsDataType = TFLOAT;
370 : }
371 16 : else if (bitpix == DOUBLE_IMG) {
372 16 : gdalDataType = GDT_Float64;
373 16 : 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 114 : if (naxis == 2) {
384 70 : nRasterXSize = naxes[0];
385 70 : nRasterYSize = naxes[1];
386 70 : nBands = 1;
387 : }
388 44 : else if (naxis == 3) {
389 44 : nRasterXSize = naxes[0];
390 44 : nRasterYSize = naxes[1];
391 44 : 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 632 : for (int i = 0; i < nBands; ++i)
402 202 : 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 114 : int nKeys = 0, nMoreKeys = 0;
413 114 : fits_get_hdrspace(hFITS, &nKeys, &nMoreKeys, &status);
414 1362 : for(keyNum = 1; keyNum <= nKeys; keyNum++)
415 : {
416 1248 : fits_read_keyn(hFITS, keyNum, key, value, 0, &status);
417 1248 : 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 1248 : 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 1248 : 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 12 : char* newValue = value;
434 12 : if (value[0] == '\'' && value[strlen(value) - 1] == '\'') {
435 12 : newValue = value + 1;
436 12 : value[strlen(value) - 1] = '\0';
437 : }
438 : // Check for long string
439 12 : 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 12 : SetMetadataItem(key, newValue);
455 : }
456 : }
457 : }
458 :
459 114 : return CE_None;
460 : }
461 :
462 :
463 :
464 : /************************************************************************/
465 : /* Open() */
466 : /************************************************************************/
467 :
468 25848 : GDALDataset* FITSDataset::Open(GDALOpenInfo* poOpenInfo) {
469 :
470 25848 : const char* fitsID = "SIMPLE = T"; // Spaces important!
471 25848 : size_t fitsIDLen = strlen(fitsID); // Should be 30 chars long
472 :
473 25848 : if ((size_t)poOpenInfo->nHeaderBytes < fitsIDLen)
474 22396 : return NULL;
475 3452 : if (memcmp(poOpenInfo->pabyHeader, fitsID, fitsIDLen))
476 3396 : return NULL;
477 :
478 : // Get access mode and attempt to open the file
479 56 : int status = 0;
480 56 : fitsfile* hFITS = 0;
481 56 : if (poOpenInfo->eAccess == GA_ReadOnly)
482 54 : fits_open_file(&hFITS, poOpenInfo->pszFilename, READONLY, &status);
483 : else
484 2 : fits_open_file(&hFITS, poOpenInfo->pszFilename, READWRITE, &status);
485 56 : 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 56 : FITSDataset* dataset = new FITSDataset();
495 56 : dataset->eAccess = poOpenInfo->eAccess;
496 :
497 : // Set up the description and initialize the dataset
498 56 : dataset->SetDescription(poOpenInfo->pszFilename);
499 56 : 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 56 : dataset->SetDescription( poOpenInfo->pszFilename );
509 56 : dataset->TryLoadXML();
510 :
511 : /* -------------------------------------------------------------------- */
512 : /* Check for external overviews. */
513 : /* -------------------------------------------------------------------- */
514 56 : dataset->oOvManager.Initialize( dataset, poOpenInfo->pszFilename, poOpenInfo->papszSiblingFiles );
515 :
516 56 : return dataset;
517 : }
518 : }
519 :
520 :
521 : /************************************************************************/
522 : /* Create() */
523 : /* */
524 : /* Create a new FITS file. */
525 : /************************************************************************/
526 :
527 98 : 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 98 : 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 98 : char* extFilename = new char[strlen(pszFilename) + 10]; // 10 for margin!
544 98 : sprintf(extFilename, "!%s", pszFilename);
545 98 : fits_create_file(&hFITS, extFilename, &status);
546 98 : delete[] extFilename;
547 98 : if (status) {
548 : CPLError(CE_Failure, CPLE_AppDefined,
549 4 : "Couldn't create FITS file %s (%d).\n", pszFilename, status);
550 4 : return NULL;
551 : }
552 :
553 : // Determine FITS type of image
554 : int bitpix;
555 94 : if (eType == GDT_Byte)
556 26 : bitpix = BYTE_IMG;
557 68 : else if (eType == GDT_Int16)
558 8 : bitpix = SHORT_IMG;
559 60 : else if (eType == GDT_Int32)
560 8 : bitpix = LONG_IMG;
561 52 : else if (eType == GDT_Float32)
562 8 : bitpix = FLOAT_IMG;
563 44 : else if (eType == GDT_Float64)
564 8 : bitpix = DOUBLE_IMG;
565 : else {
566 : CPLError(CE_Failure, CPLE_AppDefined,
567 36 : "GDALDataType (%d) unsupported for FITS", eType);
568 36 : fits_close_file(hFITS, &status);
569 36 : return NULL;
570 : }
571 :
572 : // Now create an image of appropriate size and type
573 58 : long naxes[3] = {nXSize, nYSize, nBands};
574 58 : int naxis = (nBands == 1) ? 2 : 3;
575 58 : fits_create_img(hFITS, bitpix, naxis, naxes, &status);
576 :
577 : // Check the status
578 58 : 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 58 : dataset = new FITSDataset();
587 58 : dataset->nRasterXSize = nXSize;
588 58 : dataset->nRasterYSize = nYSize;
589 58 : dataset->eAccess = GA_Update;
590 58 : dataset->SetDescription(pszFilename);
591 :
592 : // Init recalculates a lot of stuff we already know, but...
593 58 : if (dataset->Init(hFITS, false) != CE_None) {
594 0 : delete dataset;
595 0 : return NULL;
596 : }
597 : else {
598 58 : return dataset;
599 : }
600 : }
601 :
602 :
603 : /************************************************************************/
604 : /* GDALRegister_FITS() */
605 : /************************************************************************/
606 :
607 1135 : void GDALRegister_FITS() {
608 :
609 : GDALDriver* poDriver;
610 :
611 1135 : if( GDALGetDriverByName( "FITS" ) == NULL) {
612 1093 : poDriver = new GDALDriver();
613 :
614 1093 : poDriver->SetDescription( "FITS" );
615 : poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
616 1093 : "Flexible Image Transport System" );
617 : poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
618 1093 : "frmt_various.html#FITS" );
619 : poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
620 1093 : "Byte Int16 Int32 Float32 Float64" );
621 :
622 1093 : poDriver->pfnOpen = FITSDataset::Open;
623 1093 : poDriver->pfnCreate = FITSDataset::Create;
624 1093 : poDriver->pfnCreateCopy = NULL;
625 :
626 1093 : GetGDALDriverManager()->RegisterDriver(poDriver);
627 : }
628 1135 : }
|