Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: SNODAS driver
4 : * Purpose: Implementation of SNODASDataset
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 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_string.h"
30 : #include "gdal_frmts.h"
31 : #include "ogr_srs_api.h"
32 : #include "rawdataset.h"
33 :
34 : /************************************************************************/
35 : /* ==================================================================== */
36 : /* SNODASDataset */
37 : /* ==================================================================== */
38 : /************************************************************************/
39 :
40 : class SNODASRasterBand;
41 :
42 : class SNODASDataset final : public RawDataset
43 : {
44 : CPLString osDataFilename{};
45 : bool bGotTransform;
46 : double adfGeoTransform[6];
47 : bool bHasNoData;
48 : double dfNoData;
49 : bool bHasMin;
50 : double dfMin;
51 : int bHasMax;
52 : double dfMax;
53 : OGRSpatialReference m_oSRS{};
54 :
55 : friend class SNODASRasterBand;
56 :
57 : CPL_DISALLOW_COPY_ASSIGN(SNODASDataset)
58 :
59 : CPLErr Close() override;
60 :
61 : public:
62 : SNODASDataset();
63 : ~SNODASDataset() override;
64 :
65 : CPLErr GetGeoTransform(double *padfTransform) override;
66 :
67 1 : const OGRSpatialReference *GetSpatialRef() const override
68 : {
69 1 : return &m_oSRS;
70 : }
71 :
72 : char **GetFileList() override;
73 :
74 : static GDALDataset *Open(GDALOpenInfo *);
75 : static int Identify(GDALOpenInfo *);
76 : };
77 :
78 : /************************************************************************/
79 : /* ==================================================================== */
80 : /* SNODASRasterBand */
81 : /* ==================================================================== */
82 : /************************************************************************/
83 :
84 : class SNODASRasterBand final : public RawRasterBand
85 : {
86 : CPL_DISALLOW_COPY_ASSIGN(SNODASRasterBand)
87 :
88 : public:
89 : SNODASRasterBand(VSILFILE *fpRaw, int nXSize, int nYSize);
90 :
91 6 : ~SNODASRasterBand() override
92 3 : {
93 6 : }
94 :
95 : double GetNoDataValue(int *pbSuccess = nullptr) override;
96 : double GetMinimum(int *pbSuccess = nullptr) override;
97 : double GetMaximum(int *pbSuccess = nullptr) override;
98 : };
99 :
100 : /************************************************************************/
101 : /* SNODASRasterBand() */
102 : /************************************************************************/
103 :
104 3 : SNODASRasterBand::SNODASRasterBand(VSILFILE *fpRawIn, int nXSize, int nYSize)
105 : : RawRasterBand(fpRawIn, 0, 2, nXSize * 2, GDT_Int16, !CPL_IS_LSB, nXSize,
106 3 : nYSize, RawRasterBand::OwnFP::YES)
107 : {
108 3 : }
109 :
110 : /************************************************************************/
111 : /* GetNoDataValue() */
112 : /************************************************************************/
113 :
114 1 : double SNODASRasterBand::GetNoDataValue(int *pbSuccess)
115 : {
116 1 : SNODASDataset *poGDS = reinterpret_cast<SNODASDataset *>(poDS);
117 1 : if (pbSuccess)
118 1 : *pbSuccess = poGDS->bHasNoData;
119 :
120 1 : if (poGDS->bHasNoData)
121 1 : return poGDS->dfNoData;
122 :
123 0 : return RawRasterBand::GetNoDataValue(pbSuccess);
124 : }
125 :
126 : /************************************************************************/
127 : /* GetMinimum() */
128 : /************************************************************************/
129 :
130 1 : double SNODASRasterBand::GetMinimum(int *pbSuccess)
131 : {
132 1 : SNODASDataset *poGDS = reinterpret_cast<SNODASDataset *>(poDS);
133 1 : if (pbSuccess)
134 1 : *pbSuccess = poGDS->bHasMin;
135 :
136 1 : if (poGDS->bHasMin)
137 1 : return poGDS->dfMin;
138 :
139 0 : return RawRasterBand::GetMinimum(pbSuccess);
140 : }
141 :
142 : /************************************************************************/
143 : /* GetMaximum() */
144 : /************************************************************************/
145 :
146 1 : double SNODASRasterBand::GetMaximum(int *pbSuccess)
147 : {
148 1 : SNODASDataset *poGDS = reinterpret_cast<SNODASDataset *>(poDS);
149 1 : if (pbSuccess)
150 1 : *pbSuccess = poGDS->bHasMax;
151 :
152 1 : if (poGDS->bHasMax)
153 1 : return poGDS->dfMax;
154 :
155 0 : return RawRasterBand::GetMaximum(pbSuccess);
156 : }
157 :
158 : /************************************************************************/
159 : /* ==================================================================== */
160 : /* SNODASDataset */
161 : /* ==================================================================== */
162 : /************************************************************************/
163 :
164 : /************************************************************************/
165 : /* SNODASDataset() */
166 : /************************************************************************/
167 :
168 3 : SNODASDataset::SNODASDataset()
169 : : bGotTransform(false), bHasNoData(false), dfNoData(0.0), bHasMin(false),
170 3 : dfMin(0.0), bHasMax(false), dfMax(0.0)
171 : {
172 3 : m_oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
173 3 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
174 :
175 3 : adfGeoTransform[0] = 0.0;
176 3 : adfGeoTransform[1] = 1.0;
177 3 : adfGeoTransform[2] = 0.0;
178 3 : adfGeoTransform[3] = 0.0;
179 3 : adfGeoTransform[4] = 0.0;
180 3 : adfGeoTransform[5] = 1.0;
181 3 : }
182 :
183 : /************************************************************************/
184 : /* ~SNODASDataset() */
185 : /************************************************************************/
186 :
187 6 : SNODASDataset::~SNODASDataset()
188 :
189 : {
190 3 : SNODASDataset::Close();
191 6 : }
192 :
193 : /************************************************************************/
194 : /* Close() */
195 : /************************************************************************/
196 :
197 6 : CPLErr SNODASDataset::Close()
198 : {
199 6 : CPLErr eErr = CE_None;
200 6 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
201 : {
202 3 : if (SNODASDataset::FlushCache(true) != CE_None)
203 0 : eErr = CE_Failure;
204 :
205 3 : if (GDALPamDataset::Close() != CE_None)
206 0 : eErr = CE_Failure;
207 : }
208 6 : return eErr;
209 : }
210 :
211 : /************************************************************************/
212 : /* GetGeoTransform() */
213 : /************************************************************************/
214 :
215 1 : CPLErr SNODASDataset::GetGeoTransform(double *padfTransform)
216 :
217 : {
218 1 : if (bGotTransform)
219 : {
220 1 : memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
221 1 : return CE_None;
222 : }
223 :
224 0 : return GDALPamDataset::GetGeoTransform(padfTransform);
225 : }
226 :
227 : /************************************************************************/
228 : /* GetFileList() */
229 : /************************************************************************/
230 :
231 2 : char **SNODASDataset::GetFileList()
232 :
233 : {
234 2 : char **papszFileList = GDALPamDataset::GetFileList();
235 :
236 2 : papszFileList = CSLAddString(papszFileList, osDataFilename);
237 :
238 2 : return papszFileList;
239 : }
240 :
241 : /************************************************************************/
242 : /* Identify() */
243 : /************************************************************************/
244 :
245 48725 : int SNODASDataset::Identify(GDALOpenInfo *poOpenInfo)
246 : {
247 48725 : if (poOpenInfo->nHeaderBytes == 0)
248 45321 : return FALSE;
249 :
250 3404 : return STARTS_WITH_CI(reinterpret_cast<char *>(poOpenInfo->pabyHeader),
251 : "Format version: NOHRSC GIS/RS raster file v1.1");
252 : }
253 :
254 : /************************************************************************/
255 : /* Open() */
256 : /************************************************************************/
257 :
258 3 : GDALDataset *SNODASDataset::Open(GDALOpenInfo *poOpenInfo)
259 :
260 : {
261 3 : if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
262 0 : return nullptr;
263 :
264 : /* -------------------------------------------------------------------- */
265 : /* Confirm the requested access is supported. */
266 : /* -------------------------------------------------------------------- */
267 3 : if (poOpenInfo->eAccess == GA_Update)
268 : {
269 0 : CPLError(CE_Failure, CPLE_NotSupported,
270 : "The SNODAS driver does not support update access to existing"
271 : " datasets.");
272 0 : return nullptr;
273 : }
274 :
275 3 : int nRows = -1;
276 3 : int nCols = -1;
277 6 : CPLString osDataFilename;
278 3 : bool bIsInteger = false;
279 3 : bool bIs2Bytes = false;
280 3 : double dfNoData = 0;
281 3 : bool bHasNoData = false;
282 3 : double dfMin = 0;
283 3 : bool bHasMin = false;
284 3 : double dfMax = 0;
285 3 : bool bHasMax = false;
286 3 : double dfMinX = 0.0;
287 3 : double dfMinY = 0.0;
288 3 : double dfMaxX = 0.0;
289 3 : double dfMaxY = 0.0;
290 3 : bool bHasMinX = false;
291 3 : bool bHasMinY = false;
292 3 : bool bHasMaxX = false;
293 3 : bool bHasMaxY = false;
294 3 : bool bNotProjected = false;
295 3 : bool bIsWGS84 = false;
296 6 : CPLString osDataUnits;
297 6 : CPLString osDescription;
298 3 : int nStartYear = -1;
299 3 : int nStartMonth = -1;
300 3 : int nStartDay = -1;
301 3 : int nStartHour = -1;
302 3 : int nStartMinute = -1;
303 3 : int nStartSecond = -1;
304 3 : int nStopYear = -1;
305 3 : int nStopMonth = -1;
306 3 : int nStopDay = -1;
307 3 : int nStopHour = -1;
308 3 : int nStopMinute = -1;
309 3 : int nStopSecond = -1;
310 :
311 3 : const char *pszLine = nullptr;
312 324 : while ((pszLine = CPLReadLine2L(poOpenInfo->fpL, 1024, nullptr)) != nullptr)
313 : {
314 : char **papszTokens =
315 321 : CSLTokenizeStringComplex(pszLine, ":", TRUE, FALSE);
316 321 : if (CSLCount(papszTokens) != 2)
317 : {
318 3 : CSLDestroy(papszTokens);
319 3 : continue;
320 : }
321 318 : if (papszTokens[1][0] == ' ')
322 318 : memmove(papszTokens[1], papszTokens[1] + 1,
323 318 : strlen(papszTokens[1] + 1) + 1);
324 :
325 318 : if (EQUAL(papszTokens[0], "Data file pathname"))
326 : {
327 3 : osDataFilename = papszTokens[1];
328 : }
329 315 : else if (EQUAL(papszTokens[0], "Description"))
330 : {
331 3 : osDescription = papszTokens[1];
332 : }
333 312 : else if (EQUAL(papszTokens[0], "Data units"))
334 : {
335 3 : osDataUnits = papszTokens[1];
336 : }
337 :
338 309 : else if (EQUAL(papszTokens[0], "Start year"))
339 3 : nStartYear = atoi(papszTokens[1]);
340 306 : else if (EQUAL(papszTokens[0], "Start month"))
341 3 : nStartMonth = atoi(papszTokens[1]);
342 303 : else if (EQUAL(papszTokens[0], "Start day"))
343 3 : nStartDay = atoi(papszTokens[1]);
344 300 : else if (EQUAL(papszTokens[0], "Start hour"))
345 3 : nStartHour = atoi(papszTokens[1]);
346 297 : else if (EQUAL(papszTokens[0], " Start minute"))
347 0 : nStartMinute = atoi(papszTokens[1]);
348 297 : else if (EQUAL(papszTokens[0], "Start second"))
349 3 : nStartSecond = atoi(papszTokens[1]);
350 :
351 294 : else if (EQUAL(papszTokens[0], "Stop year"))
352 3 : nStopYear = atoi(papszTokens[1]);
353 291 : else if (EQUAL(papszTokens[0], "Stop month"))
354 3 : nStopMonth = atoi(papszTokens[1]);
355 288 : else if (EQUAL(papszTokens[0], "Stop day"))
356 3 : nStopDay = atoi(papszTokens[1]);
357 285 : else if (EQUAL(papszTokens[0], "Stop hour"))
358 3 : nStopHour = atoi(papszTokens[1]);
359 282 : else if (EQUAL(papszTokens[0], "Stop minute"))
360 3 : nStopMinute = atoi(papszTokens[1]);
361 279 : else if (EQUAL(papszTokens[0], "Stop second"))
362 3 : nStopSecond = atoi(papszTokens[1]);
363 :
364 276 : else if (EQUAL(papszTokens[0], "Number of columns"))
365 : {
366 3 : nCols = atoi(papszTokens[1]);
367 : }
368 273 : else if (EQUAL(papszTokens[0], "Number of rows"))
369 : {
370 3 : nRows = atoi(papszTokens[1]);
371 : }
372 270 : else if (EQUAL(papszTokens[0], "Data type"))
373 : {
374 3 : bIsInteger = EQUAL(papszTokens[1], "integer");
375 : }
376 267 : else if (EQUAL(papszTokens[0], "Data bytes per pixel"))
377 : {
378 3 : bIs2Bytes = EQUAL(papszTokens[1], "2");
379 : }
380 264 : else if (EQUAL(papszTokens[0], "Projected"))
381 : {
382 3 : bNotProjected = EQUAL(papszTokens[1], "no");
383 : }
384 261 : else if (EQUAL(papszTokens[0], "Horizontal datum"))
385 : {
386 3 : bIsWGS84 = EQUAL(papszTokens[1], "WGS84");
387 : }
388 258 : else if (EQUAL(papszTokens[0], "No data value"))
389 : {
390 3 : bHasNoData = true;
391 3 : dfNoData = CPLAtofM(papszTokens[1]);
392 : }
393 255 : else if (EQUAL(papszTokens[0], "Minimum data value"))
394 : {
395 3 : bHasMin = true;
396 3 : dfMin = CPLAtofM(papszTokens[1]);
397 : }
398 252 : else if (EQUAL(papszTokens[0], "Maximum data value"))
399 : {
400 3 : bHasMax = true;
401 3 : dfMax = CPLAtofM(papszTokens[1]);
402 : }
403 249 : else if (EQUAL(papszTokens[0], "Minimum x-axis coordinate"))
404 : {
405 3 : bHasMinX = true;
406 3 : dfMinX = CPLAtofM(papszTokens[1]);
407 : }
408 246 : else if (EQUAL(papszTokens[0], "Minimum y-axis coordinate"))
409 : {
410 3 : bHasMinY = true;
411 3 : dfMinY = CPLAtofM(papszTokens[1]);
412 : }
413 243 : else if (EQUAL(papszTokens[0], "Maximum x-axis coordinate"))
414 : {
415 3 : bHasMaxX = true;
416 3 : dfMaxX = CPLAtofM(papszTokens[1]);
417 : }
418 240 : else if (EQUAL(papszTokens[0], "Maximum y-axis coordinate"))
419 : {
420 3 : bHasMaxY = true;
421 3 : dfMaxY = CPLAtofM(papszTokens[1]);
422 : }
423 :
424 318 : CSLDestroy(papszTokens);
425 : }
426 :
427 3 : CPL_IGNORE_RET_VAL(VSIFCloseL(poOpenInfo->fpL));
428 3 : poOpenInfo->fpL = nullptr;
429 :
430 : /* -------------------------------------------------------------------- */
431 : /* Did we get the required keywords? If not we return with */
432 : /* this never having been considered to be a match. This isn't */
433 : /* an error! */
434 : /* -------------------------------------------------------------------- */
435 3 : if (nRows == -1 || nCols == -1 || !bIsInteger || !bIs2Bytes)
436 0 : return nullptr;
437 :
438 3 : if (!bNotProjected || !bIsWGS84)
439 0 : return nullptr;
440 :
441 3 : if (osDataFilename.empty())
442 0 : return nullptr;
443 :
444 3 : if (!GDALCheckDatasetDimensions(nCols, nRows))
445 0 : return nullptr;
446 :
447 : /* -------------------------------------------------------------------- */
448 : /* Open target binary file. */
449 : /* -------------------------------------------------------------------- */
450 3 : const char *pszPath = CPLGetPath(poOpenInfo->pszFilename);
451 3 : osDataFilename = CPLFormFilename(pszPath, osDataFilename, nullptr);
452 :
453 3 : VSILFILE *fpRaw = VSIFOpenL(osDataFilename, "rb");
454 :
455 3 : if (fpRaw == nullptr)
456 0 : return nullptr;
457 :
458 : /* -------------------------------------------------------------------- */
459 : /* Create a corresponding GDALDataset. */
460 : /* -------------------------------------------------------------------- */
461 6 : auto poDS = std::make_unique<SNODASDataset>();
462 :
463 3 : poDS->nRasterXSize = nCols;
464 3 : poDS->nRasterYSize = nRows;
465 3 : poDS->osDataFilename = std::move(osDataFilename);
466 3 : poDS->bHasNoData = bHasNoData;
467 3 : poDS->dfNoData = dfNoData;
468 3 : poDS->bHasMin = bHasMin;
469 3 : poDS->dfMin = dfMin;
470 3 : poDS->bHasMax = bHasMax;
471 3 : poDS->dfMax = dfMax;
472 3 : if (bHasMinX && bHasMinY && bHasMaxX && bHasMaxY)
473 : {
474 3 : poDS->bGotTransform = true;
475 3 : poDS->adfGeoTransform[0] = dfMinX;
476 3 : poDS->adfGeoTransform[1] = (dfMaxX - dfMinX) / nCols;
477 3 : poDS->adfGeoTransform[2] = 0.0;
478 3 : poDS->adfGeoTransform[3] = dfMaxY;
479 3 : poDS->adfGeoTransform[4] = 0.0;
480 3 : poDS->adfGeoTransform[5] = -(dfMaxY - dfMinY) / nRows;
481 : }
482 :
483 3 : if (!osDescription.empty())
484 3 : poDS->SetMetadataItem("Description", osDescription);
485 3 : if (!osDataUnits.empty())
486 3 : poDS->SetMetadataItem("Data_Units", osDataUnits);
487 3 : if (nStartYear != -1 && nStartMonth != -1 && nStartDay != -1 &&
488 3 : nStartHour != -1 && nStartMinute != -1 && nStartSecond != -1)
489 0 : poDS->SetMetadataItem(
490 : "Start_Date",
491 : CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d", nStartYear, nStartMonth,
492 0 : nStartDay, nStartHour, nStartMinute, nStartSecond));
493 3 : if (nStopYear != -1 && nStopMonth != -1 && nStopDay != -1 &&
494 3 : nStopHour != -1 && nStopMinute != -1 && nStopSecond != -1)
495 6 : poDS->SetMetadataItem("Stop_Date",
496 : CPLSPrintf("%04d/%02d/%02d %02d:%02d:%02d",
497 : nStopYear, nStopMonth, nStopDay,
498 3 : nStopHour, nStopMinute, nStopSecond));
499 :
500 : /* -------------------------------------------------------------------- */
501 : /* Create band information objects. */
502 : /* -------------------------------------------------------------------- */
503 6 : auto poBand = std::make_unique<SNODASRasterBand>(fpRaw, nCols, nRows);
504 3 : if (!poBand->IsValid())
505 0 : return nullptr;
506 3 : poDS->SetBand(1, std::move(poBand));
507 :
508 : /* -------------------------------------------------------------------- */
509 : /* Initialize any PAM information. */
510 : /* -------------------------------------------------------------------- */
511 3 : poDS->SetDescription(poOpenInfo->pszFilename);
512 3 : poDS->TryLoadXML();
513 :
514 : /* -------------------------------------------------------------------- */
515 : /* Check for overviews. */
516 : /* -------------------------------------------------------------------- */
517 3 : poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
518 :
519 3 : return poDS.release();
520 : }
521 :
522 : /************************************************************************/
523 : /* GDALRegister_SNODAS() */
524 : /************************************************************************/
525 :
526 1511 : void GDALRegister_SNODAS()
527 :
528 : {
529 1511 : if (GDALGetDriverByName("SNODAS") != nullptr)
530 295 : return;
531 :
532 1216 : GDALDriver *poDriver = new GDALDriver();
533 :
534 1216 : poDriver->SetDescription("SNODAS");
535 1216 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
536 1216 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
537 1216 : "Snow Data Assimilation System");
538 1216 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/snodas.html");
539 1216 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdr");
540 1216 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
541 :
542 1216 : poDriver->pfnOpen = SNODASDataset::Open;
543 1216 : poDriver->pfnIdentify = SNODASDataset::Identify;
544 :
545 1216 : GetGDALDriverManager()->RegisterDriver(poDriver);
546 : }
|