1 /******************************************************************************
2  *
3  * Project:  SDTS Translator
4  * Purpose:  Implementation of SDTSRasterReader class.
5  * Author:   Frank Warmerdam, warmerdam@pobox.com
6  *
7  ******************************************************************************
8  * Copyright (c) 1999, Frank Warmerdam
9  * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
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 #include "sdts_al.h"
31 
32 #include <algorithm>
33 
34 CPL_CVSID("$Id: sdtsrasterreader.cpp b1c9c12ad373e40b955162b45d704070d4ebf7b0 2019-06-19 16:50:15 +0200 Even Rouault $")
35 
36 /************************************************************************/
37 /*                          SDTSRasterReader()                          */
38 /************************************************************************/
39 
SDTSRasterReader()40 SDTSRasterReader::SDTSRasterReader() :
41     nXSize(0),
42     nYSize(0),
43     nXBlockSize(0),
44     nYBlockSize(0),
45     nXStart(0),
46     nYStart(0)
47 {
48     strcpy( szINTR, "CE" );
49     memset( szModule, 0, sizeof(szModule) );
50     memset( adfTransform, 0, sizeof(adfTransform) );
51     memset( szFMT, 0, sizeof(szFMT) );
52     memset( szUNITS, 0, sizeof(szUNITS) );
53     memset( szLabel, 0, sizeof(szLabel) );
54 }
55 
56 /************************************************************************/
57 /*                             ~SDTSRasterReader()                     */
58 /************************************************************************/
59 
~SDTSRasterReader()60 SDTSRasterReader::~SDTSRasterReader() {}
61 
62 /************************************************************************/
63 /*                               Close()                                */
64 /************************************************************************/
65 
Close()66 void SDTSRasterReader::Close()
67 
68 {
69     oDDFModule.Close();
70 }
71 
72 /************************************************************************/
73 /*                                Open()                                */
74 /*                                                                      */
75 /*      Open the requested cell file, and collect required              */
76 /*      information.                                                    */
77 /************************************************************************/
78 
Open(SDTS_CATD * poCATD,SDTS_IREF * poIREF,const char * pszModule)79 int SDTSRasterReader::Open( SDTS_CATD * poCATD, SDTS_IREF * poIREF,
80                             const char * pszModule )
81 
82 {
83     snprintf( szModule, sizeof(szModule), "%s", pszModule );
84 
85 /* ==================================================================== */
86 /*      Search the LDEF module for the requested cell module.           */
87 /* ==================================================================== */
88 
89 /* -------------------------------------------------------------------- */
90 /*      Open the LDEF module, and report failure if it is missing.      */
91 /* -------------------------------------------------------------------- */
92     if( poCATD->GetModuleFilePath("LDEF") == nullptr )
93     {
94         CPLError( CE_Failure, CPLE_AppDefined,
95                   "Can't find LDEF entry in CATD module ... "
96                   "can't treat as raster.\n" );
97         return FALSE;
98     }
99 
100     DDFModule oLDEF;
101     if( !oLDEF.Open( poCATD->GetModuleFilePath("LDEF") ) )
102         return FALSE;
103 
104 /* -------------------------------------------------------------------- */
105 /*      Read each record, till we find what we want.                    */
106 /* -------------------------------------------------------------------- */
107     DDFRecord *poRecord = nullptr;
108     while( (poRecord = oLDEF.ReadRecord() ) != nullptr )
109     {
110         const char* pszCandidateModule = poRecord->GetStringSubfield("LDEF",0,"CMNM",0);
111         if( pszCandidateModule == nullptr )
112         {
113             poRecord = nullptr;
114             break;
115         }
116         if( EQUAL(pszCandidateModule, pszModule) )
117             break;
118     }
119 
120     if( poRecord == nullptr )
121     {
122         CPLError( CE_Failure, CPLE_AppDefined,
123                   "Can't find module `%s' in LDEF file.\n",
124                   pszModule );
125         return FALSE;
126     }
127 
128 /* -------------------------------------------------------------------- */
129 /*      Extract raster dimensions, and origin offset (0/1).             */
130 /* -------------------------------------------------------------------- */
131     nXSize = poRecord->GetIntSubfield( "LDEF", 0, "NCOL", 0 );
132     nYSize = poRecord->GetIntSubfield( "LDEF", 0, "NROW", 0 );
133 
134     nXStart = poRecord->GetIntSubfield( "LDEF", 0, "SOCI", 0 );
135     nYStart = poRecord->GetIntSubfield( "LDEF", 0, "SORI", 0 );
136 
137 /* -------------------------------------------------------------------- */
138 /*      Get the point in the pixel that the origin defines.  We only    */
139 /*      support top left and center.                                    */
140 /* -------------------------------------------------------------------- */
141     const char* pszINTR = poRecord->GetStringSubfield(  "LDEF", 0, "INTR", 0 );
142     if( pszINTR == nullptr )
143     {
144         CPLError( CE_Failure, CPLE_AppDefined, "Can't find INTR subfield of LDEF field" );
145         return FALSE;
146     }
147     snprintf( szINTR, sizeof(szINTR), "%s", pszINTR );
148     if( EQUAL(szINTR,"") )
149         snprintf( szINTR, sizeof(szINTR), "%s", "CE" );
150 
151     if( !EQUAL(szINTR,"CE") && !EQUAL(szINTR,"TL") )
152     {
153         CPLError( CE_Warning, CPLE_AppDefined,
154                   "Unsupported INTR value of `%s', assume CE.\n"
155                   "Positions may be off by one pixel.\n",
156                   szINTR );
157         snprintf( szINTR, sizeof(szINTR), "%s", "CE" );
158     }
159 
160 /* -------------------------------------------------------------------- */
161 /*      Record the LDEF record number we used so we can find the        */
162 /*      corresponding RSDF record.                                      */
163 /* -------------------------------------------------------------------- */
164     int nLDEF_RCID = poRecord->GetIntSubfield( "LDEF", 0, "RCID", 0 );
165 
166     oLDEF.Close();
167 
168 /* ==================================================================== */
169 /*      Search the RSDF module for the requested cell module.           */
170 /* ==================================================================== */
171 
172 /* -------------------------------------------------------------------- */
173 /*      Open the RSDF module, and report failure if it is missing.      */
174 /* -------------------------------------------------------------------- */
175     if( poCATD->GetModuleFilePath("RSDF") == nullptr )
176     {
177         CPLError( CE_Failure, CPLE_AppDefined,
178                   "Can't find RSDF entry in CATD module ... "
179                   "can't treat as raster.\n" );
180         return FALSE;
181     }
182 
183     DDFModule oRSDF;
184     if( !oRSDF.Open( poCATD->GetModuleFilePath("RSDF") ) )
185         return FALSE;
186 
187 /* -------------------------------------------------------------------- */
188 /*      Read each record, till we find what we want.                    */
189 /* -------------------------------------------------------------------- */
190     while( (poRecord = oRSDF.ReadRecord() ) != nullptr )
191     {
192         if( poRecord->GetIntSubfield("LYID",0,"RCID",0) == nLDEF_RCID )
193             break;
194     }
195 
196     if( poRecord == nullptr )
197     {
198         CPLError( CE_Failure, CPLE_AppDefined,
199                   "Can't find LDEF:%d record in RSDF file.\n",
200                   nLDEF_RCID );
201         return FALSE;
202     }
203 
204 /* -------------------------------------------------------------------- */
205 /*      Establish the raster pixel/line to georef transformation.       */
206 /* -------------------------------------------------------------------- */
207 
208     if( poRecord->FindField( "SADR" ) == nullptr )
209     {
210         CPLError( CE_Failure, CPLE_AppDefined,
211                   "Can't find SADR field in RSDF record.\n" );
212         return FALSE;
213     }
214 
215     double dfZ;
216     poIREF->GetSADR( poRecord->FindField( "SADR" ), 1,
217                      adfTransform + 0, adfTransform + 3, &dfZ );
218 
219     adfTransform[1] = poIREF->dfXRes;
220     adfTransform[2] = 0.0;
221     adfTransform[4] = 0.0;
222     adfTransform[5] = -1 * poIREF->dfYRes;
223 
224 /* -------------------------------------------------------------------- */
225 /*      If the origin is the center of the pixel, then shift it back    */
226 /*      half a pixel to the top left of the top left.                   */
227 /* -------------------------------------------------------------------- */
228     if( EQUAL(szINTR,"CE") )
229     {
230         adfTransform[0] -= adfTransform[1] * 0.5;
231         adfTransform[3] -= adfTransform[5] * 0.5;
232     }
233 
234 /* -------------------------------------------------------------------- */
235 /*      Verify some other assumptions.                                  */
236 /* -------------------------------------------------------------------- */
237     const char *pszString = poRecord->GetStringSubfield( "RSDF", 0, "OBRP", 0);
238     if( pszString == nullptr )
239         pszString = "";
240     if( !EQUAL(pszString, "G2") )
241     {
242         CPLError( CE_Failure, CPLE_AppDefined,
243                   "OBRP value of `%s' not expected 2D raster code (G2).\n",
244                   pszString );
245         return FALSE;
246     }
247 
248     pszString = poRecord->GetStringSubfield( "RSDF", 0, "SCOR", 0);
249     if( pszString == nullptr )
250         pszString = "";
251     if( !EQUAL(pszString,"TL") )
252     {
253         CPLError( CE_Warning, CPLE_AppDefined,
254                   "SCOR (origin) is `%s' instead of expected top left.\n"
255                   "Georef coordinates will likely be incorrect.\n",
256                   pszString );
257     }
258 
259     oRSDF.Close();
260 
261 /* -------------------------------------------------------------------- */
262 /*      For now we will assume that the block size is one scanline.     */
263 /*      We will blow a gasket later while reading the cell file if      */
264 /*      this isn't the case.                                            */
265 /*                                                                      */
266 /*      This isn't a very flexible raster implementation!               */
267 /* -------------------------------------------------------------------- */
268     nXBlockSize = nXSize;
269     nYBlockSize = 1;
270 
271 /* ==================================================================== */
272 /*      Fetch the data type used for the raster, and the units from     */
273 /*      the data dictionary/schema record (DDSH).                       */
274 /* ==================================================================== */
275 
276 /* -------------------------------------------------------------------- */
277 /*      Open the DDSH module, and report failure if it is missing.      */
278 /* -------------------------------------------------------------------- */
279     if( poCATD->GetModuleFilePath("DDSH") == nullptr )
280     {
281         CPLError( CE_Failure, CPLE_AppDefined,
282                   "Can't find DDSH entry in CATD module ... "
283                   "can't treat as raster.\n" );
284         return FALSE;
285     }
286 
287     DDFModule oDDSH;
288     if( !oDDSH.Open( poCATD->GetModuleFilePath("DDSH") ) )
289         return FALSE;
290 
291 /* -------------------------------------------------------------------- */
292 /*      Read each record, till we find what we want.                    */
293 /* -------------------------------------------------------------------- */
294     while( (poRecord = oDDSH.ReadRecord() ) != nullptr )
295     {
296         const char* pszName = poRecord->GetStringSubfield("DDSH",0,"NAME",0);
297         if( pszName == nullptr )
298         {
299             poRecord = nullptr;
300             break;
301         }
302         if( EQUAL(pszName,pszModule) )
303             break;
304     }
305 
306     if( poRecord == nullptr )
307     {
308         CPLError( CE_Failure, CPLE_AppDefined,
309                   "Can't find DDSH record for %s.\n",
310                   pszModule );
311         return FALSE;
312     }
313 
314 /* -------------------------------------------------------------------- */
315 /*      Get some values we are interested in.                           */
316 /* -------------------------------------------------------------------- */
317     if( poRecord->GetStringSubfield("DDSH",0,"FMT",0) != nullptr )
318         snprintf( szFMT, sizeof(szFMT), "%s", poRecord->GetStringSubfield("DDSH",0,"FMT",0) );
319     else
320         snprintf( szFMT, sizeof(szFMT), "%s", "BI16" );
321     if( !EQUAL(szFMT, "BI16") && !EQUAL(szFMT, "BFP32") )
322     {
323         CPLError( CE_Failure, CPLE_AppDefined,
324                   "Unhandled FMT=%s", szFMT );
325         return FALSE;
326     }
327 
328     if( poRecord->GetStringSubfield("DDSH",0,"UNIT",0) != nullptr )
329         snprintf( szUNITS, sizeof(szUNITS), "%s", poRecord->GetStringSubfield("DDSH",0,"UNIT",0) );
330     else
331         snprintf( szUNITS, sizeof(szUNITS), "%s", "METERS" );
332 
333     if( poRecord->GetStringSubfield("DDSH",0,"ATLB",0) != nullptr )
334         snprintf( szLabel, sizeof(szLabel), "%s", poRecord->GetStringSubfield("DDSH",0,"ATLB",0) );
335     else
336         strcpy( szLabel, "" );
337 
338 /* -------------------------------------------------------------------- */
339 /*      Open the cell file.                                             */
340 /* -------------------------------------------------------------------- */
341     return oDDFModule.Open( poCATD->GetModuleFilePath(pszModule) );
342 }
343 
344 /************************************************************************/
345 /*                              GetBlock()                              */
346 /*                                                                      */
347 /*      Read a requested block of raster data from the file.            */
348 /*                                                                      */
349 /*      Currently we will always use sequential access.  In the         */
350 /*      future we should modify the iso8211 library to support          */
351 /*      seeking, and modify this to seek directly to the right          */
352 /*      record once its location is known.                              */
353 /************************************************************************/
354 
355 /**
356   Read a block of raster data from the file.
357 
358   @param nXOffset X block offset into the file.  Normally zero for scanline
359   organized raster files.
360 
361   @param nYOffset Y block offset into the file.  Normally the scanline offset
362   from top of raster for scanline organized raster files.
363 
364   @param pData pointer to GInt16 (signed short) buffer of data into which to
365   read the raster.
366 
367   @return TRUE on success and FALSE on error.
368 
369   */
370 
GetBlock(CPL_UNUSED int nXOffset,int nYOffset,void * pData)371 int SDTSRasterReader::GetBlock( CPL_UNUSED int nXOffset,
372                                 int nYOffset,
373                                 void * pData )
374 {
375     CPLAssert( nXOffset == 0 );
376 
377 /* -------------------------------------------------------------------- */
378 /*      Analyse the datatype.                                           */
379 /* -------------------------------------------------------------------- */
380     CPLAssert( EQUAL(szFMT,"BI16") || EQUAL(szFMT,"BFP32") );
381 
382     int nBytesPerValue;
383     if( EQUAL(szFMT,"BI16") )
384         nBytesPerValue = 2;
385     else
386         nBytesPerValue = 4;
387 
388     DDFRecord *poRecord = nullptr;
389 
390     for( int iTry = 0; iTry < 2; iTry++ )
391     {
392     /* -------------------------------------------------------------------- */
393     /*      Read through till we find the desired record.                   */
394     /* -------------------------------------------------------------------- */
395         CPLErrorReset();
396         while( (poRecord = oDDFModule.ReadRecord()) != nullptr )
397         {
398             if( poRecord->GetIntSubfield( "CELL", 0, "ROWI", 0 )
399                 == nYOffset + nYStart )
400             {
401                 break;
402             }
403         }
404 
405         if( CPLGetLastErrorType() == CE_Failure )
406             return FALSE;
407 
408     /* -------------------------------------------------------------------- */
409     /*      If we didn't get what we needed just start over.                */
410     /* -------------------------------------------------------------------- */
411         if( poRecord == nullptr )
412         {
413             if (iTry == 0)
414                 oDDFModule.Rewind();
415             else
416             {
417                 CPLError( CE_Failure, CPLE_AppDefined,
418                           "Cannot read scanline %d.  Raster access failed.\n",
419                           nYOffset );
420                 return FALSE;
421             }
422         }
423         else
424         {
425             break;
426         }
427     }
428 
429 /* -------------------------------------------------------------------- */
430 /*      Validate the records size.  Does it represent exactly one       */
431 /*      scanline?                                                       */
432 /* -------------------------------------------------------------------- */
433     DDFField *poCVLS = poRecord->FindField( "CVLS" );
434     if( poCVLS == nullptr )
435         return FALSE;
436 
437     if( poCVLS->GetRepeatCount() != nXSize )
438     {
439         CPLError( CE_Failure, CPLE_AppDefined,
440                   "Cell record is %d long, but we expected %d, the number\n"
441                   "of pixels in a scanline.  Raster access failed.\n",
442                   poCVLS->GetRepeatCount(), nXSize );
443         return FALSE;
444     }
445 
446 /* -------------------------------------------------------------------- */
447 /*      Does the CVLS field consist of exactly 1 B(16) field?           */
448 /* -------------------------------------------------------------------- */
449     if( poCVLS->GetDataSize() < nBytesPerValue * nXSize
450         || poCVLS->GetDataSize() > nBytesPerValue * nXSize + 1 )
451     {
452         CPLError( CE_Failure, CPLE_AppDefined,
453                   "Cell record is not of expected format.  Raster access "
454                   "failed.\n" );
455 
456         return FALSE;
457     }
458 
459 /* -------------------------------------------------------------------- */
460 /*      Copy the data to the application buffer, and byte swap if       */
461 /*      required.                                                       */
462 /* -------------------------------------------------------------------- */
463     memcpy( pData, poCVLS->GetData(), nXSize * nBytesPerValue );
464 
465 #ifdef CPL_LSB
466     if( nBytesPerValue == 2 )
467     {
468         for( int i = 0; i < nXSize; i++ )
469         {
470             reinterpret_cast<GInt16 *>( pData )[i] = CPL_MSBWORD16(
471                 reinterpret_cast<GInt16 *>( pData )[i] );
472         }
473     }
474     else
475     {
476         for( int i = 0; i < nXSize; i++ )
477         {
478             CPL_MSBPTR32( reinterpret_cast<GByte *>( pData ) + i*4 );
479         }
480     }
481 #endif
482 
483     return TRUE;
484 }
485 
486 /************************************************************************/
487 /*                            GetTransform()                            */
488 /************************************************************************/
489 
490 /**
491   Fetch the transformation between pixel/line coordinates and georeferenced
492   coordinates.
493 
494   @param padfTransformOut pointer to an array of six doubles which will be
495   filled with the georeferencing transform.
496 
497   @return TRUE is returned, indicating success.
498 
499   The padfTransformOut array consists of six values.  The pixel/line coordinate
500   (Xp,Yp) can be related to a georeferenced coordinate (Xg,Yg) or (Easting,
501   Northing).
502 
503   <pre>
504   Xg = padfTransformOut[0] + Xp * padfTransform[1] + Yp * padfTransform[2]
505   Yg = padfTransformOut[3] + Xp * padfTransform[4] + Yp * padfTransform[5]
506   </pre>
507 
508   In other words, for a north up image the top left corner of the top left
509   pixel is at georeferenced coordinate (padfTransform[0],padfTransform[3])
510   the pixel width is padfTransform[1], the pixel height is padfTransform[5]
511   and padfTransform[2] and padfTransform[4] will be zero.
512 
513   */
514 
GetTransform(double * padfTransformOut)515 int SDTSRasterReader::GetTransform( double * padfTransformOut )
516 
517 {
518     memcpy( padfTransformOut, adfTransform, sizeof(double)*6 );
519 
520     return TRUE;
521 }
522 
523 /************************************************************************/
524 /*                           GetRasterType()                            */
525 /************************************************************************/
526 
527 /**
528  * Fetch the pixel data type.
529  *
530  * Returns one of SDTS_RT_INT16 (1) or SDTS_RT_FLOAT32 (6) indicating the
531  * type of buffer that should be passed to GetBlock().
532  */
533 
GetRasterType()534 int SDTSRasterReader::GetRasterType()
535 
536 {
537     if( EQUAL(szFMT,"BFP32") )
538         return SDTS_RT_FLOAT32;
539 
540     return SDTS_RT_INT16;
541 }
542 
543 /************************************************************************/
544 /*                             GetMinMax()                              */
545 /************************************************************************/
546 
547 /**
548  * Fetch the minimum and maximum raster values that occur in the file.
549  *
550  * Note this operation current results in a scan of the entire file.
551  *
552  * @param pdfMin variable in which the minimum value encountered is returned.
553  * @param pdfMax variable in which the maximum value encountered is returned.
554  * @param dfNoData a value to ignore when computing min/max, defaults to
555  * -32766.
556  *
557  * @return TRUE on success, or FALSE if an error occurs.
558  */
559 
GetMinMax(double * pdfMin,double * pdfMax,double dfNoData)560 int SDTSRasterReader::GetMinMax( double * pdfMin, double * pdfMax,
561                                  double dfNoData )
562 
563 {
564     CPLAssert( GetBlockXSize() == GetXSize() && GetBlockYSize() == 1 );
565 
566     bool bFirst = true;
567     const bool b32Bit = GetRasterType() == SDTS_RT_FLOAT32;
568     void *pBuffer = CPLMalloc(sizeof(float) * GetXSize());
569 
570     for( int iLine = 0; iLine < GetYSize(); iLine++ )
571     {
572         if( !GetBlock( 0, iLine, pBuffer ) )
573         {
574             CPLFree( pBuffer );
575             return FALSE;
576         }
577 
578         for( int iPixel = 0; iPixel < GetXSize(); iPixel++ )
579         {
580             double dfValue;
581 
582             if( b32Bit )
583                 dfValue = reinterpret_cast<float *>( pBuffer )[iPixel];
584             else
585                 dfValue = reinterpret_cast<short *>( pBuffer )[iPixel];
586 
587             if( dfValue != dfNoData )
588             {
589                 if( bFirst )
590                 {
591                     *pdfMin = dfValue;
592                     *pdfMax = dfValue;
593                     bFirst = false;
594                 }
595                 else
596                 {
597                     *pdfMin = std::min( *pdfMin, dfValue );
598                     *pdfMax = std::max( *pdfMax, dfValue );
599                 }
600             }
601         }
602     }
603 
604     CPLFree( pBuffer );
605 
606     return !bFirst;
607 }
608