/src/gdal/frmts/aigrid/aigdataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Arc/Info Binary Grid Driver |
4 | | * Purpose: Implements GDAL interface to underlying library. |
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 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "aigrid.h" |
15 | | #include "avc.h" |
16 | | #include "cpl_string.h" |
17 | | #include "gdal_frmts.h" |
18 | | #include "gdal_pam.h" |
19 | | #include "gdal_colortable.h" |
20 | | #include "gdal_driver.h" |
21 | | #include "gdal_drivermanager.h" |
22 | | #include "gdal_openinfo.h" |
23 | | #include "gdal_cpp_functions.h" |
24 | | #include "gdal_rat.h" |
25 | | #include "ogr_spatialref.h" |
26 | | |
27 | | #include <vector> |
28 | | |
29 | | static CPLString OSR_GDS(char **papszNV, const char *pszField, |
30 | | const char *pszDefaultValue); |
31 | | |
32 | | /************************************************************************/ |
33 | | /* ==================================================================== */ |
34 | | /* AIGDataset */ |
35 | | /* ==================================================================== */ |
36 | | /************************************************************************/ |
37 | | |
38 | | class AIGRasterBand; |
39 | | |
40 | | class AIGDataset final : public GDALPamDataset |
41 | | { |
42 | | friend class AIGRasterBand; |
43 | | |
44 | | AIGInfo_t *psInfo; |
45 | | |
46 | | char **papszPrj; |
47 | | OGRSpatialReference m_oSRS{}; |
48 | | |
49 | | GDALColorTable *poCT; |
50 | | bool bHasReadRat; |
51 | | |
52 | | void TranslateColorTable(const char *); |
53 | | |
54 | | void ReadRAT(); |
55 | | GDALRasterAttributeTable *poRAT; |
56 | | |
57 | | public: |
58 | | AIGDataset(); |
59 | | ~AIGDataset() override; |
60 | | |
61 | | static GDALDataset *Open(GDALOpenInfo *); |
62 | | |
63 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
64 | | const OGRSpatialReference *GetSpatialRef() const override; |
65 | | char **GetFileList(void) override; |
66 | | }; |
67 | | |
68 | | /************************************************************************/ |
69 | | /* ==================================================================== */ |
70 | | /* AIGRasterBand */ |
71 | | /* ==================================================================== */ |
72 | | /************************************************************************/ |
73 | | |
74 | | class AIGRasterBand final : public GDALPamRasterBand |
75 | | |
76 | | { |
77 | | friend class AIGDataset; |
78 | | |
79 | | public: |
80 | | AIGRasterBand(AIGDataset *, int); |
81 | | |
82 | | CPLErr IReadBlock(int, int, void *) override; |
83 | | double GetMinimum(int *pbSuccess) override; |
84 | | double GetMaximum(int *pbSuccess) override; |
85 | | double GetNoDataValue(int *pbSuccess) override; |
86 | | |
87 | | GDALColorInterp GetColorInterpretation() override; |
88 | | GDALColorTable *GetColorTable() override; |
89 | | GDALRasterAttributeTable *GetDefaultRAT() override; |
90 | | }; |
91 | | |
92 | | /************************************************************************/ |
93 | | /* AIGRasterBand() */ |
94 | | /************************************************************************/ |
95 | | |
96 | | AIGRasterBand::AIGRasterBand(AIGDataset *poDSIn, int nBandIn) |
97 | | |
98 | 7.60k | { |
99 | 7.60k | poDS = poDSIn; |
100 | 7.60k | nBand = nBandIn; |
101 | | |
102 | 7.60k | nBlockXSize = poDSIn->psInfo->nBlockXSize; |
103 | 7.60k | nBlockYSize = poDSIn->psInfo->nBlockYSize; |
104 | | |
105 | 7.60k | if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT && |
106 | 2.13k | poDSIn->psInfo->dfMin >= 0.0 && poDSIn->psInfo->dfMax <= 254.0) |
107 | 124 | { |
108 | 124 | eDataType = GDT_Byte; |
109 | 124 | } |
110 | 7.48k | else if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT && |
111 | 2.01k | poDSIn->psInfo->dfMin >= -32767 && poDSIn->psInfo->dfMax <= 32767) |
112 | 46 | { |
113 | 46 | eDataType = GDT_Int16; |
114 | 46 | } |
115 | 7.43k | else if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT) |
116 | 1.96k | { |
117 | 1.96k | eDataType = GDT_Int32; |
118 | 1.96k | } |
119 | 5.46k | else |
120 | 5.46k | { |
121 | 5.46k | eDataType = GDT_Float32; |
122 | 5.46k | } |
123 | 7.60k | } |
124 | | |
125 | | /************************************************************************/ |
126 | | /* IReadBlock() */ |
127 | | /************************************************************************/ |
128 | | |
129 | | CPLErr AIGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) |
130 | | |
131 | 687k | { |
132 | 687k | AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS); |
133 | 687k | GInt32 *panGridRaster; |
134 | | |
135 | 687k | if (poODS->psInfo->nCellType == AIG_CELLTYPE_INT) |
136 | 49.8k | { |
137 | 49.8k | panGridRaster = (GInt32 *)VSIMalloc3(4, nBlockXSize, nBlockYSize); |
138 | 49.8k | if (panGridRaster == nullptr || |
139 | 49.8k | AIGReadTile(poODS->psInfo, nBlockXOff, nBlockYOff, panGridRaster) != |
140 | 49.8k | CE_None) |
141 | 96 | { |
142 | 96 | CPLFree(panGridRaster); |
143 | 96 | return CE_Failure; |
144 | 96 | } |
145 | | |
146 | 49.7k | if (eDataType == GDT_Byte) |
147 | 7.32k | { |
148 | 19.8M | for (int i = 0; i < nBlockXSize * nBlockYSize; i++) |
149 | 19.8M | { |
150 | 19.8M | if (panGridRaster[i] == ESRI_GRID_NO_DATA) |
151 | 19.8M | ((GByte *)pImage)[i] = 255; |
152 | 8.74k | else |
153 | 8.74k | ((GByte *)pImage)[i] = (GByte)panGridRaster[i]; |
154 | 19.8M | } |
155 | 7.32k | } |
156 | 42.4k | else if (eDataType == GDT_Int16) |
157 | 2.65k | { |
158 | 6.60M | for (int i = 0; i < nBlockXSize * nBlockYSize; i++) |
159 | 6.60M | { |
160 | 6.60M | if (panGridRaster[i] == ESRI_GRID_NO_DATA) |
161 | 6.48M | ((GInt16 *)pImage)[i] = -32768; |
162 | 123k | else |
163 | 123k | ((GInt16 *)pImage)[i] = (GInt16)panGridRaster[i]; |
164 | 6.60M | } |
165 | 2.65k | } |
166 | 39.7k | else |
167 | 39.7k | { |
168 | 57.7M | for (int i = 0; i < nBlockXSize * nBlockYSize; i++) |
169 | 57.7M | ((GInt32 *)pImage)[i] = panGridRaster[i]; |
170 | 39.7k | } |
171 | | |
172 | 49.7k | CPLFree(panGridRaster); |
173 | | |
174 | 49.7k | return CE_None; |
175 | 49.8k | } |
176 | 637k | else |
177 | 637k | { |
178 | 637k | return AIGReadFloatTile(poODS->psInfo, nBlockXOff, nBlockYOff, |
179 | 637k | (float *)pImage); |
180 | 637k | } |
181 | 687k | } |
182 | | |
183 | | /************************************************************************/ |
184 | | /* GetDefaultRAT() */ |
185 | | /************************************************************************/ |
186 | | |
187 | | GDALRasterAttributeTable *AIGRasterBand::GetDefaultRAT() |
188 | | |
189 | 0 | { |
190 | 0 | AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS); |
191 | | |
192 | | /* -------------------------------------------------------------------- */ |
193 | | /* Read info raster attribute table, if present. */ |
194 | | /* -------------------------------------------------------------------- */ |
195 | 0 | if (!poODS->bHasReadRat) |
196 | 0 | { |
197 | 0 | poODS->ReadRAT(); |
198 | 0 | poODS->bHasReadRat = true; |
199 | 0 | } |
200 | |
|
201 | 0 | if (poODS->poRAT) |
202 | 0 | return poODS->poRAT; |
203 | 0 | else |
204 | 0 | return GDALPamRasterBand::GetDefaultRAT(); |
205 | 0 | } |
206 | | |
207 | | /************************************************************************/ |
208 | | /* GetMinimum() */ |
209 | | /************************************************************************/ |
210 | | |
211 | | double AIGRasterBand::GetMinimum(int *pbSuccess) |
212 | | |
213 | 0 | { |
214 | 0 | AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS); |
215 | |
|
216 | 0 | if (pbSuccess != nullptr) |
217 | 0 | *pbSuccess = TRUE; |
218 | |
|
219 | 0 | return poODS->psInfo->dfMin; |
220 | 0 | } |
221 | | |
222 | | /************************************************************************/ |
223 | | /* GetMaximum() */ |
224 | | /************************************************************************/ |
225 | | |
226 | | double AIGRasterBand::GetMaximum(int *pbSuccess) |
227 | | |
228 | 0 | { |
229 | 0 | AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS); |
230 | |
|
231 | 0 | if (pbSuccess != nullptr) |
232 | 0 | *pbSuccess = TRUE; |
233 | |
|
234 | 0 | return poODS->psInfo->dfMax; |
235 | 0 | } |
236 | | |
237 | | /************************************************************************/ |
238 | | /* GetNoDataValue() */ |
239 | | /************************************************************************/ |
240 | | |
241 | | double AIGRasterBand::GetNoDataValue(int *pbSuccess) |
242 | | |
243 | 30.4k | { |
244 | 30.4k | if (pbSuccess != nullptr) |
245 | 22.8k | *pbSuccess = TRUE; |
246 | | |
247 | 30.4k | if (eDataType == GDT_Float32) |
248 | 21.8k | return ESRI_GRID_FLOAT_NO_DATA; |
249 | | |
250 | 8.54k | if (eDataType == GDT_Int16) |
251 | 184 | return -32768; |
252 | | |
253 | 8.36k | if (eDataType == GDT_Byte) |
254 | 496 | return 255; |
255 | | |
256 | 7.86k | return ESRI_GRID_NO_DATA; |
257 | 8.36k | } |
258 | | |
259 | | /************************************************************************/ |
260 | | /* GetColorInterpretation() */ |
261 | | /************************************************************************/ |
262 | | |
263 | | GDALColorInterp AIGRasterBand::GetColorInterpretation() |
264 | | |
265 | 0 | { |
266 | 0 | AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS); |
267 | |
|
268 | 0 | if (poODS->poCT != nullptr) |
269 | 0 | return GCI_PaletteIndex; |
270 | | |
271 | 0 | return GDALPamRasterBand::GetColorInterpretation(); |
272 | 0 | } |
273 | | |
274 | | /************************************************************************/ |
275 | | /* GetColorTable() */ |
276 | | /************************************************************************/ |
277 | | |
278 | | GDALColorTable *AIGRasterBand::GetColorTable() |
279 | | |
280 | 0 | { |
281 | 0 | AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS); |
282 | |
|
283 | 0 | if (poODS->poCT != nullptr) |
284 | 0 | return poODS->poCT; |
285 | | |
286 | 0 | return GDALPamRasterBand::GetColorTable(); |
287 | 0 | } |
288 | | |
289 | | /************************************************************************/ |
290 | | /* ==================================================================== */ |
291 | | /* AIGDataset */ |
292 | | /* ==================================================================== */ |
293 | | /************************************************************************/ |
294 | | |
295 | | /************************************************************************/ |
296 | | /* AIGDataset() */ |
297 | | /************************************************************************/ |
298 | | |
299 | | AIGDataset::AIGDataset() |
300 | 7.60k | : psInfo(nullptr), papszPrj(nullptr), poCT(nullptr), bHasReadRat(false), |
301 | 7.60k | poRAT(nullptr) |
302 | 7.60k | { |
303 | 7.60k | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
304 | 7.60k | } |
305 | | |
306 | | /************************************************************************/ |
307 | | /* ~AIGDataset() */ |
308 | | /************************************************************************/ |
309 | | |
310 | | AIGDataset::~AIGDataset() |
311 | | |
312 | 7.60k | { |
313 | 7.60k | FlushCache(true); |
314 | 7.60k | CSLDestroy(papszPrj); |
315 | 7.60k | if (psInfo != nullptr) |
316 | 7.60k | AIGClose(psInfo); |
317 | | |
318 | 7.60k | if (poCT != nullptr) |
319 | 235 | delete poCT; |
320 | | |
321 | 7.60k | if (poRAT != nullptr) |
322 | 0 | delete poRAT; |
323 | 7.60k | } |
324 | | |
325 | | /************************************************************************/ |
326 | | /* GetFileList() */ |
327 | | /************************************************************************/ |
328 | | |
329 | | char **AIGDataset::GetFileList() |
330 | | |
331 | 15.2k | { |
332 | 15.2k | char **papszFileList = GDALPamDataset::GetFileList(); |
333 | | |
334 | | // Add in all files in the cover directory. |
335 | 15.2k | char **papszCoverFiles = VSIReadDir(GetDescription()); |
336 | | |
337 | 129k | for (int i = 0; papszCoverFiles != nullptr && papszCoverFiles[i] != nullptr; |
338 | 114k | i++) |
339 | 114k | { |
340 | 114k | if (EQUAL(papszCoverFiles[i], ".") || EQUAL(papszCoverFiles[i], "..")) |
341 | 2 | continue; |
342 | | |
343 | 114k | papszFileList = CSLAddString( |
344 | 114k | papszFileList, |
345 | 114k | CPLFormFilenameSafe(GetDescription(), papszCoverFiles[i], nullptr) |
346 | 114k | .c_str()); |
347 | 114k | } |
348 | 15.2k | CSLDestroy(papszCoverFiles); |
349 | | |
350 | 15.2k | return papszFileList; |
351 | 15.2k | } |
352 | | |
353 | | /************************************************************************/ |
354 | | /* AIGErrorHandlerVATOpen() */ |
355 | | /************************************************************************/ |
356 | | |
357 | | class AIGErrorDescription |
358 | | { |
359 | | public: |
360 | | CPLErr eErr; |
361 | | CPLErrorNum no; |
362 | | std::string osMsg; |
363 | | }; |
364 | | |
365 | | static void CPL_STDCALL AIGErrorHandlerVATOpen(CPLErr eErr, CPLErrorNum no, |
366 | | const char *msg) |
367 | 0 | { |
368 | 0 | std::vector<AIGErrorDescription> *paoErrors = |
369 | 0 | (std::vector<AIGErrorDescription> *)CPLGetErrorHandlerUserData(); |
370 | 0 | if (STARTS_WITH_CI(msg, "EOF encountered in") && |
371 | 0 | strstr(msg, "../info/arc.dir") != nullptr) |
372 | 0 | return; |
373 | 0 | if (STARTS_WITH_CI(msg, "Failed to open table ")) |
374 | 0 | return; |
375 | 0 | AIGErrorDescription oError; |
376 | 0 | oError.eErr = eErr; |
377 | 0 | oError.no = no; |
378 | 0 | oError.osMsg = msg; |
379 | 0 | paoErrors->push_back(std::move(oError)); |
380 | 0 | } |
381 | | |
382 | | /************************************************************************/ |
383 | | /* ReadRAT() */ |
384 | | /************************************************************************/ |
385 | | |
386 | | void AIGDataset::ReadRAT() |
387 | | |
388 | 0 | { |
389 | | /* -------------------------------------------------------------------- */ |
390 | | /* Check if we have an associated info directory. If not */ |
391 | | /* return quietly. */ |
392 | | /* -------------------------------------------------------------------- */ |
393 | 0 | CPLString osInfoPath, osTableName; |
394 | 0 | VSIStatBufL sStatBuf; |
395 | |
|
396 | 0 | osInfoPath = psInfo->pszCoverName; |
397 | 0 | osInfoPath += "/../info"; |
398 | |
|
399 | 0 | if (VSIStatL(osInfoPath, &sStatBuf) != 0) |
400 | 0 | { |
401 | 0 | CPLDebug("AIG", "No associated info directory at: %s, skip RAT.", |
402 | 0 | osInfoPath.c_str()); |
403 | 0 | return; |
404 | 0 | } |
405 | | |
406 | 0 | osInfoPath += "/"; |
407 | | |
408 | | /* -------------------------------------------------------------------- */ |
409 | | /* Attempt to open the VAT table associated with this coverage. */ |
410 | | /* -------------------------------------------------------------------- */ |
411 | 0 | osTableName = CPLGetFilename(psInfo->pszCoverName); |
412 | 0 | osTableName += ".VAT"; |
413 | | |
414 | | /* Turn off errors that can be triggered if the info has no VAT */ |
415 | | /* table related with this coverage */ |
416 | 0 | std::vector<AIGErrorDescription> aoErrors; |
417 | 0 | CPLPushErrorHandlerEx(AIGErrorHandlerVATOpen, &aoErrors); |
418 | |
|
419 | 0 | AVCBinFile *psFile = AVCBinReadOpen( |
420 | 0 | osInfoPath, osTableName, AVCCoverTypeUnknown, AVCFileTABLE, nullptr); |
421 | 0 | CPLPopErrorHandler(); |
422 | | |
423 | | /* Emit other errors */ |
424 | 0 | std::vector<AIGErrorDescription>::const_iterator oIter; |
425 | 0 | for (oIter = aoErrors.begin(); oIter != aoErrors.end(); ++oIter) |
426 | 0 | { |
427 | 0 | const AIGErrorDescription &oError = *oIter; |
428 | 0 | CPLError(oError.eErr, oError.no, "%s", oError.osMsg.c_str()); |
429 | 0 | } |
430 | |
|
431 | 0 | CPLErrorReset(); |
432 | 0 | if (psFile == nullptr) |
433 | 0 | return; |
434 | | |
435 | 0 | AVCTableDef *psTableDef = psFile->hdr.psTableDef; |
436 | | |
437 | | /* -------------------------------------------------------------------- */ |
438 | | /* Setup columns in corresponding RAT. */ |
439 | | /* -------------------------------------------------------------------- */ |
440 | 0 | poRAT = new GDALDefaultRasterAttributeTable(); |
441 | |
|
442 | 0 | for (int iField = 0; iField < psTableDef->numFields; iField++) |
443 | 0 | { |
444 | 0 | AVCFieldInfo *psFDef = psTableDef->pasFieldDef + iField; |
445 | 0 | GDALRATFieldUsage eFUsage = GFU_Generic; |
446 | 0 | GDALRATFieldType eFType = GFT_String; |
447 | |
|
448 | 0 | CPLString osFName = psFDef->szName; |
449 | 0 | osFName.Trim(); |
450 | |
|
451 | 0 | if (EQUAL(osFName, "VALUE")) |
452 | 0 | eFUsage = GFU_MinMax; |
453 | 0 | else if (EQUAL(osFName, "COUNT")) |
454 | 0 | eFUsage = GFU_PixelCount; |
455 | |
|
456 | 0 | if (psFDef->nType1 * 10 == AVC_FT_BININT) |
457 | 0 | eFType = GFT_Integer; |
458 | 0 | else if (psFDef->nType1 * 10 == AVC_FT_BINFLOAT) |
459 | 0 | eFType = GFT_Real; |
460 | |
|
461 | 0 | poRAT->CreateColumn(osFName, eFType, eFUsage); |
462 | 0 | } |
463 | | |
464 | | /* -------------------------------------------------------------------- */ |
465 | | /* Process all records into RAT. */ |
466 | | /* -------------------------------------------------------------------- */ |
467 | 0 | AVCField *pasFields = nullptr; |
468 | 0 | int iRecord = 0; |
469 | |
|
470 | 0 | while ((pasFields = AVCBinReadNextTableRec(psFile)) != nullptr) |
471 | 0 | { |
472 | 0 | iRecord++; |
473 | |
|
474 | 0 | for (int iField = 0; iField < psTableDef->numFields; iField++) |
475 | 0 | { |
476 | 0 | switch (psTableDef->pasFieldDef[iField].nType1 * 10) |
477 | 0 | { |
478 | 0 | case AVC_FT_DATE: |
479 | 0 | case AVC_FT_FIXINT: |
480 | 0 | case AVC_FT_CHAR: |
481 | 0 | case AVC_FT_FIXNUM: |
482 | 0 | { |
483 | | // XXX - I bet mloskot would like to see const_cast + |
484 | | // static_cast :-) |
485 | 0 | const char *pszTmp = |
486 | 0 | (const char *)(pasFields[iField].pszStr); |
487 | 0 | CPLString osStrValue(pszTmp); |
488 | 0 | poRAT->SetValue(iRecord - 1, iField, |
489 | 0 | osStrValue.Trim().c_str()); |
490 | 0 | } |
491 | 0 | break; |
492 | | |
493 | 0 | case AVC_FT_BININT: |
494 | 0 | if (psTableDef->pasFieldDef[iField].nSize == 4) |
495 | 0 | poRAT->SetValue(iRecord - 1, iField, |
496 | 0 | pasFields[iField].nInt32); |
497 | 0 | else |
498 | 0 | poRAT->SetValue(iRecord - 1, iField, |
499 | 0 | pasFields[iField].nInt16); |
500 | 0 | break; |
501 | | |
502 | 0 | case AVC_FT_BINFLOAT: |
503 | 0 | if (psTableDef->pasFieldDef[iField].nSize == 4) |
504 | 0 | poRAT->SetValue(iRecord - 1, iField, |
505 | 0 | pasFields[iField].fFloat); |
506 | 0 | else |
507 | 0 | poRAT->SetValue(iRecord - 1, iField, |
508 | 0 | pasFields[iField].dDouble); |
509 | 0 | break; |
510 | 0 | } |
511 | 0 | } |
512 | 0 | } |
513 | | |
514 | | /* -------------------------------------------------------------------- */ |
515 | | /* Cleanup */ |
516 | | /* -------------------------------------------------------------------- */ |
517 | | |
518 | 0 | AVCBinReadClose(psFile); |
519 | | |
520 | | /* Workaround against #2447 and #3031, to avoid binding languages */ |
521 | | /* not being able to open the dataset */ |
522 | 0 | CPLErrorReset(); |
523 | 0 | } |
524 | | |
525 | | /************************************************************************/ |
526 | | /* Open() */ |
527 | | /************************************************************************/ |
528 | | |
529 | | GDALDataset *AIGDataset::Open(GDALOpenInfo *poOpenInfo) |
530 | | |
531 | 668k | { |
532 | | /* -------------------------------------------------------------------- */ |
533 | | /* If the pass name ends in .adf assume a file within the */ |
534 | | /* coverage has been selected, and strip that off the coverage */ |
535 | | /* name. */ |
536 | | /* -------------------------------------------------------------------- */ |
537 | 668k | CPLString osCoverName; |
538 | | |
539 | 668k | osCoverName = poOpenInfo->pszFilename; |
540 | 668k | if (osCoverName.size() > 4 && |
541 | 655k | EQUAL(osCoverName.c_str() + osCoverName.size() - 4, ".adf")) |
542 | 9.23k | { |
543 | 9.23k | osCoverName = CPLGetDirnameSafe(poOpenInfo->pszFilename); |
544 | 9.23k | if (osCoverName == "") |
545 | 0 | osCoverName = "."; |
546 | 9.23k | } |
547 | | |
548 | | /* -------------------------------------------------------------------- */ |
549 | | /* Otherwise verify we were already given a directory. */ |
550 | | /* -------------------------------------------------------------------- */ |
551 | 659k | else if (!poOpenInfo->bIsDirectory) |
552 | 654k | { |
553 | 654k | return nullptr; |
554 | 654k | } |
555 | | |
556 | | /* -------------------------------------------------------------------- */ |
557 | | /* Verify that a few of the "standard" files are available. */ |
558 | | /* -------------------------------------------------------------------- */ |
559 | 14.1k | VSIStatBufL sStatBuf; |
560 | 14.1k | CPLString osTestName; |
561 | | |
562 | 14.1k | osTestName.Printf("%s/hdr.adf", osCoverName.c_str()); |
563 | 14.1k | if (VSIStatL(osTestName, &sStatBuf) != 0) |
564 | 9.80k | { |
565 | 9.80k | osTestName.Printf("%s/HDR.ADF", osCoverName.c_str()); |
566 | 9.80k | if (VSIStatL(osTestName, &sStatBuf) != 0) |
567 | 5.93k | return nullptr; |
568 | 9.80k | } |
569 | | |
570 | | /* -------------------------------------------------------------------- */ |
571 | | /* Confirm we have at least one raster data file. These can be */ |
572 | | /* sparse so we don't require particular ones to exists but if */ |
573 | | /* there are none this is likely not a grid. */ |
574 | | /* -------------------------------------------------------------------- */ |
575 | 8.19k | char **papszFileList = VSIReadDir(osCoverName); |
576 | 8.19k | bool bGotOne = false; |
577 | | |
578 | 8.19k | if (papszFileList == nullptr) |
579 | 0 | { |
580 | | /* Useful when reading from /vsicurl/ on servers that don't */ |
581 | | /* return a file list */ |
582 | | /* such as |
583 | | * /vsicurl/http://eros.usgs.gov/archive/nslrsda/GeoTowns/NLCD/89110458 |
584 | | */ |
585 | 0 | do |
586 | 0 | { |
587 | 0 | osTestName.Printf("%s/W001001.ADF", osCoverName.c_str()); |
588 | 0 | if (VSIStatL(osTestName, &sStatBuf) == 0) |
589 | 0 | { |
590 | 0 | bGotOne = true; |
591 | 0 | break; |
592 | 0 | } |
593 | | |
594 | 0 | osTestName.Printf("%s/w001001.adf", osCoverName.c_str()); |
595 | 0 | if (VSIStatL(osTestName, &sStatBuf) == 0) |
596 | 0 | { |
597 | 0 | bGotOne = true; |
598 | 0 | break; |
599 | 0 | } |
600 | 0 | } while (false); |
601 | 0 | } |
602 | | |
603 | 38.4k | for (int iFile = 0; papszFileList != nullptr && |
604 | 38.4k | papszFileList[iFile] != nullptr && !bGotOne; |
605 | 30.2k | iFile++) |
606 | 30.2k | { |
607 | 30.2k | if (strlen(papszFileList[iFile]) != 11) |
608 | 21.9k | continue; |
609 | | |
610 | | // looking for something like w001001.adf or z001013.adf |
611 | 8.29k | if (papszFileList[iFile][0] != 'w' && papszFileList[iFile][0] != 'W' && |
612 | 5.88k | papszFileList[iFile][0] != 'z' && papszFileList[iFile][0] != 'Z') |
613 | 181 | continue; |
614 | | |
615 | 8.11k | if (!STARTS_WITH(papszFileList[iFile] + 1, "0010")) |
616 | 112 | continue; |
617 | | |
618 | 8.00k | if (!EQUAL(papszFileList[iFile] + 7, ".adf")) |
619 | 37 | continue; |
620 | | |
621 | 7.96k | bGotOne = true; |
622 | 7.96k | } |
623 | 8.19k | CSLDestroy(papszFileList); |
624 | | |
625 | 8.19k | if (!bGotOne) |
626 | 235 | return nullptr; |
627 | | |
628 | | /* -------------------------------------------------------------------- */ |
629 | | /* Open the file. */ |
630 | | /* -------------------------------------------------------------------- */ |
631 | 7.96k | AIGInfo_t *psInfo = AIGOpen(osCoverName.c_str(), "r"); |
632 | | |
633 | 7.96k | if (psInfo == nullptr) |
634 | 359 | { |
635 | 359 | CPLErrorReset(); |
636 | 359 | return nullptr; |
637 | 359 | } |
638 | | |
639 | | /* -------------------------------------------------------------------- */ |
640 | | /* Confirm the requested access is supported. */ |
641 | | /* -------------------------------------------------------------------- */ |
642 | 7.60k | if (poOpenInfo->eAccess == GA_Update) |
643 | 0 | { |
644 | 0 | AIGClose(psInfo); |
645 | 0 | ReportUpdateNotSupportedByDriver("AIG"); |
646 | 0 | return nullptr; |
647 | 0 | } |
648 | | /* -------------------------------------------------------------------- */ |
649 | | /* Create a corresponding GDALDataset. */ |
650 | | /* -------------------------------------------------------------------- */ |
651 | 7.60k | AIGDataset *poDS = new AIGDataset(); |
652 | | |
653 | 7.60k | poDS->psInfo = psInfo; |
654 | | |
655 | | /* -------------------------------------------------------------------- */ |
656 | | /* Try to read a color table (.clr). It seems it is legal to */ |
657 | | /* have more than one so we just use the first one found. */ |
658 | | /* -------------------------------------------------------------------- */ |
659 | 7.60k | char **papszFiles = VSIReadDir(psInfo->pszCoverName); |
660 | 7.60k | CPLString osClrFilename; |
661 | 7.60k | CPLString osCleanPath = CPLCleanTrailingSlashSafe(psInfo->pszCoverName); |
662 | | |
663 | | // first check for any .clr in coverage dir. |
664 | 63.3k | for (int iFile = 0; papszFiles != nullptr && papszFiles[iFile] != nullptr; |
665 | 55.7k | iFile++) |
666 | 55.9k | { |
667 | 55.9k | const std::string osExt = CPLGetExtensionSafe(papszFiles[iFile]); |
668 | 55.9k | if (!EQUAL(osExt.c_str(), "clr") && !EQUAL(osExt.c_str(), "CLR")) |
669 | 55.7k | continue; |
670 | | |
671 | 248 | osClrFilename = CPLFormFilenameSafe(psInfo->pszCoverName, |
672 | 248 | papszFiles[iFile], nullptr); |
673 | 248 | break; |
674 | 55.9k | } |
675 | | |
676 | 7.60k | CSLDestroy(papszFiles); |
677 | | |
678 | | // Look in parent if we don't find a .clr in the coverage dir. |
679 | 7.60k | if (osClrFilename.empty()) |
680 | 7.35k | { |
681 | 7.35k | CPLString osTestClrFilename; |
682 | 7.35k | osTestClrFilename.Printf("%s/../%s.clr", psInfo->pszCoverName, |
683 | 7.35k | CPLGetFilename(osCleanPath)); |
684 | | |
685 | 7.35k | if (VSIStatL(osTestClrFilename, &sStatBuf) != 0) |
686 | 7.35k | { |
687 | 7.35k | osTestClrFilename.Printf("%s/../%s.CLR", psInfo->pszCoverName, |
688 | 7.35k | CPLGetFilename(osCleanPath)); |
689 | | |
690 | 7.35k | if (!VSIStatL(osTestClrFilename, &sStatBuf)) |
691 | 0 | osClrFilename = std::move(osTestClrFilename); |
692 | 7.35k | } |
693 | 0 | else |
694 | 0 | osClrFilename = std::move(osTestClrFilename); |
695 | 7.35k | } |
696 | | |
697 | 7.60k | if (!osClrFilename.empty()) |
698 | 248 | poDS->TranslateColorTable(osClrFilename); |
699 | | |
700 | | /* -------------------------------------------------------------------- */ |
701 | | /* Establish raster info. */ |
702 | | /* -------------------------------------------------------------------- */ |
703 | 7.60k | poDS->nRasterXSize = psInfo->nPixels; |
704 | 7.60k | poDS->nRasterYSize = psInfo->nLines; |
705 | 7.60k | poDS->nBands = 1; |
706 | | |
707 | | /* -------------------------------------------------------------------- */ |
708 | | /* Create band information objects. */ |
709 | | /* -------------------------------------------------------------------- */ |
710 | 7.60k | poDS->SetBand(1, new AIGRasterBand(poDS, 1)); |
711 | | |
712 | | /* -------------------------------------------------------------------- */ |
713 | | /* Try to read projection file. */ |
714 | | /* -------------------------------------------------------------------- */ |
715 | 7.60k | const std::string osPrjFilename = |
716 | 7.60k | CPLFormCIFilenameSafe(psInfo->pszCoverName, "prj", "adf"); |
717 | 7.60k | if (VSIStatL(osPrjFilename.c_str(), &sStatBuf) == 0) |
718 | 7.09k | { |
719 | 7.09k | OGRSpatialReference oSRS; |
720 | 7.09k | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
721 | | |
722 | 7.09k | poDS->papszPrj = CSLLoad(osPrjFilename.c_str()); |
723 | | |
724 | 7.09k | if (oSRS.importFromESRI(poDS->papszPrj) == OGRERR_NONE) |
725 | 5.14k | { |
726 | | // If geographic values are in seconds, we must transform. |
727 | | // Is there a code for minutes too? |
728 | 5.14k | if (oSRS.IsGeographic() && |
729 | 538 | EQUAL(OSR_GDS(poDS->papszPrj, "Units", ""), "DS")) |
730 | 0 | { |
731 | 0 | psInfo->dfLLX /= 3600.0; |
732 | 0 | psInfo->dfURY /= 3600.0; |
733 | 0 | psInfo->dfCellSizeX /= 3600.0; |
734 | 0 | psInfo->dfCellSizeY /= 3600.0; |
735 | 0 | } |
736 | | |
737 | 5.14k | poDS->m_oSRS = std::move(oSRS); |
738 | 5.14k | } |
739 | 7.09k | } |
740 | | |
741 | | /* -------------------------------------------------------------------- */ |
742 | | /* Initialize any PAM information. */ |
743 | | /* -------------------------------------------------------------------- */ |
744 | 7.60k | poDS->SetDescription(psInfo->pszCoverName); |
745 | 7.60k | poDS->TryLoadXML(); |
746 | | |
747 | | /* -------------------------------------------------------------------- */ |
748 | | /* Open overviews. */ |
749 | | /* -------------------------------------------------------------------- */ |
750 | 7.60k | poDS->oOvManager.Initialize(poDS, psInfo->pszCoverName); |
751 | | |
752 | 7.60k | return poDS; |
753 | 7.60k | } |
754 | | |
755 | | /************************************************************************/ |
756 | | /* GetGeoTransform() */ |
757 | | /************************************************************************/ |
758 | | |
759 | | CPLErr AIGDataset::GetGeoTransform(GDALGeoTransform >) const |
760 | | |
761 | 7.60k | { |
762 | 7.60k | gt[0] = psInfo->dfLLX; |
763 | 7.60k | gt[1] = psInfo->dfCellSizeX; |
764 | 7.60k | gt[2] = 0; |
765 | | |
766 | 7.60k | gt[3] = psInfo->dfURY; |
767 | 7.60k | gt[4] = 0; |
768 | 7.60k | gt[5] = -psInfo->dfCellSizeY; |
769 | | |
770 | 7.60k | return CE_None; |
771 | 7.60k | } |
772 | | |
773 | | /************************************************************************/ |
774 | | /* GetSpatialRef() */ |
775 | | /************************************************************************/ |
776 | | |
777 | | const OGRSpatialReference *AIGDataset::GetSpatialRef() const |
778 | | |
779 | 7.60k | { |
780 | 7.60k | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
781 | 7.60k | } |
782 | | |
783 | | /************************************************************************/ |
784 | | /* TranslateColorTable() */ |
785 | | /************************************************************************/ |
786 | | |
787 | | void AIGDataset::TranslateColorTable(const char *pszClrFilename) |
788 | | |
789 | 248 | { |
790 | 248 | char **papszClrLines = CSLLoad(pszClrFilename); |
791 | 248 | if (papszClrLines == nullptr) |
792 | 13 | return; |
793 | | |
794 | 235 | poCT = new GDALColorTable(); |
795 | | |
796 | 1.27M | for (int iLine = 0; papszClrLines[iLine] != nullptr; iLine++) |
797 | 1.27M | { |
798 | 1.27M | char **papszTokens = CSLTokenizeString(papszClrLines[iLine]); |
799 | | |
800 | 1.27M | if (CSLCount(papszTokens) >= 4 && papszTokens[0][0] != '#') |
801 | 2.67k | { |
802 | 2.67k | int nIndex; |
803 | 2.67k | GDALColorEntry sEntry; |
804 | | |
805 | 2.67k | nIndex = atoi(papszTokens[0]); |
806 | 2.67k | sEntry.c1 = (short)atoi(papszTokens[1]); |
807 | 2.67k | sEntry.c2 = (short)atoi(papszTokens[2]); |
808 | 2.67k | sEntry.c3 = (short)atoi(papszTokens[3]); |
809 | 2.67k | sEntry.c4 = 255; |
810 | | |
811 | 2.67k | if ((nIndex < 0 || nIndex > 33000) || |
812 | 2.64k | (sEntry.c1 < 0 || sEntry.c1 > 255) || |
813 | 2.63k | (sEntry.c2 < 0 || sEntry.c2 > 255) || |
814 | 2.62k | (sEntry.c3 < 0 || sEntry.c3 > 255)) |
815 | 60 | { |
816 | 60 | CSLDestroy(papszTokens); |
817 | 60 | CPLError(CE_Failure, CPLE_AppDefined, |
818 | 60 | "Color table entry appears to be corrupt, skipping " |
819 | 60 | "the rest. "); |
820 | 60 | break; |
821 | 60 | } |
822 | | |
823 | 2.61k | poCT->SetColorEntry(nIndex, &sEntry); |
824 | 2.61k | } |
825 | | |
826 | 1.27M | CSLDestroy(papszTokens); |
827 | 1.27M | } |
828 | | |
829 | 235 | CSLDestroy(papszClrLines); |
830 | 235 | } |
831 | | |
832 | | /************************************************************************/ |
833 | | /* OSR_GDS() */ |
834 | | /************************************************************************/ |
835 | | |
836 | | static CPLString OSR_GDS(char **papszNV, const char *pszField, |
837 | | const char *pszDefaultValue) |
838 | | |
839 | 538 | { |
840 | 538 | if (papszNV == nullptr || papszNV[0] == nullptr) |
841 | 0 | return pszDefaultValue; |
842 | | |
843 | 538 | int iLine = 0; |
844 | 649k | for (; papszNV[iLine] != nullptr && |
845 | 648k | !EQUALN(papszNV[iLine], pszField, strlen(pszField)); |
846 | 648k | iLine++) |
847 | 648k | { |
848 | 648k | } |
849 | | |
850 | 538 | if (papszNV[iLine] == nullptr) |
851 | 511 | return pszDefaultValue; |
852 | 27 | else |
853 | 27 | { |
854 | 27 | CPLString osResult; |
855 | 27 | char **papszTokens = CSLTokenizeString(papszNV[iLine]); |
856 | | |
857 | 27 | if (CSLCount(papszTokens) > 1) |
858 | 26 | osResult = papszTokens[1]; |
859 | 1 | else |
860 | 1 | osResult = pszDefaultValue; |
861 | | |
862 | 27 | CSLDestroy(papszTokens); |
863 | 27 | return osResult; |
864 | 27 | } |
865 | 538 | } |
866 | | |
867 | | /************************************************************************/ |
868 | | /* AIGRename() */ |
869 | | /* */ |
870 | | /* Custom renamer for AIG dataset. */ |
871 | | /************************************************************************/ |
872 | | |
873 | | static CPLErr AIGRename(const char *pszNewName, const char *pszOldName) |
874 | | |
875 | 0 | { |
876 | | /* -------------------------------------------------------------------- */ |
877 | | /* Make sure we are talking about paths to the coverage */ |
878 | | /* directory. */ |
879 | | /* -------------------------------------------------------------------- */ |
880 | 0 | CPLString osOldPath, osNewPath; |
881 | |
|
882 | 0 | if (!CPLGetExtensionSafe(pszNewName).empty()) |
883 | 0 | osNewPath = CPLGetPathSafe(pszNewName); |
884 | 0 | else |
885 | 0 | osNewPath = pszNewName; |
886 | |
|
887 | 0 | if (!CPLGetExtensionSafe(pszOldName).empty()) |
888 | 0 | osOldPath = CPLGetPathSafe(pszOldName); |
889 | 0 | else |
890 | 0 | osOldPath = pszOldName; |
891 | | |
892 | | /* -------------------------------------------------------------------- */ |
893 | | /* Get file list. */ |
894 | | /* -------------------------------------------------------------------- */ |
895 | |
|
896 | 0 | GDALDatasetH hDS = GDALOpen(osOldPath, GA_ReadOnly); |
897 | 0 | if (hDS == nullptr) |
898 | 0 | return CE_Failure; |
899 | | |
900 | 0 | char **papszFileList = GDALGetFileList(hDS); |
901 | 0 | GDALClose(hDS); |
902 | |
|
903 | 0 | if (papszFileList == nullptr) |
904 | 0 | return CE_Failure; |
905 | | |
906 | | /* -------------------------------------------------------------------- */ |
907 | | /* Work out the corresponding new names. */ |
908 | | /* -------------------------------------------------------------------- */ |
909 | 0 | char **papszNewFileList = nullptr; |
910 | |
|
911 | 0 | for (int i = 0; papszFileList[i] != nullptr; i++) |
912 | 0 | { |
913 | 0 | CPLString osNewFilename; |
914 | |
|
915 | 0 | if (!EQUALN(papszFileList[i], osOldPath, osOldPath.size())) |
916 | 0 | { |
917 | 0 | CPLAssert(false); |
918 | 0 | return CE_Failure; |
919 | 0 | } |
920 | | |
921 | 0 | osNewFilename = osNewPath + (papszFileList[i] + osOldPath.size()); |
922 | |
|
923 | 0 | papszNewFileList = CSLAddString(papszNewFileList, osNewFilename); |
924 | 0 | } |
925 | | |
926 | | /* -------------------------------------------------------------------- */ |
927 | | /* Try renaming the directory. */ |
928 | | /* -------------------------------------------------------------------- */ |
929 | 0 | if (VSIRename(osNewPath, osOldPath) != 0) |
930 | 0 | { |
931 | 0 | if (VSIMkdir(osNewPath, 0777) != 0) |
932 | 0 | { |
933 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
934 | 0 | "Unable to create directory %s:\n%s", osNewPath.c_str(), |
935 | 0 | VSIStrerror(errno)); |
936 | 0 | CSLDestroy(papszNewFileList); |
937 | 0 | return CE_Failure; |
938 | 0 | } |
939 | 0 | } |
940 | | |
941 | | /* -------------------------------------------------------------------- */ |
942 | | /* Copy/rename any remaining files. */ |
943 | | /* -------------------------------------------------------------------- */ |
944 | 0 | VSIStatBufL sStatBuf; |
945 | |
|
946 | 0 | for (int i = 0; papszFileList[i] != nullptr; i++) |
947 | 0 | { |
948 | 0 | if (VSIStatL(papszFileList[i], &sStatBuf) == 0 && |
949 | 0 | VSI_ISREG(sStatBuf.st_mode)) |
950 | 0 | { |
951 | 0 | if (CPLMoveFile(papszNewFileList[i], papszFileList[i]) != 0) |
952 | 0 | { |
953 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
954 | 0 | "Unable to move %s to %s:\n%s", papszFileList[i], |
955 | 0 | papszNewFileList[i], VSIStrerror(errno)); |
956 | 0 | CSLDestroy(papszNewFileList); |
957 | 0 | return CE_Failure; |
958 | 0 | } |
959 | 0 | } |
960 | 0 | } |
961 | | |
962 | 0 | if (VSIStatL(osOldPath, &sStatBuf) == 0) |
963 | 0 | { |
964 | 0 | if (CPLUnlinkTree(osOldPath) != 0) |
965 | 0 | { |
966 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
967 | 0 | "Unable to cleanup old path."); |
968 | 0 | } |
969 | 0 | } |
970 | |
|
971 | 0 | CSLDestroy(papszFileList); |
972 | 0 | CSLDestroy(papszNewFileList); |
973 | 0 | return CE_None; |
974 | 0 | } |
975 | | |
976 | | /************************************************************************/ |
977 | | /* AIGDelete() */ |
978 | | /* */ |
979 | | /* Custom dataset deleter for AIG dataset. */ |
980 | | /************************************************************************/ |
981 | | |
982 | | static CPLErr AIGDelete(const char *pszDatasetname) |
983 | | |
984 | 0 | { |
985 | | /* -------------------------------------------------------------------- */ |
986 | | /* Get file list. */ |
987 | | /* -------------------------------------------------------------------- */ |
988 | 0 | GDALDatasetH hDS = GDALOpen(pszDatasetname, GA_ReadOnly); |
989 | 0 | if (hDS == nullptr) |
990 | 0 | return CE_Failure; |
991 | | |
992 | 0 | char **papszFileList = GDALGetFileList(hDS); |
993 | 0 | GDALClose(hDS); |
994 | |
|
995 | 0 | if (papszFileList == nullptr) |
996 | 0 | return CE_Failure; |
997 | | |
998 | | /* -------------------------------------------------------------------- */ |
999 | | /* Delete all regular files. */ |
1000 | | /* -------------------------------------------------------------------- */ |
1001 | 0 | for (int i = 0; papszFileList[i] != nullptr; i++) |
1002 | 0 | { |
1003 | 0 | VSIStatBufL sStatBuf; |
1004 | 0 | if (VSIStatL(papszFileList[i], &sStatBuf) == 0 && |
1005 | 0 | VSI_ISREG(sStatBuf.st_mode)) |
1006 | 0 | { |
1007 | 0 | if (VSIUnlink(papszFileList[i]) != 0) |
1008 | 0 | { |
1009 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1010 | 0 | "Unable to delete '%s':\n%s", papszFileList[i], |
1011 | 0 | VSIStrerror(errno)); |
1012 | 0 | return CE_Failure; |
1013 | 0 | } |
1014 | 0 | } |
1015 | 0 | } |
1016 | | |
1017 | | /* -------------------------------------------------------------------- */ |
1018 | | /* Delete directories. */ |
1019 | | /* -------------------------------------------------------------------- */ |
1020 | 0 | for (int i = 0; papszFileList[i] != nullptr; i++) |
1021 | 0 | { |
1022 | 0 | VSIStatBufL sStatBuf; |
1023 | 0 | if (VSIStatL(papszFileList[i], &sStatBuf) == 0 && |
1024 | 0 | VSI_ISDIR(sStatBuf.st_mode)) |
1025 | 0 | { |
1026 | 0 | if (CPLUnlinkTree(papszFileList[i]) != 0) |
1027 | 0 | return CE_Failure; |
1028 | 0 | } |
1029 | 0 | } |
1030 | | |
1031 | 0 | return CE_None; |
1032 | 0 | } |
1033 | | |
1034 | | /************************************************************************/ |
1035 | | /* GDALRegister_AIG() */ |
1036 | | /************************************************************************/ |
1037 | | |
1038 | | void GDALRegister_AIGrid() |
1039 | | |
1040 | 24 | { |
1041 | 24 | if (GDALGetDriverByName("AIG") != nullptr) |
1042 | 0 | return; |
1043 | | |
1044 | 24 | GDALDriver *poDriver = new GDALDriver(); |
1045 | | |
1046 | 24 | poDriver->SetDescription("AIG"); |
1047 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
1048 | 24 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Arc/Info Binary Grid"); |
1049 | 24 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/aig.html"); |
1050 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
1051 | | |
1052 | 24 | poDriver->pfnOpen = AIGDataset::Open; |
1053 | | |
1054 | 24 | poDriver->pfnRename = AIGRename; |
1055 | 24 | poDriver->pfnDelete = AIGDelete; |
1056 | | |
1057 | 24 | GetGDALDriverManager()->RegisterDriver(poDriver); |
1058 | 24 | } |