1 /******************************************************************************
2  *
3  * Project:  USGS DOQ Driver (First Generation Format)
4  * Purpose:  Implementation of DOQ1Dataset
5  * Author:   Frank Warmerdam, warmerda@home.com
6  *
7  ******************************************************************************
8  * Copyright (c) 1999, Frank Warmerdam
9  * Copyright (c) 2009-2011, 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 "gdal_frmts.h"
31 #include "cpl_string.h"
32 #include "rawdataset.h"
33 
34 CPL_CVSID("$Id: doq1dataset.cpp d110d871fd27bbaef9499c77fac17bbecf274292 2020-11-05 10:24:24 +0100 Even Rouault $")
35 
36 static const char UTM_FORMAT[] =
37     "PROJCS[\"%s / UTM zone %dN\",GEOGCS[%s,PRIMEM[\"Greenwich\",0],"
38     "UNIT[\"degree\",0.0174532925199433]],PROJECTION[\"Transverse_Mercator\"],"
39     "PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",%d],"
40     "PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],"
41     "PARAMETER[\"false_northing\",0],%s]";
42 
43 static const char WGS84_DATUM[] =
44     "\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563]]";
45 
46 static const char WGS72_DATUM[] =
47     "\"WGS 72\",DATUM[\"WGS_1972\",SPHEROID[\"NWL 10D\",6378135,298.26]]";
48 
49 static const char NAD27_DATUM[] =
50     "\"NAD27\",DATUM[\"North_American_Datum_1927\","
51     "SPHEROID[\"Clarke 1866\",6378206.4,294.978698213901]]";
52 
53 static const char NAD83_DATUM[] =
54     "\"NAD83\",DATUM[\"North_American_Datum_1983\","
55     "SPHEROID[\"GRS 1980\",6378137,298.257222101]]";
56 
57 /************************************************************************/
58 /*                            DOQGetField()                             */
59 /************************************************************************/
60 
DOQGetField(unsigned char * pabyData,int nBytes)61 static double DOQGetField( unsigned char *pabyData, int nBytes )
62 
63 {
64     char szWork[128] = { '\0' };
65 
66     memcpy( szWork, reinterpret_cast<const char *>( pabyData ), nBytes );
67     szWork[nBytes] = '\0';
68 
69     for( int i = 0; i < nBytes; i++ )
70     {
71         if( szWork[i] == 'D' || szWork[i] == 'd' )
72             szWork[i] = 'E';
73     }
74 
75     return CPLAtof(szWork);
76 }
77 
78 /************************************************************************/
79 /*                         DOQGetDescription()                          */
80 /************************************************************************/
81 
DOQGetDescription(GDALDataset * poDS,unsigned char * pabyData)82 static void DOQGetDescription( GDALDataset *poDS, unsigned char *pabyData )
83 
84 {
85     char szWork[128] = { ' ' };
86 
87     const char* pszDescBegin =  "USGS GeoTIFF DOQ 1:12000 Q-Quad of ";
88     memcpy( szWork, pszDescBegin, strlen(pszDescBegin) );
89     memcpy( szWork + strlen(pszDescBegin),
90              reinterpret_cast<const char *>( pabyData + 0 ), 38 );
91 
92     int i = 0;
93     while ( *(szWork + 72 - i) == ' ' ) {
94       i++;
95     }
96     i--;
97 
98     memcpy(
99         szWork + 73 - i, reinterpret_cast<const char *>( pabyData + 38 ), 2 );
100     memcpy(
101         szWork + 76 - i, reinterpret_cast<const char *>( pabyData + 44 ), 2 );
102     szWork[77-i] = '\0';
103 
104     poDS->SetMetadataItem( "DOQ_DESC", szWork );
105 }
106 
107 /************************************************************************/
108 /* ==================================================================== */
109 /*                              DOQ1Dataset                             */
110 /* ==================================================================== */
111 /************************************************************************/
112 
113 class DOQ1Dataset final: public RawDataset
114 {
115     VSILFILE    *fpImage;       // image data file.
116 
117     double      dfULX;
118     double      dfULY;
119     double      dfXPixelSize;
120     double      dfYPixelSize;
121 
122     char        *pszProjection;
123 
124     CPL_DISALLOW_COPY_ASSIGN(DOQ1Dataset)
125 
126   public:
127                 DOQ1Dataset();
128                 ~DOQ1Dataset();
129 
130     CPLErr      GetGeoTransform( double * padfTransform ) override;
131     const char  *_GetProjectionRef( void ) override;
GetSpatialRef() const132     const OGRSpatialReference* GetSpatialRef() const override {
133         return GetSpatialRefFromOldGetProjectionRef();
134     }
135 
136     static GDALDataset *Open( GDALOpenInfo * );
137 };
138 
139 /************************************************************************/
140 /*                            DOQ1Dataset()                             */
141 /************************************************************************/
142 
DOQ1Dataset()143 DOQ1Dataset::DOQ1Dataset() :
144     fpImage(nullptr),
145     dfULX(0.0),
146     dfULY(0.0),
147     dfXPixelSize(0.0),
148     dfYPixelSize(0.0),
149     pszProjection(nullptr)
150 {}
151 
152 /************************************************************************/
153 /*                            ~DOQ1Dataset()                            */
154 /************************************************************************/
155 
~DOQ1Dataset()156 DOQ1Dataset::~DOQ1Dataset()
157 
158 {
159     FlushCache();
160 
161     CPLFree( pszProjection );
162     if( fpImage != nullptr )
163         CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));
164 }
165 
166 /************************************************************************/
167 /*                          GetGeoTransform()                           */
168 /************************************************************************/
169 
GetGeoTransform(double * padfTransform)170 CPLErr DOQ1Dataset::GetGeoTransform( double * padfTransform )
171 
172 {
173     padfTransform[0] = dfULX;
174     padfTransform[1] = dfXPixelSize;
175     padfTransform[2] = 0.0;
176     padfTransform[3] = dfULY;
177     padfTransform[4] = 0.0;
178     padfTransform[5] = -1 * dfYPixelSize;
179 
180     return CE_None;
181 }
182 
183 /************************************************************************/
184 /*                        GetProjectionString()                         */
185 /************************************************************************/
186 
_GetProjectionRef()187 const char *DOQ1Dataset::_GetProjectionRef()
188 
189 {
190     return pszProjection;
191 }
192 
193 /************************************************************************/
194 /*                                Open()                                */
195 /************************************************************************/
196 
Open(GDALOpenInfo * poOpenInfo)197 GDALDataset *DOQ1Dataset::Open( GDALOpenInfo * poOpenInfo )
198 
199 {
200 /* -------------------------------------------------------------------- */
201 /*      We assume the user is pointing to the binary (i.e. .bil) file.  */
202 /* -------------------------------------------------------------------- */
203     if( poOpenInfo->nHeaderBytes < 212 || poOpenInfo->fpL == nullptr )
204         return nullptr;
205 
206 /* -------------------------------------------------------------------- */
207 /*      Attempt to extract a few key values from the header.            */
208 /* -------------------------------------------------------------------- */
209     const double dfWidth = DOQGetField(poOpenInfo->pabyHeader + 150, 6 );
210     const double dfHeight = DOQGetField(poOpenInfo->pabyHeader + 144, 6 );
211     const double dfBandStorage = DOQGetField(poOpenInfo->pabyHeader + 162, 3 );
212     const double dfBandTypes = DOQGetField(poOpenInfo->pabyHeader + 156, 3 );
213 
214 /* -------------------------------------------------------------------- */
215 /*      Do these values look coherent for a DOQ file?  It would be      */
216 /*      nice to do a more comprehensive test than this!                 */
217 /* -------------------------------------------------------------------- */
218     if( dfWidth < 500 || dfWidth > 25000 || CPLIsNan(dfWidth)
219         || dfHeight < 500 || dfHeight > 25000 || CPLIsNan(dfHeight)
220         || dfBandStorage < 0 || dfBandStorage > 4 || CPLIsNan(dfBandStorage)
221         || dfBandTypes < 1 || dfBandTypes > 9 || CPLIsNan(dfBandTypes) )
222         return nullptr;
223 
224     const int nWidth = static_cast<int>(dfWidth);
225     const int nHeight = static_cast<int>(dfHeight);
226     /*const int nBandStorage = static_cast<int>(dfBandStorage);*/
227     const int nBandTypes = static_cast<int>(dfBandTypes);
228 
229 /* -------------------------------------------------------------------- */
230 /*      Check the configuration.  We don't currently handle all         */
231 /*      variations, only the common ones.                               */
232 /* -------------------------------------------------------------------- */
233     if( nBandTypes > 5 )
234     {
235         CPLError( CE_Failure, CPLE_OpenFailed,
236                   "DOQ Data Type (%d) is not a supported configuration.",
237                   nBandTypes );
238         return nullptr;
239     }
240 
241 /* -------------------------------------------------------------------- */
242 /*      Confirm the requested access is supported.                      */
243 /* -------------------------------------------------------------------- */
244     if( poOpenInfo->eAccess == GA_Update )
245     {
246         CPLError( CE_Failure, CPLE_NotSupported,
247                   "The DOQ1 driver does not support update access to existing "
248                   "datasets." );
249         return nullptr;
250     }
251 
252 /* -------------------------------------------------------------------- */
253 /*      Create a corresponding GDALDataset.                             */
254 /* -------------------------------------------------------------------- */
255     DOQ1Dataset *poDS = new DOQ1Dataset();
256 
257 /* -------------------------------------------------------------------- */
258 /*      Capture some information from the file that is of interest.     */
259 /* -------------------------------------------------------------------- */
260     poDS->nRasterXSize = nWidth;
261     poDS->nRasterYSize = nHeight;
262 
263     poDS->fpImage = poOpenInfo->fpL;
264     poOpenInfo->fpL = nullptr;
265 
266 /* -------------------------------------------------------------------- */
267 /*      Compute layout of data.                                         */
268 /* -------------------------------------------------------------------- */
269     int nBytesPerPixel = 0;
270 
271     if( nBandTypes < 5 )
272         nBytesPerPixel = 1;
273     else if( nBandTypes == 5 )
274         nBytesPerPixel = 3;
275 
276     const int nBytesPerLine = nBytesPerPixel * nWidth;
277     const int nSkipBytes = 4 * nBytesPerLine;
278 
279 /* -------------------------------------------------------------------- */
280 /*      Create band information objects.                                */
281 /* -------------------------------------------------------------------- */
282     poDS->nBands = nBytesPerPixel;
283     for( int i = 0; i < poDS->nBands; i++ )
284     {
285         poDS->SetBand( i+1,
286             new RawRasterBand( poDS, i+1, poDS->fpImage,
287                                nSkipBytes + i, nBytesPerPixel, nBytesPerLine,
288                                GDT_Byte, TRUE, RawRasterBand::OwnFP::NO ) );
289     }
290 
291 /* -------------------------------------------------------------------- */
292 /*      Set the description.                                            */
293 /* -------------------------------------------------------------------- */
294     DOQGetDescription(poDS, poOpenInfo->pabyHeader);
295 
296 /* -------------------------------------------------------------------- */
297 /*      Establish the projection string.                                */
298 /* -------------------------------------------------------------------- */
299     if( static_cast<int>( DOQGetField(poOpenInfo->pabyHeader + 195, 3) ) != 1 )
300         poDS->pszProjection = VSIStrdup("");
301     else
302     {
303         int nZone = static_cast<int>(
304             DOQGetField(poOpenInfo->pabyHeader + 198, 6) );
305         if( nZone < 0 || nZone > 60 )
306             nZone = 0;
307 
308         const char *pszUnits = nullptr;
309         if( static_cast<int>( DOQGetField(poOpenInfo->pabyHeader + 204, 3))
310             == 1 )
311             pszUnits = "UNIT[\"US survey foot\",0.304800609601219]";
312         else
313             pszUnits = "UNIT[\"metre\",1]";
314 
315         const char *pszDatumLong = nullptr;
316         const char *pszDatumShort = nullptr;
317         switch( static_cast<int>(
318             DOQGetField(poOpenInfo->pabyHeader + 167, 2) ) )
319         {
320           case 1:
321             pszDatumLong = NAD27_DATUM;
322             pszDatumShort = "NAD 27";
323             break;
324 
325           case 2:
326             pszDatumLong = WGS72_DATUM;
327             pszDatumShort = "WGS 72";
328             break;
329 
330           case 3:
331             pszDatumLong = WGS84_DATUM;
332             pszDatumShort = "WGS 84";
333             break;
334 
335           case 4:
336             pszDatumLong = NAD83_DATUM;
337             pszDatumShort = "NAD 83";
338             break;
339 
340           default:
341             pszDatumLong = "DATUM[\"unknown\"]";
342             pszDatumShort = "unknown";
343             break;
344         }
345 
346         poDS->pszProjection =
347             CPLStrdup(CPLSPrintf( UTM_FORMAT, pszDatumShort, nZone,
348                                   pszDatumLong, nZone * 6 - 183, pszUnits ));
349     }
350 
351 /* -------------------------------------------------------------------- */
352 /*      Read the georeferencing information.                            */
353 /* -------------------------------------------------------------------- */
354     unsigned char abyRecordData[500] = { '\0' };
355 
356     if( VSIFSeekL( poDS->fpImage, nBytesPerLine * 2, SEEK_SET ) != 0
357         || VSIFReadL( abyRecordData, sizeof(abyRecordData),
358                       1, poDS->fpImage) != 1 )
359     {
360         CPLError( CE_Failure, CPLE_FileIO,
361                   "Header read error on %s.",
362                   poOpenInfo->pszFilename );
363         delete poDS;
364         return nullptr;
365     }
366 
367     poDS->dfULX = DOQGetField( abyRecordData + 288, 24 );
368     poDS->dfULY = DOQGetField( abyRecordData + 312, 24 );
369 
370     if( VSIFSeekL( poDS->fpImage, nBytesPerLine * 3, SEEK_SET ) != 0
371         || VSIFReadL( abyRecordData, sizeof(abyRecordData),
372                       1, poDS->fpImage) != 1 )
373     {
374         CPLError( CE_Failure, CPLE_FileIO,
375                   "Header read error on %s.",
376                   poOpenInfo->pszFilename );
377         delete poDS;
378         return nullptr;
379     }
380 
381     poDS->dfXPixelSize = DOQGetField( abyRecordData + 59, 12 );
382     poDS->dfYPixelSize = DOQGetField( abyRecordData + 71, 12 );
383 
384 /* -------------------------------------------------------------------- */
385 /*      Initialize any PAM information.                                 */
386 /* -------------------------------------------------------------------- */
387     poDS->SetDescription( poOpenInfo->pszFilename );
388     poDS->TryLoadXML();
389 
390 /* -------------------------------------------------------------------- */
391 /*      Check for overviews.                                            */
392 /* -------------------------------------------------------------------- */
393     poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
394 
395     return poDS;
396 }
397 
398 /************************************************************************/
399 /*                         GDALRegister_DOQ1()                          */
400 /************************************************************************/
401 
GDALRegister_DOQ1()402 void GDALRegister_DOQ1()
403 
404 {
405     if( GDALGetDriverByName( "DOQ1" ) != nullptr )
406         return;
407 
408     GDALDriver *poDriver = new GDALDriver();
409 
410     poDriver->SetDescription( "DOQ1" );
411     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
412     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "USGS DOQ (Old Style)" );
413     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/doq1.html" );
414     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
415 
416     poDriver->pfnOpen = DOQ1Dataset::Open;
417 
418     GetGDALDriverManager()->RegisterDriver( poDriver );
419 }
420