Ticket #2191: gsagdataset.cpp

File gsagdataset.cpp, 49.9 kB (added by ReedC, 6 months ago)

Fix for this bug and #1616

Line 
1 /******************************************************************************
2  * $Id: gsagdataset.cpp 13500 2008-01-08 22:17:42Z rouault $
3  *
4  * Project:  GDAL
5  * Purpose:  Implements the Golden Software ASCII Grid Format.
6  * Author:   Kevin Locke, kwl7@cornell.edu
7  *           (Based largely on aaigriddataset.cpp by Frank Warmerdam)
8  *
9  ******************************************************************************
10  * Copyright (c) 2006, Kevin Locke <kwl7@cornell.edu>
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included
20  * in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  ****************************************************************************/
30
31 #include "cpl_conv.h"
32
33 #include <sstream>
34 #include <float.h>
35 #include <limits.h>
36 #include <assert.h>
37
38 #include "gdal_pam.h"
39
40 #ifndef DBL_MAX
41 # ifdef __DBL_MAX__
42 define DBL_MAX __DBL_MAX__
43 # else
44 define DBL_MAX 1.7976931348623157E+308
45 # endif /* __DBL_MAX__ */
46 #endif /* DBL_MAX */
47
48 #ifndef INT_MAX
49 # define INT_MAX 2147483647
50 #endif /* INT_MAX */
51
52 CPL_CVSID("$Id: gsagdataset.cpp 13500 2008-01-08 22:17:42Z rouault $");
53
54 CPL_C_START
55 void    GDALRegister_GSAG(void);
56 CPL_C_END
57
58 /************************************************************************/
59 /* ==================================================================== */
60 /*                              GSAGDataset                             */
61 /* ==================================================================== */
62 /************************************************************************/
63
64 class GSAGRasterBand;
65
66 class GSAGDataset : public GDALPamDataset
67 {
68     friend class GSAGRasterBand;
69
70     static const double dfNODATA_VALUE;
71     static const int nFIELD_PRECISION;
72     static const size_t nMAX_HEADER_SIZE;
73
74     static CPLErr ShiftFileContents( FILE *, vsi_l_offset, int, const char * );
75
76     FILE        *fp;
77     size_t       nMinMaxZOffset;
78     char         szEOL[3];
79
80     CPLErr UpdateHeader();
81
82   public:
83                 GSAGDataset( const char *pszEOL = "\x0D\x0A" );
84                 ~GSAGDataset();
85
86     static GDALDataset *Open( GDALOpenInfo * );
87     static GDALDataset *CreateCopy( const char *pszFilename,
88                                     GDALDataset *poSrcDS,
89                                     int bStrict, char **papszOptions,
90                                     GDALProgressFunc pfnProgress,
91                                     void *pProgressData );
92     static CPLErr Delete( const char *pszFilename );
93
94     CPLErr GetGeoTransform( double *padfGeoTransform );
95     CPLErr SetGeoTransform( double *padfGeoTransform );
96 };
97
98 /* NOTE:  This is not mentioned in the spec, but Surfer 8 uses this value */
99 const double GSAGDataset::dfNODATA_VALUE = 1.70141E+38;
100
101 const int GSAGDataset::nFIELD_PRECISION = 14;
102 const size_t GSAGDataset::nMAX_HEADER_SIZE = 200;
103
104 /************************************************************************/
105 /* ==================================================================== */
106 /*                            GSAGRasterBand                            */
107 /* ==================================================================== */
108 /************************************************************************/
109
110 class GSAGRasterBand : public GDALPamRasterBand
111 {
112     friend class GSAGDataset;
113
114     double dfMinX;
115     double dfMaxX;
116     double dfMinY;
117     double dfMaxY;
118     double dfMinZ;
119     double dfMaxZ;
120
121     vsi_l_offset *panLineOffset;
122         int nLastReadLine;
123
124     double *padfRowMinZ;
125     double *padfRowMaxZ;
126     int nMinZRow;
127     int nMaxZRow;
128
129     CPLErr ScanForMinMaxZ();
130
131   public:
132
133                 GSAGRasterBand( GSAGDataset *, int, vsi_l_offset );
134                 ~GSAGRasterBand();
135    
136     CPLErr IReadBlock( int, int, void * );
137     CPLErr IWriteBlock( int, int, void * );
138
139     double GetNoDataValue( int *pbSuccess = NULL );
140     double GetMinimum( int *pbSuccess = NULL );
141     double GetMaximum( int *pbSuccess = NULL );
142 };
143
144 /************************************************************************/
145 /*                            AlmostEqual()                             */
146 /* This function is needed because in release mode "1.70141E+38" is not */
147 /* parsed as 1.70141E+38 in the last bit of the mantissa.               */
148 /* See http://gcc.gnu.org/ml/gcc/2003-08/msg01195.html for some         */
149 /* explanation.                                                         */
150 /************************************************************************/
151
152 bool AlmostEqual( double dfVal1, double dfVal2 )
153
154 {
155     const double dfTOLERANCE = 0.0000000001;
156     if( dfVal1 == 0.0 || dfVal2 == 0.0 )
157         return fabs(dfVal1 - dfVal2) < dfTOLERANCE;
158     return fabs((dfVal1 - dfVal2)/dfVal1) < dfTOLERANCE;
159 }
160
161 /************************************************************************/
162 /*                           GSAGRasterBand()                           */
163 /************************************************************************/
164
165 GSAGRasterBand::GSAGRasterBand( GSAGDataset *poDS, int nBand,
166                                 vsi_l_offset nDataStart ) :
167     padfRowMinZ(NULL),
168     padfRowMaxZ(NULL),
169     nMinZRow(-1),
170     nMaxZRow(-1)
171
172 {
173     this->poDS = poDS;
174     nBand = nBand;
175    
176     eDataType = GDT_Float64;
177
178     nBlockXSize = poDS->GetRasterXSize();
179     nBlockYSize = 1;
180
181     panLineOffset =
182         (vsi_l_offset *)CPLCalloc( poDS->nRasterYSize+1, sizeof(vsi_l_offset) );
183     if( panLineOffset == NULL )
184         return;
185
186         panLineOffset[poDS->nRasterYSize-1] = nDataStart;
187         nLastReadLine = poDS->nRasterYSize;
188 }
189
190 /************************************************************************/
191 /*                          ~GSAGRasterBand()                           */
192 /************************************************************************/
193
194 GSAGRasterBand::~GSAGRasterBand()
195 {
196     CPLFree( panLineOffset );
197     if( padfRowMinZ != NULL )
198         CPLFree( padfRowMinZ );
199     if( padfRowMaxZ != NULL )
200         CPLFree( padfRowMaxZ );
201 }
202
203 /************************************************************************/
204 /*                           ScanForMinMaxZ()                           */
205 /************************************************************************/
206
207 CPLErr GSAGRasterBand::ScanForMinMaxZ()
208
209 {
210     double *padfRowValues = (double *)VSIMalloc2( nBlockXSize, sizeof(double) );
211     if( padfRowValues == NULL )
212     {
213         CPLError( CE_Failure, CPLE_OutOfMemory,
214                   "Unable to allocate memory for grid row values.\n" );
215         return CE_Failure;
216     }
217
218     double dfNewMinZ = DBL_MAX;
219     double dfNewMaxZ = -DBL_MAX;
220     int nNewMinZRow = 0;
221     int nNewMaxZRow = 0;
222
223     /* Since we have to scan, lets calc. statistics too */
224     double dfSum = 0.0;
225     double dfSum2 = 0.0;
226     unsigned long nValuesRead = 0;
227     for( int iRow=0; iRow<nRasterYSize; iRow++ )
228     {
229         CPLErr eErr = IReadBlock( 0, iRow, padfRowValues );
230         if( eErr != CE_None )
231         {
232             VSIFree( padfRowValues );
233             return eErr;
234         }
235
236         padfRowMinZ[iRow] = DBL_MAX;
237         padfRowMaxZ[iRow] = -DBL_MAX;
238         for( int iCell=0; iCell<nRasterXSize; iCell++ )
239         {
240             if( AlmostEqual(padfRowValues[iCell], GSAGDataset::dfNODATA_VALUE) )
241                 continue;
242
243             if( padfRowValues[iCell] < padfRowMinZ[iRow] )
244                 padfRowMinZ[iRow] = padfRowValues[iCell];
245
246             if( padfRowValues[iCell] > padfRowMaxZ[iRow] )
247                 padfRowMaxZ[iRow] = padfRowValues[iCell];
248
249             dfSum += padfRowValues[iCell];
250             dfSum2 += padfRowValues[iCell] * padfRowValues[iCell];
251             nValuesRead++;
252         }
253
254         if( padfRowMinZ[iRow] < dfNewMinZ )
255         {
256             dfNewMinZ = padfRowMinZ[iRow];
257             nNewMinZRow = iRow;
258         }
259
260         if( padfRowMaxZ[iRow] > dfNewMaxZ )
261         {
262             dfNewMaxZ = padfRowMaxZ[iRow];
263             nNewMaxZRow = iRow;
264         }
265     }
266
267     VSIFree( padfRowValues );
268
269     if( nValuesRead == 0 )
270     {
271         dfMinZ = 0.0;
272         dfMaxZ = 0.0;
273         nMinZRow = 0;
274         nMaxZRow = 0;
275         return CE_None;
276     }
277
278     dfMinZ = dfNewMinZ;
279     dfMaxZ = dfNewMaxZ;
280     nMinZRow = nNewMinZRow;
281     nMaxZRow = nNewMaxZRow;
282
283     double dfMean = dfSum / nValuesRead;
284     double dfStdDev = sqrt((dfSum2 / nValuesRead) - (dfMean * dfMean));
285     SetStatistics( dfMinZ, dfMaxZ, dfMean, dfStdDev );
286
287     return CE_None;
288 }
289
290 /************************************************************************/
291 /*                             IReadBlock()                             */
292 /************************************************************************/
293
294 CPLErr GSAGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
295                                    void * pImage )
296 {
297     static size_t nMaxLineSize = 128;
298     double *pdfImage = (double *)pImage;
299     GSAGDataset *poGDS = (GSAGDataset *)poDS;
300
301     assert( poGDS != NULL );
302
303     if( nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0 )
304         return CE_Failure;
305
306     if( panLineOffset[nBlockYOff] == 0 )
307     {
308                 // Discover the last read block
309                 for ( int iFoundLine = nLastReadLine - 1; iFoundLine > nBlockYOff; iFoundLine--)
310                 {
311                         IReadBlock( nBlockXOff, iFoundLine, NULL);
312                 }
313     }
314
315     if( panLineOffset[nBlockYOff] == 0 )
316         return CE_Failure;
317     if( VSIFSeekL( poGDS->fp, panLineOffset[nBlockYOff], SEEK_SET ) != 0 )
318     {
319         CPLError( CE_Failure, CPLE_FileIO,
320               "Can't seek to offset %ld to read grid row %d.",
321               panLineOffset[nBlockYOff], nBlockYOff );
322         return CE_Failure;
323     }
324
325     size_t nLineBufSize;
326     char *szLineBuf = NULL;
327     size_t nCharsRead;
328     size_t nCharsExamined = 0;
329     /* If we know the offsets, we can just read line directly */
330     if( (nBlockYOff > 0) && ( panLineOffset[nBlockYOff-1] != 0 ) )
331     {
332         assert(panLineOffset[nBlockYOff-1] > panLineOffset[nBlockYOff]);
333         nLineBufSize = panLineOffset[nBlockYOff-1]
334                               - panLineOffset[nBlockYOff] + 1;
335     }
336     else
337     {
338         nLineBufSize = nMaxLineSize;
339     }
340
341     szLineBuf = (char *)VSIMalloc( nLineBufSize );
342     if( szLineBuf == NULL )
343     {
344         CPLError( CE_Failure, CPLE_OutOfMemory,
345                   "Unable to read block, unable to allocate line buffer.\n" );
346         return CE_Failure;
347     }
348
349     nCharsRead = VSIFReadL( szLineBuf, 1, nLineBufSize - 1, poGDS->fp );
350     if( nCharsRead == 0 )
351     {
352         VSIFree( szLineBuf );
353         CPLError( CE_Failure, CPLE_FileIO,
354                   "Can't read grid row %d at offset %ld.\n",
355                   nBlockYOff, panLineOffset[nBlockYOff] );
356         return CE_Failure;
357     }
358     szLineBuf[nCharsRead] = '\0';
359
360     char *szStart = szLineBuf;
361     char *szEnd = szStart;
362     for( int iCell=0; iCell<nBlockXSize; szStart = szEnd )
363     {
364         double dfValue = CPLStrtod( szStart, &szEnd );
365         if( szStart == szEnd )
366         {
367             /* No number found */
368
369             /* Check if this was an expected failure */
370             while( isspace( *szStart ) )
371                 szStart++;
372
373             /* Found sign at end of input, seek back to re-read it */
374             if ( (*szStart == '-' || *szStart == '+') && *(szStart+1) == '\0' )
375             {
376                 if( VSIFSeekL( poGDS->fp,
377                                VSIFTellL( poGDS->fp)-1,
378                                SEEK_SET ) != 0 )
379                 {
380                     VSIFree( szLineBuf );
381                     CPLError( CE_Failure, CPLE_FileIO,
382                               "Unable to seek in grid row %d "
383                               "(offset %ld, seek %d).\n",
384                               nBlockYOff, VSIFTellL(poGDS->fp),
385                               -1 );
386
387                     return CE_Failure;
388                 }
389             }
390             else if( *szStart != '\0' )
391             {
392                 szEnd = szStart;
393                 while( !isspace( *szEnd ) && *szEnd != '\0' )
394                     szEnd++;
395                 char cOldEnd = *szEnd;
396                 *szEnd = '\0';
397
398                 CPLError( CE_Warning, CPLE_FileIO,
399                           "Unexpected value in grid row %d (expected floating "
400                           "point value, found \"%s\").\n",
401                           nBlockYOff, szStart );
402
403                 *szEnd = cOldEnd;
404
405                 szEnd = szStart;
406                 while( !isdigit( *szEnd ) && *szEnd != '.' && *szEnd != '\0' )
407                     szEnd++;
408
409                 continue;
410             }
411             else if( static_cast<size_t>(szStart - szLineBuf) != nCharsRead )
412             {
413                 CPLError( CE_Warning, CPLE_FileIO,
414                           "Unexpected ASCII null-character in grid row %d at "
415                           "offset %ld.\n", nBlockYOff, szStart - szLineBuf );
416
417                 while( *szStart == '\0' &&
418                         static_cast<size_t>(szStart - szLineBuf) < nCharsRead )
419                     szStart++;
420
421                 szEnd = szStart;
422                 continue;
423             }
424
425             nCharsExamined += szStart - szLineBuf;
426             nCharsRead = VSIFReadL( szLineBuf, 1, nLineBufSize - 1, poGDS->fp );
427             if( nCharsRead == 0 )
428             {
429                 VSIFree( szLineBuf );
430                 CPLError( CE_Failure, CPLE_FileIO,
431                           "Can't read portion of grid row %d at offset %ld.",
432                           nBlockYOff, panLineOffset[nBlockYOff] );
433                 return CE_Failure;
434             }
435             szLineBuf[nCharsRead] = '\0';
436             szStart = szEnd = szLineBuf;
437             continue;
438         }
439         else if( *szEnd == '\0'
440                 || (*szEnd == '.' && *(szEnd+1) == '\0')
441                 || (*szEnd == '-' && *(szEnd+1) == '\0')
442                 || (*szEnd == '+' && *(szEnd+1) == '\0')
443                 || (*szEnd == 'E' && *(szEnd+1) == '\0')
444                 || (*szEnd == 'E' && *(szEnd+1) == '-' && *(szEnd+2) == '\0')
445                 || (*szEnd == 'E' && *(szEnd+1) == '+' && *(szEnd+2) == '\0')
446                 || (*szEnd == 'e' && *(szEnd+1) == '\0')
447                 || (*szEnd == 'e' && *(szEnd+1) == '-' && *(szEnd+2) == '\0')
448                 || (*szEnd == 'e' && *(szEnd+1) == '+' && *(szEnd+2) == '\0'))
449         {
450             /* Number was interrupted by a nul character */
451             while( *szEnd != '\0' )
452                 szEnd++;
453
454             if( static_cast<size_t>(szEnd - szLineBuf) != nCharsRead )
455             {
456                 CPLError( CE_Warning, CPLE_FileIO,
457                           "Unexpected ASCII null-character in grid row %d at "
458                           "offset %ld.\n", nBlockYOff, szStart - szLineBuf );
459
460                 while( *szEnd == '\0' &&
461                        static_cast<size_t>(szStart - szLineBuf) < nCharsRead )
462                     szEnd++;
463
464                 continue;
465             }
466
467             /* End of buffer, could be interrupting a number */
468             if( VSIFSeekL( poGDS->fp, szStart - szEnd, SEEK_CUR ) != 0 )
469             {
470                 VSIFree( szLineBuf );
471                 CPLError( CE_Failure, CPLE_FileIO,
472                           "Unable to seek in grid row %d (offset %ld, seek %d)"
473                           ".\n", nBlockYOff, VSIFTellL(poGDS->fp),
474                           szStart - szEnd );
475
476                 return CE_Failure;
477             }
478             nCharsExamined += szStart - szLineBuf;
479             nCharsRead = VSIFReadL( szLineBuf, 1, nLineBufSize - 1, poGDS->fp );
480             szLineBuf[nCharsRead] = '\0';
481
482             if( nCharsRead == 0 )
483             {
484                 VSIFree( szLineBuf );
485                 CPLError( CE_Failure, CPLE_FileIO,
486                           "Can't read portion of grid row %d at offset %ld.",
487                           nBlockYOff, panLineOffset[nBlockYOff] );
488                 return CE_Failure;
489             }
490             else if( nCharsRead > static_cast<size_t>(szEnd - szStart) )
491             {
492                 /* Read new data, this was not really the end */
493                 szEnd = szStart = szLineBuf;
494                 continue;
495             }
496
497             /* This is really the last value and has no tailing newline */
498             szStart = szLineBuf;
499             szEnd = szLineBuf + nCharsRead;
500         }
501
502         if( pdfImage != NULL )
503         {
504             *(pdfImage+iCell) = dfValue;
505         }
506
507         iCell++;
508     }
509
510     while( *szEnd == ' ' )
511         szEnd++;
512
513     if( *szEnd != '\0' && *szEnd != poGDS->szEOL[0] )
514         CPLDebug( "GSAG", "Grid row %d does not end with a newline.  "
515                          "Possible skew.\n", nBlockYOff );
516
517     while( isspace( *szEnd ) )
518         szEnd++;
519
520     nCharsExamined += szEnd - szLineBuf;
521
522     if( nCharsExamined >= nMaxLineSize )
523         nMaxLineSize = nCharsExamined + 1;
524
525     panLineOffset[nBlockYOff - 1] = panLineOffset[nBlockYOff] + nCharsExamined;
526         nLastReadLine = nBlockYOff;
527
528     VSIFree( szLineBuf );
529
530     return CE_None;
531 }
532
533 /************************************************************************/
534 /*                            IWriteBlock()                             */
535 /************************************************************************/
536
537 CPLErr GSAGRasterBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
538                                     void * pImage )
539
540 {
541     if( eAccess == GA_ReadOnly )
542     {
543         CPLError( CE_Failure, CPLE_NoWriteAccess,
544                   "Unable to write block, dataset opened read only.\n" );
545         return CE_Failure;
546     }
547
548     if( nBlockYOff < 0 || nBlockYOff > nRasterYSize - 1 || nBlockXOff != 0 )
549         return CE_Failure;
550
551     GSAGDataset *poGDS = (GSAGDataset *)poDS;
552     assert( poGDS != NULL );
553
554     if( padfRowMinZ == NULL || padfRowMaxZ == NULL
555         || nMinZRow < 0 || nMaxZRow < 0 )
556     {
557         padfRowMinZ = (double *)VSIMalloc2( nRasterYSize,sizeof(double) );
558         if( padfRowMinZ == NULL )
559         {
560             CPLError( CE_Failure, CPLE_OutOfMemory,
561                       "Unable to allocate space for row minimums array.\n" );
562             return CE_Failure;
563         }
564
565         padfRowMaxZ = (double *)VSIMalloc2( nRasterYSize,sizeof(double) );
566         if( padfRowMaxZ == NULL )
567         {
568             VSIFree( padfRowMinZ );
569             padfRowMinZ = NULL;
570             CPLError( CE_Failure, CPLE_OutOfMemory,
571                       "Unable to allocate space for row maximums array.\n" );
572             return CE_Failure;
573         }
574
575         CPLErr eErr = ScanForMinMaxZ();
576         if( eErr != CE_None )
577             return eErr;
578     }
579
580     if( panLineOffset[nBlockYOff+1] == 0 )
581         IReadBlock( nBlockXOff, nBlockYOff, NULL );
582
583     if( panLineOffset[nBlockYOff+1] == 0 || panLineOffset[nBlockYOff] == 0 )
584         return CE_Failure;
585
586     std::ostringstream ssOutBuf;
587     ssOutBuf.precision( GSAGDataset::nFIELD_PRECISION );
588     ssOutBuf.setf( std::ios::uppercase );
589
590     double *pdfImage = (double *)pImage;
591     padfRowMinZ[nBlockYOff] = DBL_MAX;
592     padfRowMaxZ[nBlockYOff] = -DBL_MAX;
593     for( int iCell=0; iCell<nBlockXSize; )
594     {
595         for( int iCol=0; iCol<10 && iCell<nBlockXSize; iCol++, iCell++ )
596         {
597             if( AlmostEqual( pdfImage[iCell], GSAGDataset::dfNODATA_VALUE ) )
598             {
599                 if( pdfImage[iCell] < padfRowMinZ[nBlockYOff] )
600                     padfRowMinZ[nBlockYOff] = pdfImage[iCell];
601
602                 if( pdfImage[iCell] > padfRowMaxZ[nBlockYOff] )
603                     padfRowMaxZ[nBlockYOff] = pdfImage[iCell];
604             }
605
606             ssOutBuf << pdfImage[iCell] << " ";
607         }
608         ssOutBuf << poGDS->szEOL;
609     }
610     ssOutBuf << poGDS->szEOL;
611
612     CPLString sOut = ssOutBuf.str();
613     if( sOut.length() != panLineOffset[nBlockYOff+1]-panLineOffset[nBlockYOff] )
614     {
615         int nShiftSize = sOut.length() - (panLineOffset[nBlockYOff+1]
616                                           - panLineOffset[nBlockYOff]);
617         if( nBlockYOff != poGDS->nRasterYSize
618             && GSAGDataset::ShiftFileContents( poGDS->fp,
619                                                panLineOffset[nBlockYOff+1],
620                                                nShiftSize,
621                                                poGDS->szEOL ) != CE_None )
622         {
623             CPLError( CE_Failure, CPLE_FileIO,
624                       "Failure writing block, "
625                       "unable to shift file contents.\n" );
626             return CE_Failure;
627         }
628
629         for( size_t iLine=nBlockYOff+1;
630              iLine < static_cast<unsigned>(poGDS->nRasterYSize+1)
631                 && panLineOffset[iLine] != 0; iLine++ )
632             panLineOffset[iLine] += nShiftSize;
633     }
634
635     if( VSIFSeekL( poGDS->fp, panLineOffset[nBlockYOff], SEEK_SET ) != 0 )
636     {
637         CPLError( CE_Failure, CPLE_FileIO, "Unable to seek to grid line.\n" );
638         return CE_Failure;
639     }
640
641     if( VSIFWriteL( sOut.c_str(), 1, sOut.length(),
642                     poGDS->fp ) != sOut.length() )
643     {
644         CPLError( CE_Failure, CPLE_FileIO, "Unable to write grid block.\n" );
645         return CE_Failure;
646     }
647
648     /* Update header as needed */
649     bool bHeaderNeedsUpdate = false;
650     if( nMinZRow == nBlockYOff && padfRowMinZ[nBlockYOff] > dfMinZ )
651     {
652         double dfNewMinZ = -DBL_MAX;
653         for( int iRow=0; iRow<nRasterYSize; iRow++ )
654         {
655             if( padfRowMinZ[iRow] < dfNewMinZ )
656             {
657                 dfNewMinZ = padfRowMinZ[iRow];
658                 nMinZRow = iRow;
659             }
660         }
661
662         if( dfNewMinZ != dfMinZ )
663         {
664             dfMinZ = dfNewMinZ;
665             bHeaderNeedsUpdate = true;
666         }
667     }
668
669     if( nMaxZRow == nBlockYOff && padfRowMaxZ[nBlockYOff] < dfMaxZ )
670     {
671         double dfNewMaxZ = -DBL_MAX;
672         for( int iRow=0; iRow<nRasterYSize; iRow++ )
673         {
674             if( padfRowMaxZ[iRow] > dfNewMaxZ )
675             {
676                 dfNewMaxZ = padfRowMaxZ[iRow];
677                 nMaxZRow = iRow;
678             }
679         }
680
681         if( dfNewMaxZ != dfMaxZ )
682         {
683             dfMaxZ = dfNewMaxZ;
684             bHeaderNeedsUpdate = true;
685         }
686     }
687
688     if( padfRowMinZ[nBlockYOff] < dfMinZ || padfRowMaxZ[nBlockYOff] > dfMaxZ )
689     {
690         if( padfRowMinZ[nBlockYOff] < dfMinZ )
691         {
692             dfMinZ = padfRowMinZ[nBlockYOff];
693             nMinZRow = nBlockYOff;
694         }
695
696         if( padfRowMaxZ[nBlockYOff] > dfMaxZ )
697         {
698             dfMaxZ = padfRowMaxZ[nBlockYOff];
699             nMaxZRow = nBlockYOff;
700         }
701
702         bHeaderNeedsUpdate = true;
703     }
704
705     if( bHeaderNeedsUpdate && dfMaxZ > dfMinZ )
706     {
707         CPLErr eErr = poGDS->UpdateHeader();
708         return eErr;
709     }
710
711     return CE_None;
712 }
713
714 /************************************************************************/
715 /*                           GetNoDataValue()                           */
716 /************************************************************************/
717
718 double GSAGRasterBand::GetNoDataValue( int * pbSuccess )
719 {
720     if( pbSuccess )
721         *pbSuccess = TRUE;
722
723     return GSAGDataset::dfNODATA_VALUE;
724 }
725
726 /************************************************************************/
727 /*                             GetMinimum()                             */
728 /************************************************************************/
729
730 double GSAGRasterBand::GetMinimum( int *pbSuccess )
731 {
732     if( pbSuccess )
733         *pbSuccess = TRUE;
734
735     return dfMinZ;
736 }
737
738 /************************************************************************/
739 /*                             GetMaximum()                             */
740 /************************************************************************/
741
742 double GSAGRasterBand::GetMaximum( int *pbSuccess )
743 {
744     if( pbSuccess )
745         *pbSuccess = TRUE;
746
747     return dfMaxZ;
748 }
749
750 /************************************************************************/
751 /* ==================================================================== */
752 /*                              GSAGDataset                             */
753 /* ==================================================================== */
754 /************************************************************************/
755
756 /************************************************************************/
757 /*                             GSAGDataset()                            */
758 /************************************************************************/
759
760 GSAGDataset::GSAGDataset( const char *pszEOL )
761
762 {
763     if( pszEOL == NULL || EQUAL(pszEOL, "") )
764     {
765         CPLDebug( "GSAG", "GSAGDataset() created with invalid EOL string.\n" );
766         this->szEOL[0] = '\x0D';
767         this->szEOL[1] = '\x0A';
768         this->szEOL[2] = '\0';
769     }
770     else
771     {
772         strncpy(this->szEOL, pszEOL, sizeof(this->szEOL));
773         this->szEOL[sizeof(this->szEOL) - 1] = '\0';
774     }
775 }
776
777 /************************************************************************/
778 /*                            ~GSAGDataset()                            */
779 /************************************************************************/
780
781 GSAGDataset::~GSAGDataset()
782
783 {
784     FlushCache();
785     if( fp != NULL )
786         VSIFCloseL( fp );
787 }
788
789 /************************************************************************/
790 /*                                Open()                                */
791 /************************************************************************/
792
793 GDALDataset *GSAGDataset::Open( GDALOpenInfo * poOpenInfo )
794
795 {
796     /* Check for signature */
797     if( poOpenInfo->nHeaderBytes < 5
798         || !EQUALN((const char *) poOpenInfo->pabyHeader,"DSAA",4)
799         || ( poOpenInfo->pabyHeader[4] != '\x0D'
800              && poOpenInfo->pabyHeader[4] != '\x0A' ))
801     {
802         return NULL;
803     }
804
805     /* Identify the end of line marker (should be \x0D\x0A, but try some others)
806      * (note that '\x0D' == '\r' and '\x0A' == '\n' on most systems) */
807     char szEOL[3];
808     szEOL[0] = poOpenInfo->pabyHeader[4];
809     szEOL[1] = poOpenInfo->pabyHeader[5];
810     szEOL[2] = '\0';
811     if( szEOL[1] != '\x0D' && szEOL[1] != '\x0A' )
812         szEOL[1] = '\0';
813
814 /* -------------------------------------------------------------------- */
815 /*      Create a corresponding GDALDataset.                             */
816 /* -------------------------------------------------------------------- */
817     GSAGDataset *poDS = new GSAGDataset( szEOL );
818
819 /* -------------------------------------------------------------------- */
820 /*      Open file with large file API.                                  */
821 /* -------------------------------------------------------------------- */
822
823     poDS->eAccess = poOpenInfo->eAccess;
824     if( poOpenInfo->eAccess == GA_ReadOnly )
825         poDS->fp = VSIFOpenL( poOpenInfo->pszFilename, "rb" );
826     else
827         poDS->fp = VSIFOpenL( poOpenInfo->pszFilename, "r+b" );
828
829     if( poDS->fp == NULL )
830     {
831         CPLError( CE_Failure, CPLE_OpenFailed,
832                   "VSIFOpenL(%s) failed unexpectedly.",
833                   poOpenInfo->pszFilename );
834         delete poDS;
835         return NULL;
836     }
837  
838 /* -------------------------------------------------------------------- */
839 /*      Read the header.                                                */
840 /* -------------------------------------------------------------------- */
841     char *pabyHeader;
842     bool bMustFreeHeader = false;
843     if( poOpenInfo->nHeaderBytes >= static_cast<int>(nMAX_HEADER_SIZE) )
844     {
845         pabyHeader = (char *)poOpenInfo->pabyHeader;
846     }
847     else
848     {
849         bMustFreeHeader = true;
850         pabyHeader = (char *)VSIMalloc( nMAX_HEADER_SIZE );
851         if( pabyHeader == NULL )
852         {
853             CPLError( CE_Failure, CPLE_OutOfMemory,
854                       "Unable to open dataset, unable to header buffer.\n" );
855             return NULL;
856         }
857
858         size_t nRead = VSIFReadL( pabyHeader, 1, nMAX_HEADER_SIZE-1, poDS->fp );
859         pabyHeader[nRead+1] = '\0';
860     }
861
862     const char *szErrorMsg;
863     const char *szStart = pabyHeader + 5;
864     char *szEnd;
865     double dfTemp;
866     double dfMinX;
867     double dfMaxX;
868     double dfMinY;
869     double dfMaxY;
870     double dfMinZ;
871     double dfMaxZ;
872
873     /* Parse number of X axis grid rows */
874     long nTemp = strtol( szStart, &szEnd, 10 );
875     if( szStart == szEnd || nTemp < 0l )
876     {
877         szErrorMsg = "Unable to parse the number of X axis grid columns.\n";
878         goto error;
879     }
880     else if( nTemp > INT_MAX )
881     {
882         CPLError( CE_Warning, CPLE_AppDefined,
883                   "Number of X axis grid columns not representable.\n" );
884         poDS->nRasterXSize = INT_MAX;
885     }
886     else
887     {
888         poDS->nRasterXSize = static_cast<int>(nTemp);
889     }
890     szStart = szEnd;
891
892     /* Parse number of Y axis grid rows */
893     nTemp = strtol( szStart, &szEnd, 10 );
894     if( szStart == szEnd || nTemp < 0l )
895     {
896         szErrorMsg = "Unable to parse the number of Y axis grid rows.\n";
897         goto error;
898     }
899     else if( nTemp > INT_MAX )
900     {
901         CPLError( CE_Warning, CPLE_AppDefined,
902                   "Number of Y axis grid rows not representable.\n" );
903         poDS->nRasterYSize = INT_MAX;
904     }
905     else
906     {
907         poDS->nRasterYSize = static_cast<int>(nTemp);
908     }
909     szStart = szEnd;
910
911     /* Parse the minimum X value of the grid */
912     dfTemp = CPLStrtod( szStart, &szEnd );
913     if( szStart == szEnd )
914     {
915         szErrorMsg = "Unable to parse the minimum X value.\n";
916         goto error;
917     }
918     else
919     {
920         dfMinX = dfTemp;
921     }
922     szStart = szEnd;
923
924     /* Parse the maximum X value of the grid */
925     dfTemp = CPLStrtod( szStart, &szEnd );
926     if( szStart == szEnd )
927     {
928         szErrorMsg = "Unable to parse the maximum X value.\n";
929         goto error;
930     }
931     else