/src/gdal/frmts/northwood/grcdataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GRC Reader |
4 | | * Purpose: GDAL driver for Northwood Classified Format |
5 | | * Author: Perry Casson |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2007, Waypoint Information Technology |
9 | | * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "gdal_frmts.h" |
15 | | #include "gdal_pam.h" |
16 | | #include "gdal_colortable.h" |
17 | | #include "gdal_driver.h" |
18 | | #include "gdal_drivermanager.h" |
19 | | #include "gdal_openinfo.h" |
20 | | #include "gdal_cpp_functions.h" |
21 | | #include "northwood.h" |
22 | | #include "ogrmitabspatialref.h" |
23 | | |
24 | | /************************************************************************/ |
25 | | /* ==================================================================== */ |
26 | | /* NWT_GRCDataset */ |
27 | | /* ==================================================================== */ |
28 | | /************************************************************************/ |
29 | | class NWT_GRCRasterBand; |
30 | | |
31 | | class NWT_GRCDataset final : public GDALPamDataset |
32 | | { |
33 | | friend class NWT_GRCRasterBand; |
34 | | |
35 | | private: |
36 | | VSILFILE *fp; |
37 | | GByte abyHeader[1024]; |
38 | | NWT_GRID *pGrd; |
39 | | char **papszCategories; |
40 | | mutable OGRSpatialReference m_oSRS{}; |
41 | | |
42 | | NWT_GRCDataset(const NWT_GRCDataset &) = delete; |
43 | | NWT_GRCDataset &operator=(const NWT_GRCDataset &) = delete; |
44 | | |
45 | | protected: |
46 | | GDALColorTable *poColorTable; |
47 | | |
48 | | public: |
49 | | NWT_GRCDataset(); |
50 | | ~NWT_GRCDataset() override; |
51 | | |
52 | | static GDALDataset *Open(GDALOpenInfo *); |
53 | | static int Identify(GDALOpenInfo *poOpenInfo); |
54 | | |
55 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
56 | | const OGRSpatialReference *GetSpatialRef() const override; |
57 | | }; |
58 | | |
59 | | /************************************************************************/ |
60 | | /* ==================================================================== */ |
61 | | /* NWT_GRCRasterBand */ |
62 | | /* ==================================================================== */ |
63 | | /************************************************************************/ |
64 | | |
65 | | class NWT_GRCRasterBand final : public GDALPamRasterBand |
66 | | { |
67 | | friend class NWT_GRCDataset; |
68 | | |
69 | | public: |
70 | | NWT_GRCRasterBand(NWT_GRCDataset *, int); |
71 | | ~NWT_GRCRasterBand() override; |
72 | | |
73 | | CPLErr IReadBlock(int, int, void *) override; |
74 | | double GetNoDataValue(int *pbSuccess) override; |
75 | | |
76 | | GDALColorInterp GetColorInterpretation() override; |
77 | | char **GetCategoryNames() override; |
78 | | GDALColorTable *GetColorTable() override; |
79 | | }; |
80 | | |
81 | | /************************************************************************/ |
82 | | /* NWT_GRCRasterBand() */ |
83 | | /************************************************************************/ |
84 | | |
85 | | NWT_GRCRasterBand::NWT_GRCRasterBand(NWT_GRCDataset *poDSIn, int nBandIn) |
86 | 2 | { |
87 | 2 | poDS = poDSIn; |
88 | 2 | nBand = nBandIn; |
89 | 2 | NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS); |
90 | | |
91 | 2 | if (poGDS->pGrd->nBitsPerPixel == 8) |
92 | 2 | eDataType = GDT_UInt8; |
93 | 0 | else if (poGDS->pGrd->nBitsPerPixel == 16) |
94 | 0 | eDataType = GDT_UInt16; |
95 | 0 | else /* if( poGDS->pGrd->nBitsPerPixel == 32 ) */ |
96 | 0 | eDataType = GDT_UInt32; // this would be funny |
97 | | |
98 | 2 | nBlockXSize = poDS->GetRasterXSize(); |
99 | 2 | nBlockYSize = 1; |
100 | | |
101 | | // load the color table and might as well to the ClassNames |
102 | 2 | poGDS->poColorTable = new GDALColorTable(); |
103 | | |
104 | 2 | GDALColorEntry oEntry = {255, 255, 255, 0}; |
105 | | // null value = 0 is transparent |
106 | | // alpha 0 = transparent |
107 | | |
108 | 2 | poGDS->poColorTable->SetColorEntry(0, &oEntry); |
109 | | |
110 | 2 | for (int i = 0; |
111 | 8 | i < static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems); |
112 | 6 | i++) |
113 | 6 | { |
114 | 6 | oEntry.c1 = poGDS->pGrd->stClassDict->stClassifiedItem[i]->r; |
115 | 6 | oEntry.c2 = poGDS->pGrd->stClassDict->stClassifiedItem[i]->g; |
116 | 6 | oEntry.c3 = poGDS->pGrd->stClassDict->stClassifiedItem[i]->b; |
117 | 6 | oEntry.c4 = 255; // alpha 255 = solid |
118 | | |
119 | 6 | poGDS->poColorTable->SetColorEntry( |
120 | 6 | poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal, &oEntry); |
121 | 6 | } |
122 | | |
123 | | // find the max value used in the grc |
124 | 2 | int maxValue = 0; |
125 | 2 | for (int i = 0; |
126 | 8 | i < static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems); |
127 | 6 | i++) |
128 | 6 | { |
129 | 6 | if (poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal > maxValue) |
130 | 6 | maxValue = poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal; |
131 | 6 | } |
132 | | |
133 | | // load a value for the null value |
134 | 2 | poGDS->papszCategories = CSLAddString(poGDS->papszCategories, "No Data"); |
135 | | |
136 | | // for the class names we need to load nulls string for all classes that |
137 | | // are not defined |
138 | 8 | for (int val = 1; val <= maxValue; val++) |
139 | 6 | { |
140 | 6 | int i = 0; |
141 | | // Loop through the GRC dictionary to see if the value is defined. |
142 | 12 | for (; i < |
143 | 12 | static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems); |
144 | 6 | i++) |
145 | 12 | { |
146 | 12 | if (static_cast<int>( |
147 | 12 | poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal) == |
148 | 12 | val) |
149 | 6 | { |
150 | 6 | poGDS->papszCategories = CSLAddString( |
151 | 6 | poGDS->papszCategories, |
152 | 6 | poGDS->pGrd->stClassDict->stClassifiedItem[i]->szClassName); |
153 | 6 | break; |
154 | 6 | } |
155 | 12 | } |
156 | 6 | if (i >= |
157 | 6 | static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems)) |
158 | 0 | poGDS->papszCategories = CSLAddString(poGDS->papszCategories, ""); |
159 | 6 | } |
160 | 2 | } |
161 | | |
162 | | NWT_GRCRasterBand::~NWT_GRCRasterBand() |
163 | 2 | { |
164 | 2 | } |
165 | | |
166 | | double NWT_GRCRasterBand::GetNoDataValue(int *pbSuccess) |
167 | 8 | { |
168 | 8 | if (pbSuccess != nullptr) |
169 | 6 | *pbSuccess = TRUE; |
170 | | |
171 | 8 | return 0.0; // Northwood grid 0 is always null. |
172 | 8 | } |
173 | | |
174 | | // return an array of null terminated strings for the class names |
175 | | char **NWT_GRCRasterBand::GetCategoryNames() |
176 | 0 | { |
177 | 0 | NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS); |
178 | |
|
179 | 0 | return poGDS->papszCategories; |
180 | 0 | } |
181 | | |
182 | | // return the color table |
183 | | GDALColorTable *NWT_GRCRasterBand::GetColorTable() |
184 | 0 | { |
185 | 0 | NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS); |
186 | |
|
187 | 0 | return poGDS->poColorTable; |
188 | 0 | } |
189 | | |
190 | | GDALColorInterp NWT_GRCRasterBand::GetColorInterpretation() |
191 | 0 | { |
192 | 0 | if (nBand == 1) |
193 | 0 | return GCI_PaletteIndex; |
194 | | |
195 | 0 | return GCI_Undefined; |
196 | 0 | } |
197 | | |
198 | | /************************************************************************/ |
199 | | /* IReadBlock() */ |
200 | | /************************************************************************/ |
201 | | CPLErr NWT_GRCRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, |
202 | | void *pImage) |
203 | 362 | { |
204 | 362 | NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS); |
205 | 362 | const int nBytesPerPixel = poGDS->pGrd->nBitsPerPixel / 8; |
206 | 362 | if (nBytesPerPixel <= 0 || nBlockXSize > INT_MAX / nBytesPerPixel) |
207 | 0 | return CE_Failure; |
208 | 362 | const int nRecordSize = nBlockXSize * nBytesPerPixel; |
209 | | |
210 | 362 | if (nBand == 1) |
211 | 362 | { // grc's are just one band of indices |
212 | 362 | VSIFSeekL(poGDS->fp, |
213 | 362 | 1024 + nRecordSize * static_cast<vsi_l_offset>(nBlockYOff), |
214 | 362 | SEEK_SET); |
215 | 362 | if (static_cast<int>(VSIFReadL(pImage, 1, nRecordSize, poGDS->fp)) != |
216 | 362 | nRecordSize) |
217 | 0 | return CE_Failure; |
218 | 362 | } |
219 | 0 | else |
220 | 0 | { |
221 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, "No band number %d", nBand); |
222 | 0 | return CE_Failure; |
223 | 0 | } |
224 | 362 | return CE_None; |
225 | 362 | } |
226 | | |
227 | | /************************************************************************/ |
228 | | /* ==================================================================== */ |
229 | | /* NWT_GRCDataset */ |
230 | | /* ==================================================================== */ |
231 | | /************************************************************************/ |
232 | | NWT_GRCDataset::NWT_GRCDataset() |
233 | 8 | : fp(nullptr), pGrd(nullptr), papszCategories(nullptr), |
234 | 8 | poColorTable(nullptr) |
235 | 8 | { |
236 | 8 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
237 | 8 | memset(abyHeader, 0, sizeof(abyHeader)); |
238 | 8 | } |
239 | | |
240 | | /************************************************************************/ |
241 | | /* ~NWT_GRCDataset() */ |
242 | | /************************************************************************/ |
243 | | NWT_GRCDataset::~NWT_GRCDataset() |
244 | 8 | { |
245 | 8 | delete poColorTable; |
246 | 8 | CSLDestroy(papszCategories); |
247 | | |
248 | 8 | NWT_GRCDataset::FlushCache(true); |
249 | 8 | if (pGrd) |
250 | 8 | { |
251 | 8 | pGrd->fp = nullptr; // this prevents nwtCloseGrid from closing the fp |
252 | 8 | nwtCloseGrid(pGrd); |
253 | 8 | } |
254 | | |
255 | 8 | if (fp != nullptr) |
256 | 8 | VSIFCloseL(fp); |
257 | 8 | } |
258 | | |
259 | | /************************************************************************/ |
260 | | /* GetGeoTransform() */ |
261 | | /************************************************************************/ |
262 | | CPLErr NWT_GRCDataset::GetGeoTransform(GDALGeoTransform >) const |
263 | 2 | { |
264 | 2 | gt.xorig = pGrd->dfMinX - (pGrd->dfStepSize * 0.5); |
265 | 2 | gt.yorig = pGrd->dfMaxY + (pGrd->dfStepSize * 0.5); |
266 | 2 | gt.xscale = pGrd->dfStepSize; |
267 | 2 | gt.xrot = 0.0; |
268 | | |
269 | 2 | gt.yrot = 0.0; |
270 | 2 | gt.yscale = -1 * pGrd->dfStepSize; |
271 | | |
272 | 2 | return CE_None; |
273 | 2 | } |
274 | | |
275 | | /************************************************************************/ |
276 | | /* GetSpatialRef() */ |
277 | | /************************************************************************/ |
278 | | const OGRSpatialReference *NWT_GRCDataset::GetSpatialRef() const |
279 | 2 | { |
280 | 2 | if (m_oSRS.IsEmpty()) |
281 | 2 | { |
282 | 2 | OGRSpatialReference *poSpatialRef = |
283 | 2 | MITABCoordSys2SpatialRef(pGrd->cMICoordSys); |
284 | 2 | if (poSpatialRef) |
285 | 2 | { |
286 | 2 | m_oSRS = *poSpatialRef; |
287 | 2 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
288 | 2 | poSpatialRef->Release(); |
289 | 2 | } |
290 | 2 | } |
291 | 2 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
292 | 2 | } |
293 | | |
294 | | /************************************************************************/ |
295 | | /* Identify() */ |
296 | | /************************************************************************/ |
297 | | |
298 | | int NWT_GRCDataset::Identify(GDALOpenInfo *poOpenInfo) |
299 | 487k | { |
300 | | /* -------------------------------------------------------------------- */ |
301 | | /* Look for the header */ |
302 | | /* -------------------------------------------------------------------- */ |
303 | 487k | if (poOpenInfo->nHeaderBytes < 1024) |
304 | 429k | return FALSE; |
305 | | |
306 | 58.3k | if (poOpenInfo->pabyHeader[0] != 'H' || poOpenInfo->pabyHeader[1] != 'G' || |
307 | 17 | poOpenInfo->pabyHeader[2] != 'P' || poOpenInfo->pabyHeader[3] != 'C' || |
308 | 16 | poOpenInfo->pabyHeader[4] != '8') |
309 | 58.2k | return FALSE; |
310 | | |
311 | 16 | return TRUE; |
312 | 58.3k | } |
313 | | |
314 | | /************************************************************************/ |
315 | | /* Open() */ |
316 | | /************************************************************************/ |
317 | | |
318 | | GDALDataset *NWT_GRCDataset::Open(GDALOpenInfo *poOpenInfo) |
319 | 8 | { |
320 | 8 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
321 | 0 | return nullptr; |
322 | | |
323 | | /* -------------------------------------------------------------------- */ |
324 | | /* Create a corresponding GDALDataset. */ |
325 | | /* -------------------------------------------------------------------- */ |
326 | 8 | NWT_GRCDataset *poDS = new NWT_GRCDataset(); |
327 | | |
328 | 8 | poDS->fp = poOpenInfo->fpL; |
329 | 8 | poOpenInfo->fpL = nullptr; |
330 | | |
331 | | /* -------------------------------------------------------------------- */ |
332 | | /* Read the header. */ |
333 | | /* -------------------------------------------------------------------- */ |
334 | 8 | VSIFSeekL(poDS->fp, 0, SEEK_SET); |
335 | 8 | VSIFReadL(poDS->abyHeader, 1, 1024, poDS->fp); |
336 | 8 | poDS->pGrd = static_cast<NWT_GRID *>(malloc(sizeof(NWT_GRID))); |
337 | 8 | if (!poDS->pGrd) |
338 | 0 | { |
339 | 0 | delete poDS; |
340 | 0 | return nullptr; |
341 | 0 | } |
342 | | |
343 | 8 | poDS->pGrd->fp = poDS->fp; |
344 | | |
345 | 8 | if (!nwt_ParseHeader(poDS->pGrd, poDS->abyHeader) || |
346 | 4 | !GDALCheckDatasetDimensions(poDS->pGrd->nXSide, poDS->pGrd->nYSide) || |
347 | 4 | poDS->pGrd->stClassDict == nullptr) |
348 | 5 | { |
349 | 5 | delete poDS; |
350 | 5 | return nullptr; |
351 | 5 | } |
352 | | |
353 | 3 | if (poDS->pGrd->nBitsPerPixel != 8 && poDS->pGrd->nBitsPerPixel != 16 && |
354 | 1 | poDS->pGrd->nBitsPerPixel != 32) |
355 | 1 | { |
356 | 1 | delete poDS; |
357 | 1 | return nullptr; |
358 | 1 | } |
359 | | |
360 | 2 | poDS->nRasterXSize = poDS->pGrd->nXSide; |
361 | 2 | poDS->nRasterYSize = poDS->pGrd->nYSide; |
362 | | |
363 | | /* -------------------------------------------------------------------- */ |
364 | | /* Create band information objects. */ |
365 | | /* -------------------------------------------------------------------- */ |
366 | 2 | poDS->SetBand(1, new NWT_GRCRasterBand(poDS, 1)); // Class Indexes |
367 | | |
368 | | /* -------------------------------------------------------------------- */ |
369 | | /* Initialize any PAM information. */ |
370 | | /* -------------------------------------------------------------------- */ |
371 | 2 | poDS->SetDescription(poOpenInfo->pszFilename); |
372 | 2 | poDS->TryLoadXML(); |
373 | | |
374 | | /* -------------------------------------------------------------------- */ |
375 | | /* Check for external overviews. */ |
376 | | /* -------------------------------------------------------------------- */ |
377 | 2 | poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename, |
378 | 2 | poOpenInfo->GetSiblingFiles()); |
379 | | |
380 | 2 | return poDS; |
381 | 3 | } |
382 | | |
383 | | /************************************************************************/ |
384 | | /* GDALRegister_GRC() */ |
385 | | /************************************************************************/ |
386 | | |
387 | | void GDALRegister_NWT_GRC() |
388 | | |
389 | 24 | { |
390 | 24 | if (GDALGetDriverByName("NWT_GRC") != nullptr) |
391 | 0 | return; |
392 | | |
393 | 24 | GDALDriver *poDriver = new GDALDriver(); |
394 | | |
395 | 24 | poDriver->SetDescription("NWT_GRC"); |
396 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
397 | 24 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, |
398 | 24 | "Northwood Classified Grid Format .grc/.tab"); |
399 | 24 | poDriver->SetMetadataItem( |
400 | 24 | GDAL_DMD_HELPTOPIC, |
401 | 24 | "drivers/raster/nwtgrd.html#driver-capabilities-nwt-grc"); |
402 | 24 | poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "grc"); |
403 | 24 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
404 | | |
405 | 24 | poDriver->pfnOpen = NWT_GRCDataset::Open; |
406 | 24 | poDriver->pfnIdentify = NWT_GRCDataset::Identify; |
407 | | |
408 | 24 | GetGDALDriverManager()->RegisterDriver(poDriver); |
409 | 24 | } |