/src/gdal/frmts/raw/ndfdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: NDF Driver |
4 | | * Purpose: Implementation of NLAPS Data Format read support. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2005, Frank Warmerdam |
9 | | * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_string.h" |
15 | | #include "gdal_frmts.h" |
16 | | #include "ogr_spatialref.h" |
17 | | #include "rawdataset.h" |
18 | | |
19 | | /************************************************************************/ |
20 | | /* ==================================================================== */ |
21 | | /* NDFDataset */ |
22 | | /* ==================================================================== */ |
23 | | /************************************************************************/ |
24 | | |
25 | | class NDFDataset final : public RawDataset |
26 | | { |
27 | | double adfGeoTransform[6]; |
28 | | |
29 | | OGRSpatialReference m_oSRS{}; |
30 | | char **papszExtraFiles; |
31 | | |
32 | | char **papszHeader; |
33 | | const char *Get(const char *pszKey, const char *pszDefault); |
34 | | |
35 | | CPL_DISALLOW_COPY_ASSIGN(NDFDataset) |
36 | | |
37 | | CPLErr Close() override; |
38 | | |
39 | | public: |
40 | | NDFDataset(); |
41 | | ~NDFDataset() override; |
42 | | |
43 | | CPLErr GetGeoTransform(double *padfTransform) override; |
44 | | |
45 | | const OGRSpatialReference *GetSpatialRef() const override |
46 | 0 | { |
47 | 0 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
48 | 0 | } |
49 | | |
50 | | char **GetFileList(void) override; |
51 | | |
52 | | static GDALDataset *Open(GDALOpenInfo *); |
53 | | static int Identify(GDALOpenInfo *); |
54 | | }; |
55 | | |
56 | | /************************************************************************/ |
57 | | /* NDFDataset() */ |
58 | | /************************************************************************/ |
59 | | |
60 | 0 | NDFDataset::NDFDataset() : papszExtraFiles(nullptr), papszHeader(nullptr) |
61 | 0 | { |
62 | 0 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
63 | 0 | adfGeoTransform[0] = 0.0; |
64 | 0 | adfGeoTransform[1] = 1.0; |
65 | 0 | adfGeoTransform[2] = 0.0; |
66 | 0 | adfGeoTransform[3] = 0.0; |
67 | 0 | adfGeoTransform[4] = 0.0; |
68 | 0 | adfGeoTransform[5] = 1.0; |
69 | 0 | } |
70 | | |
71 | | /************************************************************************/ |
72 | | /* ~NDFDataset() */ |
73 | | /************************************************************************/ |
74 | | |
75 | | NDFDataset::~NDFDataset() |
76 | | |
77 | 0 | { |
78 | 0 | NDFDataset::Close(); |
79 | 0 | } |
80 | | |
81 | | /************************************************************************/ |
82 | | /* Close() */ |
83 | | /************************************************************************/ |
84 | | |
85 | | CPLErr NDFDataset::Close() |
86 | 0 | { |
87 | 0 | CPLErr eErr = CE_None; |
88 | 0 | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
89 | 0 | { |
90 | 0 | if (NDFDataset::FlushCache(true) != CE_None) |
91 | 0 | eErr = CE_Failure; |
92 | |
|
93 | 0 | CSLDestroy(papszHeader); |
94 | 0 | CSLDestroy(papszExtraFiles); |
95 | |
|
96 | 0 | if (GDALPamDataset::Close() != CE_None) |
97 | 0 | eErr = CE_Failure; |
98 | 0 | } |
99 | 0 | return eErr; |
100 | 0 | } |
101 | | |
102 | | /************************************************************************/ |
103 | | /* GetGeoTransform() */ |
104 | | /************************************************************************/ |
105 | | |
106 | | CPLErr NDFDataset::GetGeoTransform(double *padfTransform) |
107 | | |
108 | 0 | { |
109 | 0 | memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6); |
110 | 0 | return CE_None; |
111 | 0 | } |
112 | | |
113 | | /************************************************************************/ |
114 | | /* Get() */ |
115 | | /* */ |
116 | | /* Fetch a value from the header by keyword. */ |
117 | | /************************************************************************/ |
118 | | |
119 | | const char *NDFDataset::Get(const char *pszKey, const char *pszDefault) |
120 | | |
121 | 0 | { |
122 | 0 | const char *pszResult = CSLFetchNameValue(papszHeader, pszKey); |
123 | |
|
124 | 0 | if (pszResult == nullptr) |
125 | 0 | return pszDefault; |
126 | | |
127 | 0 | return pszResult; |
128 | 0 | } |
129 | | |
130 | | /************************************************************************/ |
131 | | /* GetFileList() */ |
132 | | /************************************************************************/ |
133 | | |
134 | | char **NDFDataset::GetFileList() |
135 | | |
136 | 0 | { |
137 | | // Main data file, etc. |
138 | 0 | char **papszFileList = GDALPamDataset::GetFileList(); |
139 | | |
140 | | // Header file. |
141 | 0 | papszFileList = CSLInsertStrings(papszFileList, -1, papszExtraFiles); |
142 | |
|
143 | 0 | return papszFileList; |
144 | 0 | } |
145 | | |
146 | | /************************************************************************/ |
147 | | /* Identify() */ |
148 | | /************************************************************************/ |
149 | | |
150 | | int NDFDataset::Identify(GDALOpenInfo *poOpenInfo) |
151 | | |
152 | 475k | { |
153 | | /* -------------------------------------------------------------------- */ |
154 | | /* The user must select the header file (i.e. .H1). */ |
155 | | /* -------------------------------------------------------------------- */ |
156 | 475k | return poOpenInfo->nHeaderBytes >= 50 && |
157 | 475k | (STARTS_WITH_CI( |
158 | 1.61k | reinterpret_cast<const char *>(poOpenInfo->pabyHeader), |
159 | 1.61k | "NDF_REVISION=2") || |
160 | 1.61k | STARTS_WITH_CI( |
161 | 1.61k | reinterpret_cast<const char *>(poOpenInfo->pabyHeader), |
162 | 1.61k | "NDF_REVISION=0")); |
163 | 475k | } |
164 | | |
165 | | /************************************************************************/ |
166 | | /* Open() */ |
167 | | /************************************************************************/ |
168 | | |
169 | | GDALDataset *NDFDataset::Open(GDALOpenInfo *poOpenInfo) |
170 | | |
171 | 0 | { |
172 | | /* -------------------------------------------------------------------- */ |
173 | | /* The user must select the header file (i.e. .H1). */ |
174 | | /* -------------------------------------------------------------------- */ |
175 | 0 | if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr) |
176 | 0 | return nullptr; |
177 | | |
178 | | /* -------------------------------------------------------------------- */ |
179 | | /* Confirm the requested access is supported. */ |
180 | | /* -------------------------------------------------------------------- */ |
181 | 0 | if (poOpenInfo->eAccess == GA_Update) |
182 | 0 | { |
183 | 0 | ReportUpdateNotSupportedByDriver("NDF"); |
184 | 0 | return nullptr; |
185 | 0 | } |
186 | | /* -------------------------------------------------------------------- */ |
187 | | /* Read and process the header into a local name/value */ |
188 | | /* stringlist. We just take off the trailing semicolon. The */ |
189 | | /* keyword is already separated from the value by an equal */ |
190 | | /* sign. */ |
191 | | /* -------------------------------------------------------------------- */ |
192 | | |
193 | 0 | const char *pszLine = nullptr; |
194 | 0 | const int nHeaderMax = 1000; |
195 | 0 | int nHeaderLines = 0; |
196 | 0 | char **papszHeader = |
197 | 0 | static_cast<char **>(CPLMalloc(sizeof(char *) * (nHeaderMax + 1))); |
198 | |
|
199 | 0 | while (nHeaderLines < nHeaderMax && |
200 | 0 | (pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr && |
201 | 0 | !EQUAL(pszLine, "END_OF_HDR;")) |
202 | 0 | { |
203 | 0 | if (strstr(pszLine, "=") == nullptr) |
204 | 0 | break; |
205 | | |
206 | 0 | char *pszFixed = CPLStrdup(pszLine); |
207 | 0 | if (pszFixed[strlen(pszFixed) - 1] == ';') |
208 | 0 | pszFixed[strlen(pszFixed) - 1] = '\0'; |
209 | |
|
210 | 0 | papszHeader[nHeaderLines++] = pszFixed; |
211 | 0 | papszHeader[nHeaderLines] = nullptr; |
212 | 0 | } |
213 | 0 | CPL_IGNORE_RET_VAL(VSIFCloseL(poOpenInfo->fpL)); |
214 | 0 | poOpenInfo->fpL = nullptr; |
215 | |
|
216 | 0 | if (CSLFetchNameValue(papszHeader, "PIXELS_PER_LINE") == nullptr || |
217 | 0 | CSLFetchNameValue(papszHeader, "LINES_PER_DATA_FILE") == nullptr || |
218 | 0 | CSLFetchNameValue(papszHeader, "BITS_PER_PIXEL") == nullptr || |
219 | 0 | CSLFetchNameValue(papszHeader, "PIXEL_FORMAT") == nullptr) |
220 | 0 | { |
221 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
222 | 0 | "Dataset appears to be NDF but is missing a required field."); |
223 | 0 | CSLDestroy(papszHeader); |
224 | 0 | return nullptr; |
225 | 0 | } |
226 | | |
227 | 0 | if (!EQUAL(CSLFetchNameValue(papszHeader, "PIXEL_FORMAT"), "BYTE") || |
228 | 0 | !EQUAL(CSLFetchNameValue(papszHeader, "BITS_PER_PIXEL"), "8")) |
229 | 0 | { |
230 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
231 | 0 | "Currently NDF driver supports only 8bit BYTE format."); |
232 | 0 | CSLDestroy(papszHeader); |
233 | 0 | return nullptr; |
234 | 0 | } |
235 | | |
236 | | /* -------------------------------------------------------------------- */ |
237 | | /* Confirm the requested access is supported. */ |
238 | | /* -------------------------------------------------------------------- */ |
239 | 0 | if (poOpenInfo->eAccess == GA_Update) |
240 | 0 | { |
241 | 0 | CSLDestroy(papszHeader); |
242 | 0 | ReportUpdateNotSupportedByDriver("NDF"); |
243 | 0 | return nullptr; |
244 | 0 | } |
245 | | |
246 | | /* -------------------------------------------------------------------- */ |
247 | | /* Create a corresponding GDALDataset. */ |
248 | | /* -------------------------------------------------------------------- */ |
249 | 0 | auto poDS = std::make_unique<NDFDataset>(); |
250 | 0 | poDS->papszHeader = papszHeader; |
251 | |
|
252 | 0 | poDS->nRasterXSize = atoi(poDS->Get("PIXELS_PER_LINE", "")); |
253 | 0 | poDS->nRasterYSize = atoi(poDS->Get("LINES_PER_DATA_FILE", "")); |
254 | | |
255 | | /* -------------------------------------------------------------------- */ |
256 | | /* Create a raw raster band for each file. */ |
257 | | /* -------------------------------------------------------------------- */ |
258 | 0 | const char *pszBand = |
259 | 0 | CSLFetchNameValue(papszHeader, "NUMBER_OF_BANDS_IN_VOLUME"); |
260 | 0 | if (pszBand == nullptr) |
261 | 0 | { |
262 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find band count"); |
263 | 0 | return nullptr; |
264 | 0 | } |
265 | 0 | const int nBands = atoi(pszBand); |
266 | |
|
267 | 0 | if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) || |
268 | 0 | !GDALCheckBandCount(nBands, FALSE)) |
269 | 0 | { |
270 | 0 | return nullptr; |
271 | 0 | } |
272 | | |
273 | 0 | for (int iBand = 0; iBand < nBands; iBand++) |
274 | 0 | { |
275 | 0 | char szKey[100]; |
276 | 0 | snprintf(szKey, sizeof(szKey), "BAND%d_FILENAME", iBand + 1); |
277 | 0 | CPLString osFilename = poDS->Get(szKey, ""); |
278 | | |
279 | | // NDF1 file do not include the band filenames. |
280 | 0 | if (osFilename.empty()) |
281 | 0 | { |
282 | 0 | char szBandExtension[15]; |
283 | 0 | snprintf(szBandExtension, sizeof(szBandExtension), "I%d", |
284 | 0 | iBand + 1); |
285 | 0 | osFilename = |
286 | 0 | CPLResetExtensionSafe(poOpenInfo->pszFilename, szBandExtension); |
287 | 0 | } |
288 | 0 | else |
289 | 0 | { |
290 | 0 | CPLString osBasePath = CPLGetPathSafe(poOpenInfo->pszFilename); |
291 | 0 | osFilename = CPLFormFilenameSafe(osBasePath, osFilename, nullptr); |
292 | 0 | } |
293 | |
|
294 | 0 | VSILFILE *fpRaw = VSIFOpenL(osFilename, "rb"); |
295 | 0 | if (fpRaw == nullptr) |
296 | 0 | { |
297 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
298 | 0 | "Failed to open band file: %s", osFilename.c_str()); |
299 | 0 | return nullptr; |
300 | 0 | } |
301 | 0 | poDS->papszExtraFiles = CSLAddString(poDS->papszExtraFiles, osFilename); |
302 | |
|
303 | 0 | auto poBand = RawRasterBand::Create( |
304 | 0 | poDS.get(), iBand + 1, fpRaw, 0, 1, poDS->nRasterXSize, GDT_Byte, |
305 | 0 | RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN, |
306 | 0 | RawRasterBand::OwnFP::YES); |
307 | 0 | if (!poBand) |
308 | 0 | return nullptr; |
309 | | |
310 | 0 | snprintf(szKey, sizeof(szKey), "BAND%d_NAME", iBand + 1); |
311 | 0 | poBand->SetDescription(poDS->Get(szKey, "")); |
312 | |
|
313 | 0 | snprintf(szKey, sizeof(szKey), "BAND%d_WAVELENGTHS", iBand + 1); |
314 | 0 | poBand->SetMetadataItem("WAVELENGTHS", poDS->Get(szKey, "")); |
315 | |
|
316 | 0 | snprintf(szKey, sizeof(szKey), "BAND%d_RADIOMETRIC_GAINS/BIAS", |
317 | 0 | iBand + 1); |
318 | 0 | poBand->SetMetadataItem("RADIOMETRIC_GAINS_BIAS", poDS->Get(szKey, "")); |
319 | |
|
320 | 0 | poDS->SetBand(iBand + 1, std::move(poBand)); |
321 | 0 | } |
322 | | |
323 | | /* -------------------------------------------------------------------- */ |
324 | | /* Fetch and parse USGS projection parameters. */ |
325 | | /* -------------------------------------------------------------------- */ |
326 | 0 | double adfUSGSParams[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
327 | 0 | const CPLStringList aosParamTokens(CSLTokenizeStringComplex( |
328 | 0 | poDS->Get("USGS_PROJECTION_NUMBER", ""), ",", FALSE, TRUE)); |
329 | |
|
330 | 0 | if (aosParamTokens.size() >= 15) |
331 | 0 | { |
332 | 0 | for (int i = 0; i < 15; i++) |
333 | 0 | adfUSGSParams[i] = CPLAtof(aosParamTokens[i]); |
334 | 0 | } |
335 | | |
336 | | /* -------------------------------------------------------------------- */ |
337 | | /* Minimal georef support ... should add full USGS style */ |
338 | | /* support at some point. */ |
339 | | /* -------------------------------------------------------------------- */ |
340 | 0 | const int nUSGSProjection = atoi(poDS->Get("USGS_PROJECTION_NUMBER", "")); |
341 | 0 | const int nZone = atoi(poDS->Get("USGS_MAP_ZONE", "0")); |
342 | |
|
343 | 0 | OGRSpatialReference oSRS; |
344 | 0 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
345 | 0 | oSRS.importFromUSGS(nUSGSProjection, nZone, adfUSGSParams, 12); |
346 | |
|
347 | 0 | const CPLString osDatum = poDS->Get("HORIZONTAL_DATUM", ""); |
348 | 0 | if (EQUAL(osDatum, "WGS84") || EQUAL(osDatum, "NAD83") || |
349 | 0 | EQUAL(osDatum, "NAD27")) |
350 | 0 | { |
351 | 0 | oSRS.SetWellKnownGeogCS(osDatum); |
352 | 0 | } |
353 | 0 | else if (STARTS_WITH_CI(osDatum, "NAD27")) |
354 | 0 | { |
355 | 0 | oSRS.SetWellKnownGeogCS("NAD27"); |
356 | 0 | } |
357 | 0 | else |
358 | 0 | { |
359 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
360 | 0 | "Unrecognized datum name in NLAPS/NDF file:%s, " |
361 | 0 | "assuming WGS84.", |
362 | 0 | osDatum.c_str()); |
363 | 0 | oSRS.SetWellKnownGeogCS("WGS84"); |
364 | 0 | } |
365 | |
|
366 | 0 | if (oSRS.GetRoot() != nullptr) |
367 | 0 | { |
368 | 0 | poDS->m_oSRS = std::move(oSRS); |
369 | 0 | } |
370 | | |
371 | | /* -------------------------------------------------------------------- */ |
372 | | /* Get geotransform. */ |
373 | | /* -------------------------------------------------------------------- */ |
374 | 0 | char **papszUL = |
375 | 0 | CSLTokenizeString2(poDS->Get("UPPER_LEFT_CORNER", ""), ",", 0); |
376 | 0 | char **papszUR = |
377 | 0 | CSLTokenizeString2(poDS->Get("UPPER_RIGHT_CORNER", ""), ",", 0); |
378 | 0 | char **papszLL = |
379 | 0 | CSLTokenizeString2(poDS->Get("LOWER_LEFT_CORNER", ""), ",", 0); |
380 | |
|
381 | 0 | if (CSLCount(papszUL) == 4 && CSLCount(papszUR) == 4 && |
382 | 0 | CSLCount(papszLL) == 4) |
383 | 0 | { |
384 | 0 | poDS->adfGeoTransform[0] = CPLAtof(papszUL[2]); |
385 | 0 | poDS->adfGeoTransform[1] = (CPLAtof(papszUR[2]) - CPLAtof(papszUL[2])) / |
386 | 0 | (poDS->nRasterXSize - 1); |
387 | 0 | poDS->adfGeoTransform[2] = (CPLAtof(papszUR[3]) - CPLAtof(papszUL[3])) / |
388 | 0 | (poDS->nRasterXSize - 1); |
389 | |
|
390 | 0 | poDS->adfGeoTransform[3] = CPLAtof(papszUL[3]); |
391 | 0 | poDS->adfGeoTransform[4] = (CPLAtof(papszLL[2]) - CPLAtof(papszUL[2])) / |
392 | 0 | (poDS->nRasterYSize - 1); |
393 | 0 | poDS->adfGeoTransform[5] = (CPLAtof(papszLL[3]) - CPLAtof(papszUL[3])) / |
394 | 0 | (poDS->nRasterYSize - 1); |
395 | | |
396 | | // Move origin up-left half a pixel. |
397 | 0 | poDS->adfGeoTransform[0] -= poDS->adfGeoTransform[1] * 0.5; |
398 | 0 | poDS->adfGeoTransform[0] -= poDS->adfGeoTransform[4] * 0.5; |
399 | 0 | poDS->adfGeoTransform[3] -= poDS->adfGeoTransform[2] * 0.5; |
400 | 0 | poDS->adfGeoTransform[3] -= poDS->adfGeoTransform[5] * 0.5; |
401 | 0 | } |
402 | |
|
403 | 0 | CSLDestroy(papszUL); |
404 | 0 | CSLDestroy(papszLL); |
405 | 0 | CSLDestroy(papszUR); |
406 | | |
407 | | /* -------------------------------------------------------------------- */ |
408 | | /* Initialize any PAM information. */ |
409 | | /* -------------------------------------------------------------------- */ |
410 | 0 | poDS->SetDescription(poOpenInfo->pszFilename); |
411 | 0 | poDS->TryLoadXML(); |
412 | | |
413 | | /* -------------------------------------------------------------------- */ |
414 | | /* Check for overviews. */ |
415 | | /* -------------------------------------------------------------------- */ |
416 | 0 | poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename); |
417 | |
|
418 | 0 | return poDS.release(); |
419 | 0 | } |
420 | | |
421 | | /************************************************************************/ |
422 | | /* GDALRegister_NDF() */ |
423 | | /************************************************************************/ |
424 | | |
425 | | void GDALRegister_NDF() |
426 | | |
427 | 2 | { |
428 | 2 | if (GDALGetDriverByName("NDF") != nullptr) |
429 | 0 | return; |
430 | | |
431 | 2 | GDALDriver *poDriver = new GDALDriver(); |
432 | | |
433 | 2 | poDriver->SetDescription("NDF"); |
434 | 2 | poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); |
435 | 2 | poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "NLAPS Data Format"); |
436 | 2 | poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/ndf.html"); |
437 | 2 | poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
438 | | |
439 | 2 | poDriver->pfnIdentify = NDFDataset::Identify; |
440 | 2 | poDriver->pfnOpen = NDFDataset::Open; |
441 | | |
442 | 2 | GetGDALDriverManager()->RegisterDriver(poDriver); |
443 | 2 | } |