Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CTG driver
4 : * Purpose: GDALDataset driver for CTG dataset.
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdal_frmts.h"
14 : #include "gdal_pam.h"
15 : #include "ogr_spatialref.h"
16 :
17 : constexpr int HEADER_LINE_COUNT = 5;
18 :
19 : typedef struct
20 : {
21 : int nCode;
22 : const char *pszDesc;
23 : } LULCDescStruct;
24 :
25 : static const LULCDescStruct asLULCDesc[] = {
26 : {1, "Urban or Built-Up Land"},
27 : {2, "Agricultural Land"},
28 : {3, "Rangeland"},
29 : {4, "Forest Land"},
30 : {5, "Water"},
31 : {6, "Wetland"},
32 : {7, "Barren Land"},
33 : {8, "Tundra"},
34 : {9, "Perennial Snow and Ice"},
35 : {11, "Residential"},
36 : {12, "Commercial Services"},
37 : {13, "Industrial"},
38 : {14, "Transportation, Communications"},
39 : {15, "Industrial and Commercial"},
40 : {16, "Mixed Urban or Built-Up Land"},
41 : {17, "Other Urban or Built-Up Land"},
42 : {21, "Cropland and Pasture"},
43 : {22, "Orchards, Groves, Vineyards, Nurseries"},
44 : {23, "Confined Feeding Operations"},
45 : {24, "Other Agricultural Land"},
46 : {31, "Herbaceous Rangeland"},
47 : {32, "Shrub and Brush Rangeland"},
48 : {33, "Mixed Rangeland"},
49 : {41, "Deciduous Forest Land"},
50 : {42, "Evergreen Forest Land"},
51 : {43, "Mixed Forest Land"},
52 : {51, "Streams and Canals"},
53 : {52, "Lakes"},
54 : {53, "Reservoirs"},
55 : {54, "Bays and Estuaries"},
56 : {61, "Forested Wetlands"},
57 : {62, "Nonforested Wetlands"},
58 : {71, "Dry Salt Flats"},
59 : {72, "Beaches"},
60 : {73, "Sandy Areas Other than Beaches"},
61 : {74, "Bare Exposed Rock"},
62 : {75, "Strip Mines, Quarries, and Gravel Pits"},
63 : {76, "Transitional Areas"},
64 : {77, "Mixed Barren Land"},
65 : {81, "Shrub and Brush Tundra"},
66 : {82, "Herbaceous Tundra"},
67 : {83, "Bare Ground"},
68 : {84, "Wet Tundra"},
69 : {85, "Mixed Tundra"},
70 : {91, "Perennial Snowfields"},
71 : {92, "Glaciers"}};
72 :
73 : static const char *const apszBandDescription[] = {
74 : "Land Use and Land Cover",
75 : "Political units",
76 : "Census county subdivisions and SMSA tracts",
77 : "Hydrologic units",
78 : "Federal land ownership",
79 : "State land ownership"};
80 :
81 : /************************************************************************/
82 : /* ==================================================================== */
83 : /* CTGDataset */
84 : /* ==================================================================== */
85 : /************************************************************************/
86 :
87 : class CTGRasterBand;
88 :
89 : class CTGDataset final : public GDALPamDataset
90 : {
91 : friend class CTGRasterBand;
92 :
93 : VSILFILE *fp;
94 :
95 : int nNWEasting, nNWNorthing, nCellSize, nUTMZone;
96 : OGRSpatialReference m_oSRS{};
97 :
98 : int bHasReadImagery;
99 : GByte *pabyImage;
100 :
101 : int ReadImagery();
102 :
103 : static const char *ExtractField(char *szOutput, const char *pszBuffer,
104 : int nOffset, int nLength);
105 :
106 : public:
107 : CTGDataset();
108 : ~CTGDataset() override;
109 :
110 : CPLErr GetGeoTransform(double *) override;
111 :
112 1 : const OGRSpatialReference *GetSpatialRef() const override
113 : {
114 1 : return &m_oSRS;
115 : }
116 :
117 : static GDALDataset *Open(GDALOpenInfo *);
118 : static int Identify(GDALOpenInfo *);
119 : };
120 :
121 : /************************************************************************/
122 : /* ==================================================================== */
123 : /* CTGRasterBand */
124 : /* ==================================================================== */
125 : /************************************************************************/
126 :
127 : class CTGRasterBand final : public GDALPamRasterBand
128 : {
129 : friend class CTGDataset;
130 :
131 : char **papszCategories;
132 :
133 : public:
134 : CTGRasterBand(CTGDataset *, int);
135 : ~CTGRasterBand() override;
136 :
137 : CPLErr IReadBlock(int, int, void *) override;
138 : double GetNoDataValue(int *pbSuccess = nullptr) override;
139 : char **GetCategoryNames() override;
140 : };
141 :
142 : /************************************************************************/
143 : /* CTGRasterBand() */
144 : /************************************************************************/
145 :
146 18 : CTGRasterBand::CTGRasterBand(CTGDataset *poDSIn, int nBandIn)
147 18 : : papszCategories(nullptr)
148 : {
149 18 : poDS = poDSIn;
150 18 : nBand = nBandIn;
151 :
152 18 : eDataType = GDT_Int32;
153 :
154 18 : nBlockXSize = poDS->GetRasterXSize();
155 18 : nBlockYSize = poDS->GetRasterYSize();
156 18 : }
157 :
158 : /************************************************************************/
159 : /* ~CTGRasterBand() */
160 : /************************************************************************/
161 :
162 36 : CTGRasterBand::~CTGRasterBand()
163 :
164 : {
165 18 : CSLDestroy(papszCategories);
166 36 : }
167 :
168 : /************************************************************************/
169 : /* IReadBlock() */
170 : /************************************************************************/
171 :
172 1 : CPLErr CTGRasterBand::IReadBlock(int /* nBlockXOff */, int /* nBlockYOff */,
173 : void *pImage)
174 : {
175 1 : CTGDataset *poGDS = (CTGDataset *)poDS;
176 :
177 1 : poGDS->ReadImagery();
178 1 : memcpy(pImage,
179 1 : poGDS->pabyImage +
180 1 : sizeof(int) * (nBand - 1) * nBlockXSize * nBlockYSize,
181 1 : sizeof(int) * nBlockXSize * nBlockYSize);
182 :
183 1 : return CE_None;
184 : }
185 :
186 : /************************************************************************/
187 : /* GetNoDataValue() */
188 : /************************************************************************/
189 :
190 1 : double CTGRasterBand::GetNoDataValue(int *pbSuccess)
191 : {
192 1 : if (pbSuccess)
193 1 : *pbSuccess = TRUE;
194 :
195 1 : return 0.0;
196 : }
197 :
198 : /************************************************************************/
199 : /* GetCategoryNames() */
200 : /************************************************************************/
201 :
202 2 : char **CTGRasterBand::GetCategoryNames()
203 : {
204 2 : if (nBand != 1)
205 1 : return nullptr;
206 :
207 1 : if (papszCategories != nullptr)
208 0 : return papszCategories;
209 :
210 1 : int nasLULCDescSize = (int)(sizeof(asLULCDesc) / sizeof(asLULCDesc[0]));
211 1 : int nCategoriesSize = asLULCDesc[nasLULCDescSize - 1].nCode;
212 1 : papszCategories = (char **)CPLCalloc(nCategoriesSize + 2, sizeof(char *));
213 47 : for (int i = 0; i < nasLULCDescSize; i++)
214 : {
215 46 : papszCategories[asLULCDesc[i].nCode] = CPLStrdup(asLULCDesc[i].pszDesc);
216 : }
217 93 : for (int i = 0; i < nCategoriesSize; i++)
218 : {
219 92 : if (papszCategories[i] == nullptr)
220 47 : papszCategories[i] = CPLStrdup("");
221 : }
222 1 : papszCategories[nCategoriesSize + 1] = nullptr;
223 :
224 1 : return papszCategories;
225 : }
226 :
227 : /************************************************************************/
228 : /* ~CTGDataset() */
229 : /************************************************************************/
230 :
231 3 : CTGDataset::CTGDataset()
232 : : fp(nullptr), nNWEasting(0), nNWNorthing(0), nCellSize(0), nUTMZone(0),
233 3 : bHasReadImagery(FALSE), pabyImage(nullptr)
234 : {
235 3 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
236 3 : }
237 :
238 : /************************************************************************/
239 : /* ~CTGDataset() */
240 : /************************************************************************/
241 :
242 6 : CTGDataset::~CTGDataset()
243 :
244 : {
245 3 : CPLFree(pabyImage);
246 3 : if (fp != nullptr)
247 3 : VSIFCloseL(fp);
248 6 : }
249 :
250 : /************************************************************************/
251 : /* ExtractField() */
252 : /************************************************************************/
253 :
254 69 : const char *CTGDataset::ExtractField(char *szField, const char *pszBuffer,
255 : int nOffset, int nLength)
256 : {
257 69 : CPLAssert(nLength <= 10);
258 69 : memcpy(szField, pszBuffer + nOffset, nLength);
259 69 : szField[nLength] = 0;
260 69 : return szField;
261 : }
262 :
263 : /************************************************************************/
264 : /* ReadImagery() */
265 : /************************************************************************/
266 :
267 1 : int CTGDataset::ReadImagery()
268 : {
269 1 : if (bHasReadImagery)
270 0 : return TRUE;
271 :
272 1 : bHasReadImagery = TRUE;
273 :
274 : char szLine[81];
275 : char szField[11];
276 1 : szLine[80] = 0;
277 1 : int nLine = HEADER_LINE_COUNT;
278 1 : VSIFSeekL(fp, nLine * 80, SEEK_SET);
279 1 : const int nCells = nRasterXSize * nRasterYSize;
280 2 : while (VSIFReadL(szLine, 1, 80, fp) == 80)
281 : {
282 1 : const int nZone = atoi(ExtractField(szField, szLine, 0, 3));
283 1 : if (nZone != nUTMZone)
284 : {
285 0 : CPLError(CE_Failure, CPLE_AppDefined,
286 : "Read error at line %d, %s. Did not expected UTM zone %d",
287 : nLine, szLine, nZone);
288 0 : return FALSE;
289 : }
290 : const int nX =
291 1 : atoi(ExtractField(szField, szLine, 3, 8)) - nCellSize / 2;
292 : const int nY =
293 1 : atoi(ExtractField(szField, szLine, 11, 8)) + nCellSize / 2;
294 1 : const GIntBig nDiffX = static_cast<GIntBig>(nX) - nNWEasting;
295 1 : const GIntBig nDiffY = static_cast<GIntBig>(nNWNorthing) - nY;
296 1 : if (nDiffX < 0 || (nDiffX % nCellSize) != 0 || nDiffY < 0 ||
297 1 : (nDiffY % nCellSize) != 0)
298 : {
299 0 : CPLError(CE_Failure, CPLE_AppDefined,
300 : "Read error at line %d, %s. Unexpected cell coordinates",
301 : nLine, szLine);
302 0 : return FALSE;
303 : }
304 1 : const GIntBig nCellX = nDiffX / nCellSize;
305 1 : const GIntBig nCellY = nDiffY / nCellSize;
306 1 : if (nCellX >= nRasterXSize || nCellY >= nRasterYSize)
307 : {
308 0 : CPLError(CE_Failure, CPLE_AppDefined,
309 : "Read error at line %d, %s. Unexpected cell coordinates",
310 : nLine, szLine);
311 0 : return FALSE;
312 : }
313 7 : for (int i = 0; i < 6; i++)
314 : {
315 6 : int nVal = atoi(ExtractField(szField, szLine, 20 + 10 * i, 10));
316 6 : if (nVal >= 2000000000)
317 0 : nVal = 0;
318 : ((int *)
319 6 : pabyImage)[i * nCells +
320 6 : static_cast<int>(nCellY) * nRasterXSize + nCellX] =
321 : nVal;
322 : }
323 :
324 1 : nLine++;
325 : }
326 :
327 1 : return TRUE;
328 : }
329 :
330 : /************************************************************************/
331 : /* Identify() */
332 : /************************************************************************/
333 :
334 50649 : int CTGDataset::Identify(GDALOpenInfo *poOpenInfo)
335 : {
336 101294 : CPLString osFilename; // let in that scope
337 :
338 50648 : GDALOpenInfo *poOpenInfoToDelete = nullptr;
339 : /* GZipped grid_cell.gz files are common, so automagically open them */
340 : /* if the /vsigzip/ has not been explicitly passed */
341 50648 : const char *pszFilename = CPLGetFilename(poOpenInfo->pszFilename);
342 50651 : if ((EQUAL(pszFilename, "grid_cell.gz") ||
343 50650 : EQUAL(pszFilename, "grid_cell1.gz") ||
344 50650 : EQUAL(pszFilename, "grid_cell2.gz")) &&
345 1 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
346 : {
347 0 : osFilename = "/vsigzip/";
348 0 : osFilename += poOpenInfo->pszFilename;
349 0 : poOpenInfo = poOpenInfoToDelete = new GDALOpenInfo(
350 0 : osFilename.c_str(), GA_ReadOnly, poOpenInfo->GetSiblingFiles());
351 : }
352 :
353 50651 : if (poOpenInfo->nHeaderBytes < HEADER_LINE_COUNT * 80)
354 : {
355 48450 : delete poOpenInfoToDelete;
356 48444 : return FALSE;
357 : }
358 :
359 : /* -------------------------------------------------------------------- */
360 : /* Check that it looks roughly as a CTG dataset */
361 : /* -------------------------------------------------------------------- */
362 2201 : const char *pszData = (const char *)poOpenInfo->pabyHeader;
363 4766 : for (int i = 0; i < 4 * 80; i++)
364 : {
365 4759 : if (!((pszData[i] >= '0' && pszData[i] <= '9') || pszData[i] == ' ' ||
366 2196 : pszData[i] == '-'))
367 : {
368 2194 : delete poOpenInfoToDelete;
369 2194 : return FALSE;
370 : }
371 : }
372 :
373 : char szField[11];
374 7 : int nRows = atoi(ExtractField(szField, pszData, 0, 10));
375 7 : int nCols = atoi(ExtractField(szField, pszData, 20, 10));
376 7 : int nMinColIndex = atoi(ExtractField(szField, pszData + 80, 0, 5));
377 7 : int nMinRowIndex = atoi(ExtractField(szField, pszData + 80, 5, 5));
378 7 : int nMaxColIndex = atoi(ExtractField(szField, pszData + 80, 10, 5));
379 7 : int nMaxRowIndex = atoi(ExtractField(szField, pszData + 80, 15, 5));
380 :
381 7 : if (nRows <= 0 || nCols <= 0 || nMinColIndex != 1 || nMinRowIndex != 1 ||
382 6 : nMaxRowIndex != nRows || nMaxColIndex != nCols)
383 : {
384 1 : delete poOpenInfoToDelete;
385 1 : return FALSE;
386 : }
387 :
388 6 : delete poOpenInfoToDelete;
389 6 : return TRUE;
390 : }
391 :
392 : /************************************************************************/
393 : /* Open() */
394 : /************************************************************************/
395 :
396 3 : GDALDataset *CTGDataset::Open(GDALOpenInfo *poOpenInfo)
397 :
398 : {
399 3 : if (!Identify(poOpenInfo))
400 0 : return nullptr;
401 :
402 6 : CPLString osFilename(poOpenInfo->pszFilename);
403 :
404 : /* GZipped grid_cell.gz files are common, so automagically open them */
405 : /* if the /vsigzip/ has not been explicitly passed */
406 3 : const char *pszFilename = CPLGetFilename(poOpenInfo->pszFilename);
407 3 : if ((EQUAL(pszFilename, "grid_cell.gz") ||
408 3 : EQUAL(pszFilename, "grid_cell1.gz") ||
409 3 : EQUAL(pszFilename, "grid_cell2.gz")) &&
410 0 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsigzip/"))
411 : {
412 0 : osFilename = "/vsigzip/";
413 0 : osFilename += poOpenInfo->pszFilename;
414 : }
415 :
416 3 : if (poOpenInfo->eAccess == GA_Update)
417 : {
418 0 : CPLError(CE_Failure, CPLE_NotSupported,
419 : "The CTG driver does not support update access to existing"
420 : " datasets.\n");
421 0 : return nullptr;
422 : }
423 :
424 : /* -------------------------------------------------------------------- */
425 : /* Find dataset characteristics */
426 : /* -------------------------------------------------------------------- */
427 3 : VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "rb");
428 3 : if (fp == nullptr)
429 0 : return nullptr;
430 :
431 : char szHeader[HEADER_LINE_COUNT * 80 + 1];
432 3 : szHeader[HEADER_LINE_COUNT * 80] = 0;
433 3 : if (VSIFReadL(szHeader, 1, HEADER_LINE_COUNT * 80, fp) !=
434 : HEADER_LINE_COUNT * 80)
435 : {
436 0 : VSIFCloseL(fp);
437 0 : return nullptr;
438 : }
439 :
440 216 : for (int i = HEADER_LINE_COUNT * 80 - 1; i >= 0; i--)
441 : {
442 216 : if (szHeader[i] == ' ')
443 213 : szHeader[i] = 0;
444 : else
445 3 : break;
446 : }
447 :
448 : char szField[11];
449 3 : int nRows = atoi(ExtractField(szField, szHeader, 0, 10));
450 3 : int nCols = atoi(ExtractField(szField, szHeader, 20, 10));
451 :
452 : /* -------------------------------------------------------------------- */
453 : /* Create a corresponding GDALDataset. */
454 : /* -------------------------------------------------------------------- */
455 3 : CTGDataset *poDS = new CTGDataset();
456 3 : poDS->fp = fp;
457 3 : fp = nullptr;
458 3 : poDS->nRasterXSize = nCols;
459 3 : poDS->nRasterYSize = nRows;
460 :
461 3 : poDS->SetMetadataItem("TITLE", szHeader + 4 * 80);
462 :
463 3 : poDS->nCellSize = atoi(ExtractField(szField, szHeader, 35, 5));
464 3 : if (poDS->nCellSize <= 0 || poDS->nCellSize >= 10000)
465 : {
466 0 : delete poDS;
467 0 : return nullptr;
468 : }
469 3 : poDS->nNWEasting = atoi(ExtractField(szField, szHeader + 3 * 80, 40, 10));
470 3 : poDS->nNWNorthing = atoi(ExtractField(szField, szHeader + 3 * 80, 50, 10));
471 3 : poDS->nUTMZone = atoi(ExtractField(szField, szHeader, 50, 5));
472 3 : if (poDS->nUTMZone <= 0 || poDS->nUTMZone > 60)
473 : {
474 0 : delete poDS;
475 0 : return nullptr;
476 : }
477 :
478 3 : poDS->m_oSRS.importFromEPSG(32600 + poDS->nUTMZone);
479 :
480 3 : if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
481 : {
482 0 : delete poDS;
483 0 : return nullptr;
484 : }
485 :
486 : /* -------------------------------------------------------------------- */
487 : /* Read the imagery */
488 : /* -------------------------------------------------------------------- */
489 : GByte *pabyImage =
490 3 : (GByte *)VSICalloc(static_cast<size_t>(nCols) * nRows, 6 * sizeof(int));
491 3 : if (pabyImage == nullptr)
492 : {
493 0 : delete poDS;
494 0 : return nullptr;
495 : }
496 3 : poDS->pabyImage = pabyImage;
497 :
498 : /* -------------------------------------------------------------------- */
499 : /* Create band information objects. */
500 : /* -------------------------------------------------------------------- */
501 3 : poDS->nBands = 6;
502 21 : for (int i = 0; i < poDS->nBands; i++)
503 : {
504 18 : poDS->SetBand(i + 1, new CTGRasterBand(poDS, i + 1));
505 18 : poDS->GetRasterBand(i + 1)->SetDescription(apszBandDescription[i]);
506 : }
507 :
508 : /* -------------------------------------------------------------------- */
509 : /* Initialize any PAM information. */
510 : /* -------------------------------------------------------------------- */
511 3 : poDS->SetDescription(poOpenInfo->pszFilename);
512 3 : poDS->TryLoadXML();
513 :
514 : /* -------------------------------------------------------------------- */
515 : /* Support overviews. */
516 : /* -------------------------------------------------------------------- */
517 3 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
518 :
519 3 : return poDS;
520 : }
521 :
522 : /************************************************************************/
523 : /* GetGeoTransform() */
524 : /************************************************************************/
525 :
526 1 : CPLErr CTGDataset::GetGeoTransform(double *padfTransform)
527 :
528 : {
529 1 : padfTransform[0] = static_cast<double>(nNWEasting) - nCellSize / 2;
530 1 : padfTransform[1] = nCellSize;
531 1 : padfTransform[2] = 0;
532 1 : padfTransform[3] = static_cast<double>(nNWNorthing) + nCellSize / 2;
533 1 : padfTransform[4] = 0.;
534 1 : padfTransform[5] = -nCellSize;
535 :
536 1 : return CE_None;
537 : }
538 :
539 : /************************************************************************/
540 : /* GDALRegister_CTG() */
541 : /************************************************************************/
542 :
543 1595 : void GDALRegister_CTG()
544 :
545 : {
546 1595 : if (GDALGetDriverByName("CTG") != nullptr)
547 302 : return;
548 :
549 1293 : GDALDriver *poDriver = new GDALDriver();
550 :
551 1293 : poDriver->SetDescription("CTG");
552 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
553 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
554 1293 : "USGS LULC Composite Theme Grid");
555 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ctg.html");
556 :
557 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
558 :
559 1293 : poDriver->pfnOpen = CTGDataset::Open;
560 1293 : poDriver->pfnIdentify = CTGDataset::Identify;
561 :
562 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
563 : }
|