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