/src/gdal/frmts/grib/gribdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GRIB Driver |
4 | | * Purpose: GDALDataset driver for GRIB translator for read support |
5 | | * Author: Bas Retsios, retsios@itc.nl |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2007, ITC |
9 | | * Copyright (c) 2008-2017, Even Rouault <even dot rouault at spatialys dot com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************** |
13 | | * |
14 | | */ |
15 | | |
16 | | #include "cpl_port.h" |
17 | | #include "gribdataset.h" |
18 | | #include "gribdrivercore.h" |
19 | | |
20 | | #include <cassert> |
21 | | #include <cerrno> |
22 | | #include <cmath> |
23 | | #include <cstddef> |
24 | | #include <cstdio> |
25 | | #include <cstdlib> |
26 | | #include <cstring> |
27 | | #if HAVE_FCNTL_H |
28 | | #include <fcntl.h> |
29 | | #endif |
30 | | |
31 | | #include <algorithm> |
32 | | #include <mutex> |
33 | | #include <set> |
34 | | #include <string> |
35 | | #include <vector> |
36 | | |
37 | | #include "cpl_conv.h" |
38 | | #include "cpl_error.h" |
39 | | #include "cpl_multiproc.h" |
40 | | #include "cpl_string.h" |
41 | | #include "cpl_vsi.h" |
42 | | #include "cpl_time.h" |
43 | | #include "degrib/degrib/degrib2.h" |
44 | | #include "degrib/degrib/inventory.h" |
45 | | #include "degrib/degrib/meta.h" |
46 | | #include "degrib/degrib/metaname.h" |
47 | | #include "degrib/degrib/myerror.h" |
48 | | #include "degrib/degrib/type.h" |
49 | | CPL_C_START |
50 | | #include "degrib/g2clib/grib2.h" |
51 | | #include "degrib/g2clib/pdstemplates.h" |
52 | | CPL_C_END |
53 | | #include "gdal.h" |
54 | | #include "gdal_frmts.h" |
55 | | #include "gdal_pam.h" |
56 | | #include "gdal_priv.h" |
57 | | #include "ogr_spatialref.h" |
58 | | #include "memdataset.h" |
59 | | |
60 | | static CPLMutex *hGRIBMutex = nullptr; |
61 | | |
62 | | /************************************************************************/ |
63 | | /* ConvertUnitInText() */ |
64 | | /************************************************************************/ |
65 | | |
66 | | static CPLString ConvertUnitInText(bool bMetricUnits, const char *pszTxt) |
67 | 27.9k | { |
68 | 27.9k | if (pszTxt == nullptr) |
69 | 0 | return CPLString(); |
70 | 27.9k | if (!bMetricUnits) |
71 | 0 | return pszTxt; |
72 | | |
73 | 27.9k | CPLString osRes(pszTxt); |
74 | 27.9k | size_t iPos = osRes.find("[K]"); |
75 | 27.9k | if (iPos != std::string::npos) |
76 | 5.13k | osRes = osRes.substr(0, iPos) + "[C]" + osRes.substr(iPos + 3); |
77 | 27.9k | return osRes; |
78 | 27.9k | } |
79 | | |
80 | | /************************************************************************/ |
81 | | /* Lon360to180() */ |
82 | | /************************************************************************/ |
83 | | |
84 | | static inline double Lon360to180(double lon) |
85 | 345 | { |
86 | 345 | if (lon == 180) |
87 | 0 | return 180; |
88 | 345 | return fmod(lon + 180, 360) - 180; |
89 | 345 | } |
90 | | |
91 | | /************************************************************************/ |
92 | | /* GRIBRasterBand() */ |
93 | | /************************************************************************/ |
94 | | |
95 | | GRIBRasterBand::GRIBRasterBand(GRIBDataset *poDSIn, int nBandIn, |
96 | | inventoryType *psInv) |
97 | 13.9k | : start(psInv->start), subgNum(psInv->subgNum), |
98 | 13.9k | longFstLevel(CPLStrdup(psInv->longFstLevel)), m_Grib_Data(nullptr), |
99 | 13.9k | m_Grib_MetaData(nullptr), nGribDataXSize(poDSIn->nRasterXSize), |
100 | 13.9k | nGribDataYSize(poDSIn->nRasterYSize), m_nGribVersion(psInv->GribVersion), |
101 | 13.9k | m_bHasLookedForNoData(false), m_dfNoData(0.0), m_bHasNoData(false) |
102 | | |
103 | 13.9k | { |
104 | 13.9k | poDS = poDSIn; |
105 | 13.9k | nBand = nBandIn; |
106 | | |
107 | | // Let user do -ot Float32 if needed for saving space, GRIB contains |
108 | | // Float64 (though not fully utilized most of the time). |
109 | 13.9k | eDataType = GDT_Float64; |
110 | | |
111 | 13.9k | nBlockXSize = poDSIn->nRasterXSize; |
112 | 13.9k | nBlockYSize = 1; |
113 | | |
114 | 13.9k | if (psInv->unitName != nullptr && psInv->comment != nullptr && |
115 | 13.9k | psInv->element != nullptr) |
116 | 13.9k | { |
117 | 13.9k | bLoadedMetadata = true; |
118 | 13.9k | const char *pszGribNormalizeUnits = |
119 | 13.9k | CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES"); |
120 | 13.9k | bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits); |
121 | | |
122 | 13.9k | SetMetadataItem("GRIB_UNIT", |
123 | 13.9k | ConvertUnitInText(bMetricUnits, psInv->unitName)); |
124 | 13.9k | SetMetadataItem("GRIB_COMMENT", |
125 | 13.9k | ConvertUnitInText(bMetricUnits, psInv->comment)); |
126 | 13.9k | SetMetadataItem("GRIB_ELEMENT", psInv->element); |
127 | 13.9k | SetMetadataItem("GRIB_SHORT_NAME", psInv->shortFstLevel); |
128 | 13.9k | SetMetadataItem("GRIB_REF_TIME", |
129 | 13.9k | CPLString().Printf("%.0f", psInv->refTime)); |
130 | 13.9k | SetMetadataItem("GRIB_VALID_TIME", |
131 | 13.9k | CPLString().Printf("%.0f", psInv->validTime)); |
132 | 13.9k | SetMetadataItem("GRIB_FORECAST_SECONDS", |
133 | 13.9k | CPLString().Printf("%.0f", psInv->foreSec)); |
134 | 13.9k | } |
135 | 13.9k | } |
136 | | |
137 | | /************************************************************************/ |
138 | | /* FindMetaData() */ |
139 | | /************************************************************************/ |
140 | | |
141 | | void GRIBRasterBand::FindMetaData() |
142 | 0 | { |
143 | 0 | if (bLoadedMetadata) |
144 | 0 | return; |
145 | 0 | if (m_Grib_MetaData == nullptr) |
146 | 0 | { |
147 | 0 | grib_MetaData *metaData; |
148 | 0 | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
149 | 0 | GRIBRasterBand::ReadGribData(poGDS->fp, start, subgNum, nullptr, |
150 | 0 | &metaData); |
151 | 0 | if (metaData == nullptr) |
152 | 0 | return; |
153 | 0 | m_Grib_MetaData = metaData; |
154 | 0 | } |
155 | 0 | bLoadedMetadata = true; |
156 | 0 | m_nGribVersion = m_Grib_MetaData->GribVersion; |
157 | |
|
158 | 0 | const char *pszGribNormalizeUnits = |
159 | 0 | CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES"); |
160 | 0 | bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits); |
161 | |
|
162 | 0 | GDALRasterBand::SetMetadataItem( |
163 | 0 | "GRIB_UNIT", |
164 | 0 | ConvertUnitInText(bMetricUnits, m_Grib_MetaData->unitName)); |
165 | 0 | GDALRasterBand::SetMetadataItem( |
166 | 0 | "GRIB_COMMENT", |
167 | 0 | ConvertUnitInText(bMetricUnits, m_Grib_MetaData->comment)); |
168 | |
|
169 | 0 | GDALRasterBand::SetMetadataItem("GRIB_ELEMENT", m_Grib_MetaData->element); |
170 | 0 | GDALRasterBand::SetMetadataItem("GRIB_SHORT_NAME", |
171 | 0 | m_Grib_MetaData->shortFstLevel); |
172 | |
|
173 | 0 | if (m_nGribVersion == 2) |
174 | 0 | { |
175 | 0 | GDALRasterBand::SetMetadataItem( |
176 | 0 | "GRIB_REF_TIME", |
177 | 0 | CPLString().Printf("%.0f", m_Grib_MetaData->pds2.refTime)); |
178 | 0 | GDALRasterBand::SetMetadataItem( |
179 | 0 | "GRIB_VALID_TIME", |
180 | 0 | CPLString().Printf("%.0f", m_Grib_MetaData->pds2.sect4.validTime)); |
181 | 0 | } |
182 | 0 | else if (m_nGribVersion == 1) |
183 | 0 | { |
184 | 0 | GDALRasterBand::SetMetadataItem( |
185 | 0 | "GRIB_REF_TIME", |
186 | 0 | CPLString().Printf("%.0f", m_Grib_MetaData->pds1.refTime)); |
187 | 0 | GDALRasterBand::SetMetadataItem( |
188 | 0 | "GRIB_VALID_TIME", |
189 | 0 | CPLString().Printf("%.0f", m_Grib_MetaData->pds1.validTime)); |
190 | 0 | } |
191 | |
|
192 | 0 | GDALRasterBand::SetMetadataItem( |
193 | 0 | "GRIB_FORECAST_SECONDS", |
194 | 0 | CPLString().Printf("%d", m_Grib_MetaData->deltTime)); |
195 | 0 | } |
196 | | |
197 | | /************************************************************************/ |
198 | | /* FindTrueStart() */ |
199 | | /* */ |
200 | | /* Scan after the official start of the message to find its */ |
201 | | /* true starting offset. */ |
202 | | /************************************************************************/ |
203 | | vsi_l_offset GRIBRasterBand::FindTrueStart(VSILFILE *fp, vsi_l_offset start) |
204 | 819 | { |
205 | | // GRIB messages can be preceded by "garbage". GRIB2Inventory() |
206 | | // does not return the offset to the real start of the message |
207 | 819 | char szHeader[1024 + 1]; |
208 | 819 | VSIFSeekL(fp, start, SEEK_SET); |
209 | 819 | const int nRead = |
210 | 819 | static_cast<int>(VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp)); |
211 | 819 | szHeader[nRead] = 0; |
212 | | // Find the real offset of the fist message |
213 | 819 | int nOffsetFirstMessage = 0; |
214 | 130k | for (int j = 0; j + 3 < nRead; j++) |
215 | 130k | { |
216 | 130k | if (STARTS_WITH_CI(szHeader + j, "GRIB") |
217 | | #ifdef ENABLE_TDLP |
218 | | || STARTS_WITH_CI(szHeader + j, "TDLP") |
219 | | #endif |
220 | 130k | ) |
221 | 819 | { |
222 | 819 | nOffsetFirstMessage = j; |
223 | 819 | break; |
224 | 819 | } |
225 | 130k | } |
226 | 819 | return start + nOffsetFirstMessage; |
227 | 819 | } |
228 | | |
229 | | /************************************************************************/ |
230 | | /* FindPDSTemplateGRIB2() */ |
231 | | /* */ |
232 | | /* Scan the file for the PDS template info and represent it as */ |
233 | | /* metadata. */ |
234 | | /************************************************************************/ |
235 | | |
236 | | void GRIBRasterBand::FindPDSTemplateGRIB2() |
237 | | |
238 | 0 | { |
239 | 0 | CPLAssert(m_nGribVersion == 2); |
240 | |
|
241 | 0 | if (bLoadedPDS) |
242 | 0 | return; |
243 | 0 | bLoadedPDS = true; |
244 | |
|
245 | 0 | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
246 | 0 | start = FindTrueStart(poGDS->fp, start); |
247 | | |
248 | | // Read section 0 |
249 | 0 | GByte abySection0[16]; |
250 | 0 | if (VSIFSeekL(poGDS->fp, start, SEEK_SET) != 0 || |
251 | 0 | VSIFReadL(abySection0, 16, 1, poGDS->fp) != 1) |
252 | 0 | { |
253 | 0 | CPLDebug("GRIB", "Cannot read leading bytes of section 0"); |
254 | 0 | return; |
255 | 0 | } |
256 | 0 | GByte nDiscipline = abySection0[7 - 1]; |
257 | 0 | CPLString osDiscipline; |
258 | 0 | osDiscipline = CPLString().Printf("%d", nDiscipline); |
259 | 0 | static const char *const table00[] = {"Meteorological", |
260 | 0 | "Hydrological", |
261 | 0 | "Land Surface", |
262 | 0 | "Space products", |
263 | 0 | "Space products", |
264 | 0 | "Reserved", |
265 | 0 | "Reserved", |
266 | 0 | "Reserved", |
267 | 0 | "Reserved", |
268 | 0 | "Reserved", |
269 | 0 | "Oceanographic Products"}; |
270 | 0 | m_nDisciplineCode = nDiscipline; |
271 | 0 | if (nDiscipline < CPL_ARRAYSIZE(table00)) |
272 | 0 | { |
273 | 0 | m_osDisciplineName = table00[nDiscipline]; |
274 | 0 | osDiscipline += CPLString("(") + |
275 | 0 | CPLString(table00[nDiscipline]).replaceAll(' ', '_') + |
276 | 0 | ")"; |
277 | 0 | } |
278 | |
|
279 | 0 | GDALRasterBand::SetMetadataItem("GRIB_DISCIPLINE", osDiscipline.c_str()); |
280 | |
|
281 | 0 | GByte abyHead[5] = {0}; |
282 | |
|
283 | 0 | if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1) |
284 | 0 | { |
285 | 0 | CPLDebug("GRIB", "Cannot read 5 leading bytes past section 0"); |
286 | 0 | return; |
287 | 0 | } |
288 | | |
289 | 0 | GUInt32 nSectSize = 0; |
290 | 0 | if (abyHead[4] == 1) |
291 | 0 | { |
292 | 0 | memcpy(&nSectSize, abyHead, 4); |
293 | 0 | CPL_MSBPTR32(&nSectSize); |
294 | 0 | if (nSectSize >= 21 && nSectSize <= 100000 /* arbitrary upper limit */) |
295 | 0 | { |
296 | 0 | GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize)); |
297 | 0 | memcpy(pabyBody, abyHead, 5); |
298 | 0 | VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp); |
299 | |
|
300 | 0 | CPLString osIDS; |
301 | 0 | unsigned short nCenter = static_cast<unsigned short>( |
302 | 0 | pabyBody[6 - 1] * 256 + pabyBody[7 - 1]); |
303 | 0 | if (nCenter != GRIB2MISSING_u1 && nCenter != GRIB2MISSING_u2) |
304 | 0 | { |
305 | 0 | osIDS += "CENTER="; |
306 | 0 | m_nCenter = nCenter; |
307 | 0 | osIDS += CPLSPrintf("%d", nCenter); |
308 | 0 | const char *pszCenter = centerLookup(nCenter); |
309 | 0 | if (pszCenter) |
310 | 0 | { |
311 | 0 | m_osCenterName = pszCenter; |
312 | 0 | osIDS += CPLString("(") + pszCenter + ")"; |
313 | 0 | } |
314 | 0 | } |
315 | |
|
316 | 0 | unsigned short nSubCenter = static_cast<unsigned short>( |
317 | 0 | pabyBody[8 - 1] * 256 + pabyBody[9 - 1]); |
318 | 0 | if (nSubCenter != GRIB2MISSING_u2) |
319 | 0 | { |
320 | 0 | if (!osIDS.empty()) |
321 | 0 | osIDS += " "; |
322 | 0 | osIDS += "SUBCENTER="; |
323 | 0 | osIDS += CPLSPrintf("%d", nSubCenter); |
324 | 0 | m_nSubCenter = nSubCenter; |
325 | 0 | const char *pszSubCenter = subCenterLookup(nCenter, nSubCenter); |
326 | 0 | if (pszSubCenter) |
327 | 0 | { |
328 | 0 | m_osSubCenterName = pszSubCenter; |
329 | 0 | osIDS += CPLString("(") + pszSubCenter + ")"; |
330 | 0 | } |
331 | 0 | } |
332 | |
|
333 | 0 | if (!osIDS.empty()) |
334 | 0 | osIDS += " "; |
335 | 0 | osIDS += "MASTER_TABLE="; |
336 | 0 | osIDS += CPLSPrintf("%d", pabyBody[10 - 1]); |
337 | 0 | osIDS += " "; |
338 | 0 | osIDS += "LOCAL_TABLE="; |
339 | 0 | osIDS += CPLSPrintf("%d", pabyBody[11 - 1]); |
340 | 0 | osIDS += " "; |
341 | 0 | osIDS += "SIGNF_REF_TIME="; |
342 | 0 | unsigned nSignRefTime = pabyBody[12 - 1]; |
343 | 0 | osIDS += CPLSPrintf("%d", nSignRefTime); |
344 | 0 | static const char *const table12[] = { |
345 | 0 | "Analysis", "Start of Forecast", "Verifying time of forecast", |
346 | 0 | "Observation time"}; |
347 | 0 | if (nSignRefTime < CPL_ARRAYSIZE(table12)) |
348 | 0 | { |
349 | 0 | m_osSignRefTimeName = table12[nSignRefTime]; |
350 | 0 | osIDS += CPLString("(") + |
351 | 0 | CPLString(table12[nSignRefTime]).replaceAll(' ', '_') + |
352 | 0 | ")"; |
353 | 0 | } |
354 | 0 | osIDS += " "; |
355 | 0 | osIDS += "REF_TIME="; |
356 | 0 | m_osRefTime = |
357 | 0 | CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", |
358 | 0 | pabyBody[13 - 1] * 256 + pabyBody[14 - 1], |
359 | 0 | pabyBody[15 - 1], pabyBody[16 - 1], pabyBody[17 - 1], |
360 | 0 | pabyBody[18 - 1], pabyBody[19 - 1]); |
361 | 0 | osIDS += m_osRefTime; |
362 | 0 | osIDS += " "; |
363 | 0 | osIDS += "PROD_STATUS="; |
364 | 0 | unsigned nProdStatus = pabyBody[20 - 1]; |
365 | 0 | osIDS += CPLSPrintf("%d", nProdStatus); |
366 | 0 | static const char *const table13[] = { |
367 | 0 | "Operational", "Operational test", |
368 | 0 | "Research", "Re-analysis", |
369 | 0 | "TIGGE", "TIGGE test", |
370 | 0 | "S2S operational", "S2S test", |
371 | 0 | "UERRA", "UERRA test"}; |
372 | 0 | if (nProdStatus < CPL_ARRAYSIZE(table13)) |
373 | 0 | { |
374 | 0 | m_osProductionStatus = table13[nProdStatus]; |
375 | 0 | osIDS += CPLString("(") + |
376 | 0 | CPLString(table13[nProdStatus]).replaceAll(' ', '_') + |
377 | 0 | ")"; |
378 | 0 | } |
379 | 0 | osIDS += " "; |
380 | 0 | osIDS += "TYPE="; |
381 | 0 | unsigned nType = pabyBody[21 - 1]; |
382 | 0 | osIDS += CPLSPrintf("%d", nType); |
383 | 0 | static const char *const table14[] = { |
384 | 0 | "Analysis", |
385 | 0 | "Forecast", |
386 | 0 | "Analysis and forecast", |
387 | 0 | "Control forecast", |
388 | 0 | "Perturbed forecast", |
389 | 0 | "Control and perturbed forecast", |
390 | 0 | "Processed satellite observations", |
391 | 0 | "Processed radar observations", |
392 | 0 | "Event Probability"}; |
393 | 0 | if (nType < CPL_ARRAYSIZE(table14)) |
394 | 0 | { |
395 | 0 | m_osType = table14[nType]; |
396 | 0 | osIDS += CPLString("(") + |
397 | 0 | CPLString(table14[nType]).replaceAll(' ', '_') + ")"; |
398 | 0 | } |
399 | |
|
400 | 0 | GDALRasterBand::SetMetadataItem("GRIB_IDS", osIDS); |
401 | |
|
402 | 0 | CPLFree(pabyBody); |
403 | 0 | } |
404 | |
|
405 | 0 | if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1) |
406 | 0 | { |
407 | 0 | CPLDebug("GRIB", "Cannot read 5 leading bytes past section 1"); |
408 | 0 | return; |
409 | 0 | } |
410 | 0 | } |
411 | | |
412 | 0 | if (subgNum > 0) |
413 | 0 | { |
414 | | // If we are a subgrid, then iterate over all preceding subgrids |
415 | 0 | for (int iSubMessage = 0; iSubMessage < subgNum;) |
416 | 0 | { |
417 | 0 | memcpy(&nSectSize, abyHead, 4); |
418 | 0 | CPL_MSBPTR32(&nSectSize); |
419 | 0 | if (nSectSize < 5) |
420 | 0 | { |
421 | 0 | CPLDebug("GRIB", "Invalid section size for iSubMessage = %d", |
422 | 0 | iSubMessage); |
423 | 0 | return; |
424 | 0 | } |
425 | 0 | if (VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0) |
426 | 0 | { |
427 | 0 | CPLDebug("GRIB", |
428 | 0 | "Cannot read past section for iSubMessage = %d", |
429 | 0 | iSubMessage); |
430 | 0 | return; |
431 | 0 | } |
432 | 0 | if (abyHead[4] < 2 || abyHead[4] > 7) |
433 | 0 | { |
434 | 0 | CPLDebug("GRIB", "Invalid section number for iSubMessage = %d", |
435 | 0 | iSubMessage); |
436 | 0 | return; |
437 | 0 | } |
438 | 0 | if (abyHead[4] == 7) |
439 | 0 | { |
440 | 0 | ++iSubMessage; |
441 | 0 | } |
442 | 0 | if (VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1) |
443 | 0 | { |
444 | 0 | CPLDebug("GRIB", |
445 | 0 | "Cannot read 5 leading bytes for iSubMessage = %d", |
446 | 0 | iSubMessage); |
447 | 0 | return; |
448 | 0 | } |
449 | 0 | } |
450 | 0 | } |
451 | | |
452 | | // Skip to section 4 |
453 | 0 | while (abyHead[4] != 4) |
454 | 0 | { |
455 | 0 | memcpy(&nSectSize, abyHead, 4); |
456 | 0 | CPL_MSBPTR32(&nSectSize); |
457 | |
|
458 | 0 | const int nCurSection = abyHead[4]; |
459 | 0 | if (nSectSize < 5) |
460 | 0 | { |
461 | 0 | CPLDebug("GRIB", "Invalid section size for section %d", |
462 | 0 | nCurSection); |
463 | 0 | return; |
464 | 0 | } |
465 | 0 | if (VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0 || |
466 | 0 | VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1) |
467 | 0 | { |
468 | 0 | CPLDebug("GRIB", "Cannot read section %d", nCurSection); |
469 | 0 | return; |
470 | 0 | } |
471 | 0 | } |
472 | | |
473 | | // Collect section 4 octet information. We read the file |
474 | | // ourselves since the GRIB API does not appear to preserve all |
475 | | // this for us. |
476 | | // if( abyHead[4] == 4 ) |
477 | 0 | { |
478 | 0 | memcpy(&nSectSize, abyHead, 4); |
479 | 0 | CPL_MSBPTR32(&nSectSize); |
480 | 0 | if (nSectSize >= 9 && nSectSize <= 100000 /* arbitrary upper limit */) |
481 | 0 | { |
482 | 0 | GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize)); |
483 | 0 | memcpy(pabyBody, abyHead, 5); |
484 | 0 | if (VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp) != |
485 | 0 | nSectSize - 5) |
486 | 0 | { |
487 | 0 | CPLDebug("GRIB", "Cannot read section 4"); |
488 | 0 | CPLFree(pabyBody); |
489 | 0 | return; |
490 | 0 | } |
491 | | |
492 | 0 | GUInt16 nCoordCount = 0; |
493 | 0 | memcpy(&nCoordCount, pabyBody + 6 - 1, 2); |
494 | 0 | CPL_MSBPTR16(&nCoordCount); |
495 | |
|
496 | 0 | GUInt16 nPDTN = 0; |
497 | 0 | memcpy(&nPDTN, pabyBody + 8 - 1, 2); |
498 | 0 | CPL_MSBPTR16(&nPDTN); |
499 | |
|
500 | 0 | GDALRasterBand::SetMetadataItem("GRIB_PDS_PDTN", |
501 | 0 | CPLString().Printf("%d", nPDTN)); |
502 | 0 | m_nPDTN = nPDTN; |
503 | |
|
504 | 0 | CPLString osOctet; |
505 | 0 | const int nTemplateFoundByteCount = |
506 | 0 | nSectSize - 9U >= nCoordCount * 4U |
507 | 0 | ? static_cast<int>(nSectSize - 9 - nCoordCount * 4) |
508 | 0 | : 0; |
509 | 0 | for (int i = 0; i < nTemplateFoundByteCount; i++) |
510 | 0 | { |
511 | 0 | char szByte[10] = {'\0'}; |
512 | |
|
513 | 0 | if (i == 0) |
514 | 0 | snprintf(szByte, sizeof(szByte), "%d", pabyBody[i + 9]); |
515 | 0 | else |
516 | 0 | snprintf(szByte, sizeof(szByte), " %d", pabyBody[i + 9]); |
517 | 0 | osOctet += szByte; |
518 | 0 | } |
519 | |
|
520 | 0 | GDALRasterBand::SetMetadataItem("GRIB_PDS_TEMPLATE_NUMBERS", |
521 | 0 | osOctet); |
522 | |
|
523 | 0 | g2int iofst = 0; |
524 | 0 | g2int pdsnum = 0; |
525 | 0 | g2int *pdstempl = nullptr; |
526 | 0 | g2int mappdslen = 0; |
527 | 0 | g2float *coordlist = nullptr; |
528 | 0 | g2int numcoord = 0; |
529 | 0 | if (getpdsindex(nPDTN) < 0) |
530 | 0 | { |
531 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
532 | 0 | "Template 4.%d is not recognized currently", nPDTN); |
533 | 0 | } |
534 | 0 | else if (g2_unpack4(pabyBody, nSectSize, &iofst, &pdsnum, &pdstempl, |
535 | 0 | &mappdslen, &coordlist, &numcoord) == 0) |
536 | 0 | { |
537 | 0 | gtemplate *mappds = extpdstemplate(pdsnum, pdstempl); |
538 | 0 | if (mappds) |
539 | 0 | { |
540 | 0 | int nTemplateByteCount = 0; |
541 | 0 | for (int i = 0; i < mappds->maplen; i++) |
542 | 0 | nTemplateByteCount += abs(mappds->map[i]); |
543 | 0 | for (int i = 0; i < mappds->extlen; i++) |
544 | 0 | nTemplateByteCount += abs(mappds->ext[i]); |
545 | 0 | if (nTemplateByteCount == nTemplateFoundByteCount) |
546 | 0 | { |
547 | 0 | CPLString osValues; |
548 | 0 | for (g2int i = 0; i < mappds->maplen + mappds->extlen; |
549 | 0 | i++) |
550 | 0 | { |
551 | 0 | if (i > 0) |
552 | 0 | osValues += " "; |
553 | 0 | const int nEltSize = |
554 | 0 | (i < mappds->maplen) |
555 | 0 | ? mappds->map[i] |
556 | 0 | : mappds->ext[i - mappds->maplen]; |
557 | 0 | if (nEltSize == 4) |
558 | 0 | { |
559 | 0 | m_anPDSTemplateAssembledValues.push_back( |
560 | 0 | static_cast<GUInt32>(pdstempl[i])); |
561 | 0 | osValues += CPLSPrintf( |
562 | 0 | "%u", static_cast<GUInt32>(pdstempl[i])); |
563 | 0 | } |
564 | 0 | else |
565 | 0 | { |
566 | 0 | m_anPDSTemplateAssembledValues.push_back( |
567 | 0 | pdstempl[i]); |
568 | 0 | osValues += CPLSPrintf("%d", pdstempl[i]); |
569 | 0 | } |
570 | 0 | } |
571 | 0 | GDALRasterBand::SetMetadataItem( |
572 | 0 | "GRIB_PDS_TEMPLATE_ASSEMBLED_VALUES", osValues); |
573 | 0 | } |
574 | 0 | else |
575 | 0 | { |
576 | 0 | CPLDebug( |
577 | 0 | "GRIB", |
578 | 0 | "Cannot expose GRIB_PDS_TEMPLATE_ASSEMBLED_VALUES " |
579 | 0 | "as we would expect %d bytes from the " |
580 | 0 | "tables, but %d are available", |
581 | 0 | nTemplateByteCount, nTemplateFoundByteCount); |
582 | 0 | } |
583 | |
|
584 | 0 | free(mappds->ext); |
585 | 0 | free(mappds); |
586 | 0 | } |
587 | 0 | } |
588 | 0 | free(pdstempl); |
589 | 0 | free(coordlist); |
590 | |
|
591 | 0 | CPLFree(pabyBody); |
592 | |
|
593 | 0 | FindNoDataGrib2(false); |
594 | 0 | } |
595 | 0 | else |
596 | 0 | { |
597 | 0 | CPLDebug("GRIB", "Invalid section size for section %d", 4); |
598 | 0 | } |
599 | 0 | } |
600 | 0 | } |
601 | | |
602 | | /************************************************************************/ |
603 | | /* FindNoDataGrib2() */ |
604 | | /************************************************************************/ |
605 | | |
606 | | void GRIBRasterBand::FindNoDataGrib2(bool bSeekToStart) |
607 | 0 | { |
608 | | // There is no easy way in the degrib API to retrieve the nodata value |
609 | | // without decompressing the data point section (which is slow), so |
610 | | // retrieve nodata value by parsing section 5 (Data Representation Section) |
611 | | // We also check section 6 to see if there is a bitmap |
612 | 0 | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
613 | 0 | CPLAssert(m_nGribVersion == 2); |
614 | |
|
615 | 0 | if (m_bHasLookedForNoData) |
616 | 0 | return; |
617 | 0 | m_bHasLookedForNoData = true; |
618 | |
|
619 | 0 | if (bSeekToStart) |
620 | 0 | { |
621 | | // Skip over section 0 |
622 | 0 | VSIFSeekL(poGDS->fp, start + 16, SEEK_SET); |
623 | 0 | } |
624 | |
|
625 | 0 | GByte abyHead[5] = {0}; |
626 | 0 | VSIFReadL(abyHead, 5, 1, poGDS->fp); |
627 | | |
628 | | // Skip to section 5 |
629 | 0 | GUInt32 nSectSize = 0; |
630 | 0 | while (abyHead[4] != 5) |
631 | 0 | { |
632 | 0 | memcpy(&nSectSize, abyHead, 4); |
633 | 0 | CPL_MSBPTR32(&nSectSize); |
634 | |
|
635 | 0 | if (nSectSize < 5 || |
636 | 0 | VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR) != 0 || |
637 | 0 | VSIFReadL(abyHead, 5, 1, poGDS->fp) != 1) |
638 | 0 | break; |
639 | 0 | } |
640 | | |
641 | | // See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_sect5.shtml |
642 | 0 | if (abyHead[4] == 5) |
643 | 0 | { |
644 | 0 | memcpy(&nSectSize, abyHead, 4); |
645 | 0 | CPL_MSBPTR32(&nSectSize); |
646 | 0 | if (nSectSize >= 11 && nSectSize <= 100000 /* arbitrary upper limit */) |
647 | 0 | { |
648 | 0 | GByte *pabyBody = static_cast<GByte *>(CPLMalloc(nSectSize)); |
649 | 0 | memcpy(pabyBody, abyHead, 5); |
650 | 0 | VSIFReadL(pabyBody + 5, 1, nSectSize - 5, poGDS->fp); |
651 | |
|
652 | 0 | GUInt16 nDRTN = 0; |
653 | 0 | memcpy(&nDRTN, pabyBody + 10 - 1, 2); |
654 | 0 | CPL_MSBPTR16(&nDRTN); |
655 | |
|
656 | 0 | GDALRasterBand::SetMetadataItem("DRS_DRTN", CPLSPrintf("%d", nDRTN), |
657 | 0 | "GRIB"); |
658 | 0 | if ((nDRTN == GS5_SIMPLE || nDRTN == GS5_CMPLX || |
659 | 0 | nDRTN == GS5_CMPLXSEC || nDRTN == GS5_JPEG2000 || |
660 | 0 | nDRTN == GS5_PNG) && |
661 | 0 | nSectSize >= 20) |
662 | 0 | { |
663 | 0 | float fRef; |
664 | 0 | memcpy(&fRef, pabyBody + 12 - 1, 4); |
665 | 0 | CPL_MSBPTR32(&fRef); |
666 | 0 | GDALRasterBand::SetMetadataItem( |
667 | 0 | "DRS_REF_VALUE", CPLSPrintf("%.10f", fRef), "GRIB"); |
668 | |
|
669 | 0 | GUInt16 nBinaryScaleFactorUnsigned; |
670 | 0 | memcpy(&nBinaryScaleFactorUnsigned, pabyBody + 16 - 1, 2); |
671 | 0 | CPL_MSBPTR16(&nBinaryScaleFactorUnsigned); |
672 | 0 | const int nBSF = |
673 | 0 | (nBinaryScaleFactorUnsigned & 0x8000) |
674 | 0 | ? -static_cast<int>(nBinaryScaleFactorUnsigned & 0x7FFF) |
675 | 0 | : static_cast<int>(nBinaryScaleFactorUnsigned); |
676 | 0 | GDALRasterBand::SetMetadataItem("DRS_BINARY_SCALE_FACTOR", |
677 | 0 | CPLSPrintf("%d", nBSF), "GRIB"); |
678 | |
|
679 | 0 | GUInt16 nDecimalScaleFactorUnsigned; |
680 | 0 | memcpy(&nDecimalScaleFactorUnsigned, pabyBody + 18 - 1, 2); |
681 | 0 | CPL_MSBPTR16(&nDecimalScaleFactorUnsigned); |
682 | 0 | const int nDSF = |
683 | 0 | (nDecimalScaleFactorUnsigned & 0x8000) |
684 | 0 | ? -static_cast<int>(nDecimalScaleFactorUnsigned & |
685 | 0 | 0x7FFF) |
686 | 0 | : static_cast<int>(nDecimalScaleFactorUnsigned); |
687 | 0 | GDALRasterBand::SetMetadataItem("DRS_DECIMAL_SCALE_FACTOR", |
688 | 0 | CPLSPrintf("%d", nDSF), "GRIB"); |
689 | |
|
690 | 0 | const int nBits = pabyBody[20 - 1]; |
691 | 0 | GDALRasterBand::SetMetadataItem( |
692 | 0 | "DRS_NBITS", CPLSPrintf("%d", nBits), "GRIB"); |
693 | 0 | } |
694 | | |
695 | | // 2 = Grid Point Data - Complex Packing |
696 | | // 3 = Grid Point Data - Complex Packing and Spatial Differencing |
697 | 0 | if ((nDRTN == GS5_CMPLX || nDRTN == GS5_CMPLXSEC) && |
698 | 0 | nSectSize >= 31) |
699 | 0 | { |
700 | 0 | const int nMiss = pabyBody[23 - 1]; |
701 | 0 | if (nMiss == 1 || nMiss == 2) |
702 | 0 | { |
703 | 0 | const int original_field_type = pabyBody[21 - 1]; |
704 | 0 | if (original_field_type == 0) // Floating Point |
705 | 0 | { |
706 | 0 | float fTemp; |
707 | 0 | memcpy(&fTemp, &pabyBody[24 - 1], 4); |
708 | 0 | CPL_MSBPTR32(&fTemp); |
709 | 0 | m_dfNoData = fTemp; |
710 | 0 | m_bHasNoData = true; |
711 | 0 | if (nMiss == 2) |
712 | 0 | { |
713 | 0 | memcpy(&fTemp, &pabyBody[28 - 1], 4); |
714 | 0 | CPL_MSBPTR32(&fTemp); |
715 | 0 | CPLDebug("GRIB", |
716 | 0 | "Secondary missing value also set for " |
717 | 0 | "band %d : %f", |
718 | 0 | nBand, fTemp); |
719 | 0 | } |
720 | 0 | } |
721 | 0 | else if (original_field_type == 1) // Integer |
722 | 0 | { |
723 | 0 | int iTemp; |
724 | 0 | memcpy(&iTemp, &pabyBody[24 - 1], 4); |
725 | 0 | CPL_MSBPTR32(&iTemp); |
726 | 0 | m_dfNoData = iTemp; |
727 | 0 | m_bHasNoData = true; |
728 | 0 | if (nMiss == 2) |
729 | 0 | { |
730 | 0 | memcpy(&iTemp, &pabyBody[28 - 1], 4); |
731 | 0 | CPL_MSBPTR32(&iTemp); |
732 | 0 | CPLDebug("GRIB", |
733 | 0 | "Secondary missing value also set for " |
734 | 0 | "band %d : %d", |
735 | 0 | nBand, iTemp); |
736 | 0 | } |
737 | 0 | } |
738 | 0 | else |
739 | 0 | { |
740 | | // FIXME What to do? Blindly convert to float? |
741 | 0 | CPLDebug("GRIB", |
742 | 0 | "Complex Packing - Type of Original Field " |
743 | 0 | "Values for band %d: %u", |
744 | 0 | nBand, original_field_type); |
745 | 0 | } |
746 | 0 | } |
747 | 0 | } |
748 | |
|
749 | 0 | if (nDRTN == GS5_CMPLXSEC && nSectSize >= 48) |
750 | 0 | { |
751 | 0 | const int nOrder = pabyBody[48 - 1]; |
752 | 0 | GDALRasterBand::SetMetadataItem( |
753 | 0 | "DRS_SPATIAL_DIFFERENCING_ORDER", CPLSPrintf("%d", nOrder), |
754 | 0 | "GRIB"); |
755 | 0 | } |
756 | |
|
757 | 0 | CPLFree(pabyBody); |
758 | 0 | } |
759 | 0 | else if (nSectSize > 5) |
760 | 0 | { |
761 | 0 | VSIFSeekL(poGDS->fp, nSectSize - 5, SEEK_CUR); |
762 | 0 | } |
763 | 0 | } |
764 | |
|
765 | 0 | if (!m_bHasNoData) |
766 | 0 | { |
767 | | // Check bitmap section |
768 | 0 | GByte abySection6[6] = {0}; |
769 | 0 | VSIFReadL(abySection6, 6, 1, poGDS->fp); |
770 | | // Is there a bitmap ? |
771 | 0 | if (abySection6[4] == 6 && abySection6[5] == 0) |
772 | 0 | { |
773 | 0 | m_dfNoData = 9999.0; // Same value as in metaparse.cpp:ParseGrid() |
774 | 0 | m_bHasNoData = true; |
775 | 0 | } |
776 | 0 | } |
777 | 0 | } |
778 | | |
779 | | /************************************************************************/ |
780 | | /* GetDescription() */ |
781 | | /************************************************************************/ |
782 | | |
783 | | const char *GRIBRasterBand::GetDescription() const |
784 | 0 | { |
785 | 0 | if (longFstLevel == nullptr) |
786 | 0 | return GDALPamRasterBand::GetDescription(); |
787 | | |
788 | 0 | return longFstLevel; |
789 | 0 | } |
790 | | |
791 | | /************************************************************************/ |
792 | | /* LoadData() */ |
793 | | /************************************************************************/ |
794 | | |
795 | | CPLErr GRIBRasterBand::LoadData() |
796 | | |
797 | 0 | { |
798 | 0 | if (!m_Grib_Data) |
799 | 0 | { |
800 | 0 | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
801 | |
|
802 | 0 | if (poGDS->bCacheOnlyOneBand) |
803 | 0 | { |
804 | | // In "one-band-at-a-time" strategy, if the last recently used |
805 | | // band is not that one, uncache it. We could use a smarter strategy |
806 | | // based on a LRU, but that's a bit overkill for now. |
807 | 0 | poGDS->poLastUsedBand->UncacheData(); |
808 | 0 | poGDS->nCachedBytes = 0; |
809 | 0 | } |
810 | 0 | else |
811 | 0 | { |
812 | | // Once we have cached more than nCachedBytesThreshold bytes, we |
813 | | // will switch to "one-band-at-a-time" strategy, instead of caching |
814 | | // all bands that have been accessed. |
815 | 0 | if (poGDS->nCachedBytes > poGDS->nCachedBytesThreshold) |
816 | 0 | { |
817 | 0 | GUIntBig nMinCacheSize = |
818 | 0 | 1 + static_cast<GUIntBig>(poGDS->nRasterXSize) * |
819 | 0 | poGDS->nRasterYSize * poGDS->nBands * |
820 | 0 | GDALGetDataTypeSizeBytes(eDataType) / 1024 / 1024; |
821 | 0 | CPLDebug("GRIB", |
822 | 0 | "Maximum band cache size reached for this dataset. " |
823 | 0 | "Caching only one band at a time from now, which can " |
824 | 0 | "negatively affect performance. Consider " |
825 | 0 | "increasing GRIB_CACHEMAX to a higher value (in MB), " |
826 | 0 | "at least " CPL_FRMT_GUIB " in that instance", |
827 | 0 | nMinCacheSize); |
828 | 0 | for (int i = 0; i < poGDS->nBands; i++) |
829 | 0 | { |
830 | 0 | reinterpret_cast<GRIBRasterBand *>( |
831 | 0 | poGDS->GetRasterBand(i + 1)) |
832 | 0 | ->UncacheData(); |
833 | 0 | } |
834 | 0 | poGDS->nCachedBytes = 0; |
835 | 0 | poGDS->bCacheOnlyOneBand = TRUE; |
836 | 0 | } |
837 | 0 | } |
838 | | |
839 | | // we don't seem to have any way to detect errors in this! |
840 | 0 | if (m_Grib_MetaData != nullptr) |
841 | 0 | { |
842 | 0 | MetaFree(m_Grib_MetaData); |
843 | 0 | delete m_Grib_MetaData; |
844 | 0 | m_Grib_MetaData = nullptr; |
845 | 0 | } |
846 | 0 | ReadGribData(poGDS->fp, start, subgNum, &m_Grib_Data, &m_Grib_MetaData); |
847 | 0 | if (!m_Grib_Data) |
848 | 0 | { |
849 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Out of memory."); |
850 | 0 | if (m_Grib_MetaData != nullptr) |
851 | 0 | { |
852 | 0 | MetaFree(m_Grib_MetaData); |
853 | 0 | delete m_Grib_MetaData; |
854 | 0 | m_Grib_MetaData = nullptr; |
855 | 0 | } |
856 | 0 | return CE_Failure; |
857 | 0 | } |
858 | | |
859 | | // Check the band matches the dataset as a whole, size wise. (#3246) |
860 | 0 | nGribDataXSize = m_Grib_MetaData->gds.Nx; |
861 | 0 | nGribDataYSize = m_Grib_MetaData->gds.Ny; |
862 | 0 | if (nGribDataXSize <= 0 || nGribDataYSize <= 0) |
863 | 0 | { |
864 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
865 | 0 | "Band %d of GRIB dataset is %dx%d.", nBand, nGribDataXSize, |
866 | 0 | nGribDataYSize); |
867 | 0 | MetaFree(m_Grib_MetaData); |
868 | 0 | delete m_Grib_MetaData; |
869 | 0 | m_Grib_MetaData = nullptr; |
870 | 0 | return CE_Failure; |
871 | 0 | } |
872 | | |
873 | 0 | poGDS->nCachedBytes += static_cast<GIntBig>(nGribDataXSize) * |
874 | 0 | nGribDataYSize * sizeof(double); |
875 | 0 | poGDS->poLastUsedBand = this; |
876 | |
|
877 | 0 | if (nGribDataXSize != nRasterXSize || nGribDataYSize != nRasterYSize) |
878 | 0 | { |
879 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
880 | 0 | "Band %d of GRIB dataset is %dx%d, while the first band " |
881 | 0 | "and dataset is %dx%d. Georeferencing of band %d may " |
882 | 0 | "be incorrect, and data access may be incomplete.", |
883 | 0 | nBand, nGribDataXSize, nGribDataYSize, nRasterXSize, |
884 | 0 | nRasterYSize, nBand); |
885 | 0 | } |
886 | 0 | } |
887 | | |
888 | 0 | return CE_None; |
889 | 0 | } |
890 | | |
891 | | /************************************************************************/ |
892 | | /* IsGdalinfoInteractive() */ |
893 | | /************************************************************************/ |
894 | | |
895 | | #ifdef BUILD_APPS |
896 | | static bool IsGdalinfoInteractive() |
897 | | { |
898 | | static const bool bIsGdalinfoInteractive = []() |
899 | | { |
900 | | if (CPLIsInteractive(stdout)) |
901 | | { |
902 | | std::string osPath; |
903 | | osPath.resize(1024); |
904 | | if (CPLGetExecPath(&osPath[0], static_cast<int>(osPath.size()))) |
905 | | { |
906 | | osPath = CPLGetBasenameSafe(osPath.c_str()); |
907 | | } |
908 | | return osPath == "gdalinfo"; |
909 | | } |
910 | | return false; |
911 | | }(); |
912 | | return bIsGdalinfoInteractive; |
913 | | } |
914 | | #endif |
915 | | |
916 | | /************************************************************************/ |
917 | | /* GetMetaData() */ |
918 | | /************************************************************************/ |
919 | | char **GRIBRasterBand::GetMetadata(const char *pszDomain) |
920 | 0 | { |
921 | 0 | FindMetaData(); |
922 | 0 | if ((pszDomain == nullptr || pszDomain[0] == 0) && m_nGribVersion == 2 && |
923 | 0 | CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON"))) |
924 | 0 | { |
925 | | #ifdef BUILD_APPS |
926 | | // Detect slow execution of e.g. |
927 | | // "gdalinfo /vsis3/noaa-hrrr-bdp-pds/hrrr.20220804/conus/hrrr.t00z.wrfsfcf01.grib2" |
928 | | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
929 | | if (poGDS->m_bSideCarIdxUsed && !poGDS->m_bWarnedGdalinfoNomd && |
930 | | poGDS->GetRasterCount() > 10 && |
931 | | !VSIIsLocal(poGDS->GetDescription()) && IsGdalinfoInteractive()) |
932 | | { |
933 | | if (poGDS->m_nFirstMetadataQueriedTimeStamp) |
934 | | { |
935 | | if (time(nullptr) - poGDS->m_nFirstMetadataQueriedTimeStamp > 2) |
936 | | { |
937 | | poGDS->m_bWarnedGdalinfoNomd = true; |
938 | | |
939 | | CPLError( |
940 | | CE_Warning, CPLE_AppDefined, |
941 | | "If metadata does not matter, faster result could be " |
942 | | "obtained by adding the -nomd switch to gdalinfo"); |
943 | | } |
944 | | } |
945 | | else |
946 | | { |
947 | | poGDS->m_nFirstMetadataQueriedTimeStamp = time(nullptr); |
948 | | } |
949 | | } |
950 | | #endif |
951 | |
|
952 | 0 | FindPDSTemplateGRIB2(); |
953 | 0 | } |
954 | 0 | return GDALPamRasterBand::GetMetadata(pszDomain); |
955 | 0 | } |
956 | | |
957 | | /************************************************************************/ |
958 | | /* GetMetaDataItem() */ |
959 | | /************************************************************************/ |
960 | | const char *GRIBRasterBand::GetMetadataItem(const char *pszName, |
961 | | const char *pszDomain) |
962 | 0 | { |
963 | 0 | if (!((!pszDomain || pszDomain[0] == 0) && |
964 | 0 | (EQUAL(pszName, "STATISTICS_MINIMUM") || |
965 | 0 | EQUAL(pszName, "STATISTICS_MAXIMUM")))) |
966 | 0 | { |
967 | 0 | FindMetaData(); |
968 | 0 | if (m_nGribVersion == 2 && |
969 | 0 | CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON"))) |
970 | 0 | { |
971 | 0 | FindPDSTemplateGRIB2(); |
972 | 0 | } |
973 | 0 | } |
974 | 0 | return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain); |
975 | 0 | } |
976 | | |
977 | | /************************************************************************/ |
978 | | /* IReadBlock() */ |
979 | | /************************************************************************/ |
980 | | |
981 | | CPLErr GRIBRasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff, |
982 | | void *pImage) |
983 | | |
984 | 0 | { |
985 | 0 | CPLErr eErr = LoadData(); |
986 | 0 | if (eErr != CE_None) |
987 | 0 | return eErr; |
988 | | |
989 | 0 | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
990 | | |
991 | | // The image as read is always upside down to our normal |
992 | | // orientation so we need to effectively flip it at this |
993 | | // point. We also need to deal with bands that are a different |
994 | | // size than the dataset as a whole. |
995 | |
|
996 | 0 | if (nGribDataXSize == nRasterXSize && nGribDataYSize == nRasterYSize && |
997 | 0 | poGDS->nSplitAndSwapColumn == 0) |
998 | 0 | { |
999 | | // Simple 1:1 case. |
1000 | 0 | memcpy(pImage, |
1001 | 0 | m_Grib_Data + static_cast<size_t>(nRasterXSize) * |
1002 | 0 | (nRasterYSize - nBlockYOff - 1), |
1003 | 0 | nRasterXSize * sizeof(double)); |
1004 | |
|
1005 | 0 | return CE_None; |
1006 | 0 | } |
1007 | | |
1008 | 0 | memset(pImage, 0, sizeof(double) * nRasterXSize); |
1009 | |
|
1010 | 0 | if (nBlockYOff >= nGribDataYSize) // Off image? |
1011 | 0 | return CE_None; |
1012 | | |
1013 | 0 | int nSplitAndSwapColumn = poGDS->nSplitAndSwapColumn; |
1014 | 0 | if (nRasterXSize != nGribDataXSize) |
1015 | 0 | nSplitAndSwapColumn = 0; |
1016 | |
|
1017 | 0 | const int nCopyWords = std::min(nRasterXSize, nGribDataXSize); |
1018 | |
|
1019 | 0 | memcpy(pImage, |
1020 | 0 | m_Grib_Data + |
1021 | 0 | static_cast<size_t>(nGribDataXSize) * |
1022 | 0 | (nGribDataYSize - nBlockYOff - 1) + |
1023 | 0 | nSplitAndSwapColumn, |
1024 | 0 | (nCopyWords - nSplitAndSwapColumn) * sizeof(double)); |
1025 | |
|
1026 | 0 | if (nSplitAndSwapColumn > 0) |
1027 | 0 | memcpy(reinterpret_cast<void *>(reinterpret_cast<double *>(pImage) + |
1028 | 0 | nCopyWords - nSplitAndSwapColumn), |
1029 | 0 | m_Grib_Data + static_cast<size_t>(nGribDataXSize) * |
1030 | 0 | (nGribDataYSize - nBlockYOff - 1), |
1031 | 0 | nSplitAndSwapColumn * sizeof(double)); |
1032 | |
|
1033 | 0 | return CE_None; |
1034 | 0 | } |
1035 | | |
1036 | | /************************************************************************/ |
1037 | | /* GetNoDataValue() */ |
1038 | | /************************************************************************/ |
1039 | | |
1040 | | double GRIBRasterBand::GetNoDataValue(int *pbSuccess) |
1041 | 0 | { |
1042 | 0 | if (m_bHasLookedForNoData) |
1043 | 0 | { |
1044 | 0 | if (pbSuccess) |
1045 | 0 | *pbSuccess = m_bHasNoData; |
1046 | 0 | return m_dfNoData; |
1047 | 0 | } |
1048 | | |
1049 | 0 | m_bHasLookedForNoData = true; |
1050 | 0 | if (m_Grib_MetaData == nullptr) |
1051 | 0 | { |
1052 | 0 | GRIBDataset *poGDS = static_cast<GRIBDataset *>(poDS); |
1053 | |
|
1054 | | #ifdef BUILD_APPS |
1055 | | // Detect slow execution of e.g. |
1056 | | // "gdalinfo /vsis3/noaa-hrrr-bdp-pds/hrrr.20220804/conus/hrrr.t00z.wrfsfcf01.grib2" |
1057 | | |
1058 | | if (poGDS->m_bSideCarIdxUsed && !poGDS->m_bWarnedGdalinfoNonodata && |
1059 | | poGDS->GetRasterCount() > 10 && |
1060 | | !VSIIsLocal(poGDS->GetDescription()) && IsGdalinfoInteractive()) |
1061 | | { |
1062 | | if (poGDS->m_nFirstNodataQueriedTimeStamp) |
1063 | | { |
1064 | | if (time(nullptr) - poGDS->m_nFirstNodataQueriedTimeStamp > 2) |
1065 | | { |
1066 | | poGDS->m_bWarnedGdalinfoNonodata = true; |
1067 | | |
1068 | | CPLError(CE_Warning, CPLE_AppDefined, |
1069 | | "If nodata value does not matter, faster result " |
1070 | | "could be obtained by adding the -nonodata switch " |
1071 | | "to gdalinfo"); |
1072 | | } |
1073 | | } |
1074 | | else |
1075 | | { |
1076 | | poGDS->m_nFirstNodataQueriedTimeStamp = time(nullptr); |
1077 | | } |
1078 | | } |
1079 | | #endif |
1080 | |
|
1081 | 0 | ReadGribData(poGDS->fp, start, subgNum, nullptr, &m_Grib_MetaData); |
1082 | 0 | if (m_Grib_MetaData == nullptr) |
1083 | 0 | { |
1084 | 0 | m_bHasNoData = false; |
1085 | 0 | m_dfNoData = 0; |
1086 | 0 | if (pbSuccess) |
1087 | 0 | *pbSuccess = m_bHasNoData; |
1088 | 0 | return m_dfNoData; |
1089 | 0 | } |
1090 | 0 | } |
1091 | | |
1092 | 0 | if (m_Grib_MetaData->gridAttrib.f_miss == 0) |
1093 | 0 | { |
1094 | 0 | m_bHasNoData = false; |
1095 | 0 | m_dfNoData = 0; |
1096 | 0 | if (pbSuccess) |
1097 | 0 | *pbSuccess = m_bHasNoData; |
1098 | 0 | return m_dfNoData; |
1099 | 0 | } |
1100 | | |
1101 | 0 | if (m_Grib_MetaData->gridAttrib.f_miss == 2) |
1102 | 0 | { |
1103 | | // What TODO? |
1104 | 0 | CPLDebug("GRIB", "Secondary missing value also set for band %d : %f", |
1105 | 0 | nBand, m_Grib_MetaData->gridAttrib.missSec); |
1106 | 0 | } |
1107 | |
|
1108 | 0 | m_bHasNoData = true; |
1109 | 0 | m_dfNoData = m_Grib_MetaData->gridAttrib.missPri; |
1110 | 0 | if (pbSuccess) |
1111 | 0 | *pbSuccess = m_bHasNoData; |
1112 | 0 | return m_dfNoData; |
1113 | 0 | } |
1114 | | |
1115 | | /************************************************************************/ |
1116 | | /* ReadGribData() */ |
1117 | | /************************************************************************/ |
1118 | | |
1119 | | void GRIBRasterBand::ReadGribData(VSILFILE *fp, vsi_l_offset start, int subgNum, |
1120 | | double **data, grib_MetaData **metaData) |
1121 | 819 | { |
1122 | | // Initialization, for calling the ReadGrib2Record function. |
1123 | 819 | sInt4 f_endMsg = 1; // 1 if we read the last grid in a GRIB message, or we |
1124 | | // haven't read any messages. |
1125 | | // int subgNum = 0; // The subgrid in the message that we are interested in. |
1126 | 819 | sChar f_unit = 2; // None = 0, English = 1, Metric = 2 |
1127 | 819 | double majEarth = 0.0; // -radEarth if < 6000 ignore, otherwise use this |
1128 | | // to override the radEarth in the GRIB1 or GRIB2 |
1129 | | // message. Needed because NCEP uses 6371.2 but |
1130 | | // GRIB1 could only state 6367.47. |
1131 | 819 | double minEarth = 0.0; // -minEarth if < 6000 ignore, otherwise use this |
1132 | | // to override the minEarth in the GRIB1 or GRIB2 |
1133 | | // message. |
1134 | 819 | sChar f_SimpleVer = 4; // Which version of the simple NDFD Weather table |
1135 | | // to use. (1 is 6/2003) (2 is 1/2004) (3 is |
1136 | | // 2/2004) (4 is 11/2004) (default 4) |
1137 | 819 | LatLon lwlf; // Lower left corner (cookie slicing) -lwlf |
1138 | 819 | LatLon uprt; // Upper right corner (cookie slicing) -uprt |
1139 | 819 | IS_dataType is; // Un-parsed meta data for this GRIB2 message. As well |
1140 | | // as some memory used by the unpacker. |
1141 | | |
1142 | 819 | lwlf.lat = -100; // lat == -100 instructs the GRIB decoder that we don't |
1143 | | // want a subgrid |
1144 | | |
1145 | 819 | IS_Init(&is); |
1146 | | |
1147 | 819 | const char *pszGribNormalizeUnits = |
1148 | 819 | CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES"); |
1149 | 819 | if (!CPLTestBool(pszGribNormalizeUnits)) |
1150 | 0 | f_unit = 0; // Do not normalize units to metric. |
1151 | | |
1152 | 819 | start = FindTrueStart(fp, start); |
1153 | | // Read GRIB message from file position "start". |
1154 | 819 | VSIFSeekL(fp, start, SEEK_SET); |
1155 | 819 | uInt4 grib_DataLen = 0; // Size of Grib_Data. |
1156 | 819 | *metaData = new grib_MetaData(); |
1157 | 819 | MetaInit(*metaData); |
1158 | 819 | const int simpWWA = 0; // seem to be unused in degrib |
1159 | 819 | ReadGrib2Record(fp, f_unit, data, &grib_DataLen, *metaData, &is, subgNum, |
1160 | 819 | majEarth, minEarth, f_SimpleVer, simpWWA, &f_endMsg, &lwlf, |
1161 | 819 | &uprt); |
1162 | | |
1163 | | // No intention to show errors, just swallow it and free the memory. |
1164 | 819 | char *errMsg = errSprintf(nullptr); |
1165 | 819 | if (errMsg != nullptr) |
1166 | 819 | CPLDebug("GRIB", "%s", errMsg); |
1167 | 819 | free(errMsg); |
1168 | 819 | IS_Free(&is); |
1169 | 819 | } |
1170 | | |
1171 | | /************************************************************************/ |
1172 | | /* UncacheData() */ |
1173 | | /************************************************************************/ |
1174 | | |
1175 | | void GRIBRasterBand::UncacheData() |
1176 | 13.9k | { |
1177 | 13.9k | if (m_Grib_Data) |
1178 | 0 | free(m_Grib_Data); |
1179 | 13.9k | m_Grib_Data = nullptr; |
1180 | 13.9k | if (m_Grib_MetaData) |
1181 | 217 | { |
1182 | 217 | MetaFree(m_Grib_MetaData); |
1183 | 217 | delete m_Grib_MetaData; |
1184 | 217 | } |
1185 | 13.9k | m_Grib_MetaData = nullptr; |
1186 | 13.9k | } |
1187 | | |
1188 | | /************************************************************************/ |
1189 | | /* ~GRIBRasterBand() */ |
1190 | | /************************************************************************/ |
1191 | | |
1192 | | GRIBRasterBand::~GRIBRasterBand() |
1193 | 13.9k | { |
1194 | 13.9k | if (longFstLevel != nullptr) |
1195 | 13.9k | CPLFree(longFstLevel); |
1196 | 13.9k | UncacheData(); |
1197 | 13.9k | } |
1198 | | |
1199 | 1.71k | gdal::grib::InventoryWrapper::~InventoryWrapper() = default; |
1200 | | |
1201 | | /************************************************************************/ |
1202 | | /* InventoryWrapperGrib */ |
1203 | | /************************************************************************/ |
1204 | | class InventoryWrapperGrib : public gdal::grib::InventoryWrapper |
1205 | | { |
1206 | | public: |
1207 | 1.71k | explicit InventoryWrapperGrib(VSILFILE *fp) : gdal::grib::InventoryWrapper() |
1208 | 1.71k | { |
1209 | 1.71k | result_ = GRIB2Inventory(fp, &inv_, &inv_len_, 0 /* all messages */, |
1210 | 1.71k | &num_messages_); |
1211 | 1.71k | } |
1212 | | |
1213 | | ~InventoryWrapperGrib() override; |
1214 | | }; |
1215 | | |
1216 | | InventoryWrapperGrib::~InventoryWrapperGrib() |
1217 | 1.71k | { |
1218 | 1.71k | if (inv_ == nullptr) |
1219 | 459 | return; |
1220 | 51.9k | for (uInt4 i = 0; i < inv_len_; i++) |
1221 | 50.7k | { |
1222 | 50.7k | GRIB2InventoryFree(inv_ + i); |
1223 | 50.7k | } |
1224 | 1.25k | free(inv_); |
1225 | 1.25k | } |
1226 | | |
1227 | | /************************************************************************/ |
1228 | | /* InventoryWrapperSidecar */ |
1229 | | /************************************************************************/ |
1230 | | |
1231 | | class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper |
1232 | | { |
1233 | | public: |
1234 | | explicit InventoryWrapperSidecar(VSILFILE *fp, uint64_t nStartOffset, |
1235 | | int64_t nSize) |
1236 | 0 | : gdal::grib::InventoryWrapper() |
1237 | 0 | { |
1238 | 0 | result_ = -1; |
1239 | 0 | VSIFSeekL(fp, 0, SEEK_END); |
1240 | 0 | size_t length = static_cast<size_t>(VSIFTellL(fp)); |
1241 | 0 | if (length > 4 * 1024 * 1024) |
1242 | 0 | return; |
1243 | 0 | std::string osSidecar; |
1244 | 0 | osSidecar.resize(length); |
1245 | 0 | VSIFSeekL(fp, 0, SEEK_SET); |
1246 | 0 | if (VSIFReadL(&osSidecar[0], length, 1, fp) != 1) |
1247 | 0 | return; |
1248 | | |
1249 | 0 | const CPLStringList aosMsgs( |
1250 | 0 | CSLTokenizeString2(osSidecar.c_str(), "\n", |
1251 | 0 | CSLT_PRESERVEQUOTES | CSLT_STRIPLEADSPACES)); |
1252 | 0 | inv_ = static_cast<inventoryType *>( |
1253 | 0 | CPLCalloc(aosMsgs.size(), sizeof(inventoryType))); |
1254 | |
|
1255 | 0 | for (const char *pszMsg : aosMsgs) |
1256 | 0 | { |
1257 | | // We are parsing |
1258 | | // "msgNum[.subgNum]:start:dontcare:name1:name2:name3" For NOMADS: |
1259 | | // "msgNum[.subgNum]:start:reftime:var:level:time" |
1260 | 0 | const CPLStringList aosTokens(CSLTokenizeString2( |
1261 | 0 | pszMsg, ":", CSLT_PRESERVEQUOTES | CSLT_ALLOWEMPTYTOKENS)); |
1262 | 0 | CPLStringList aosNum; |
1263 | |
|
1264 | 0 | if (aosTokens.size() < 6) |
1265 | 0 | goto err_sidecar; |
1266 | | |
1267 | 0 | aosNum = CPLStringList(CSLTokenizeString2(aosTokens[0], ".", 0)); |
1268 | 0 | if (aosNum.size() < 1) |
1269 | 0 | goto err_sidecar; |
1270 | | |
1271 | | // FindMetaData will retrieve the correct version number |
1272 | 0 | char *endptr; |
1273 | 0 | strtol(aosNum[0], &endptr, 10); |
1274 | 0 | if (*endptr != 0) |
1275 | 0 | goto err_sidecar; |
1276 | | |
1277 | 0 | if (aosNum.size() < 2) |
1278 | 0 | inv_[inv_len_].subgNum = 0; |
1279 | 0 | else |
1280 | 0 | { |
1281 | 0 | auto subgNum = strtol(aosNum[1], &endptr, 10); |
1282 | 0 | if (*endptr != 0) |
1283 | 0 | goto err_sidecar; |
1284 | 0 | if (subgNum <= 0 || subgNum > 65536) |
1285 | 0 | goto err_sidecar; |
1286 | | // .idx file use a 1-based indexing, whereas DEGRIB uses a |
1287 | | // 0-based one |
1288 | 0 | subgNum--; |
1289 | 0 | inv_[inv_len_].subgNum = static_cast<unsigned short>(subgNum); |
1290 | 0 | } |
1291 | | |
1292 | 0 | inv_[inv_len_].start = strtoll(aosTokens[1], &endptr, 10); |
1293 | 0 | if (*endptr != 0) |
1294 | 0 | goto err_sidecar; |
1295 | | |
1296 | 0 | if (inv_[inv_len_].start < nStartOffset) |
1297 | 0 | continue; |
1298 | 0 | if (nSize > 0 && inv_[inv_len_].start >= nStartOffset + nSize) |
1299 | 0 | break; |
1300 | | |
1301 | 0 | inv_[inv_len_].start -= nStartOffset; |
1302 | |
|
1303 | 0 | inv_[inv_len_].unitName = nullptr; |
1304 | 0 | inv_[inv_len_].comment = nullptr; |
1305 | 0 | inv_[inv_len_].element = nullptr; |
1306 | 0 | inv_[inv_len_].shortFstLevel = nullptr; |
1307 | | // This is going into the description field -> |
1308 | | // the only one available before loading the metadata |
1309 | 0 | inv_[inv_len_].longFstLevel = VSIStrdup(CPLSPrintf( |
1310 | 0 | "%s:%s:%s", aosTokens[3], aosTokens[4], aosTokens[5])); |
1311 | 0 | ++inv_len_; |
1312 | |
|
1313 | 0 | continue; |
1314 | | |
1315 | 0 | err_sidecar: |
1316 | 0 | CPLDebug("GRIB", |
1317 | 0 | "Failed parsing sidecar entry '%s', " |
1318 | 0 | "falling back to constructing an inventory", |
1319 | 0 | pszMsg); |
1320 | 0 | return; |
1321 | 0 | } |
1322 | | |
1323 | 0 | result_ = inv_len_; |
1324 | 0 | } |
1325 | | |
1326 | | ~InventoryWrapperSidecar() override; |
1327 | | }; |
1328 | | |
1329 | | InventoryWrapperSidecar::~InventoryWrapperSidecar() |
1330 | 0 | { |
1331 | 0 | if (inv_ == nullptr) |
1332 | 0 | return; |
1333 | | |
1334 | 0 | for (unsigned i = 0; i < inv_len_; i++) |
1335 | 0 | VSIFree(inv_[i].longFstLevel); |
1336 | |
|
1337 | 0 | VSIFree(inv_); |
1338 | 0 | } |
1339 | | |
1340 | | /************************************************************************/ |
1341 | | /* ==================================================================== */ |
1342 | | /* GRIBDataset */ |
1343 | | /* ==================================================================== */ |
1344 | | /************************************************************************/ |
1345 | | |
1346 | | GRIBDataset::GRIBDataset() |
1347 | 1.71k | : fp(nullptr), nCachedBytes(0), |
1348 | | // Switch caching strategy once 100 MB threshold is reached. |
1349 | | // Why 100 MB? --> Why not. |
1350 | 1.71k | nCachedBytesThreshold(static_cast<GIntBig>(atoi( |
1351 | 1.71k | CPLGetConfigOption("GRIB_CACHEMAX", "100"))) * |
1352 | 1.71k | 1024 * 1024), |
1353 | 1.71k | bCacheOnlyOneBand(FALSE), nSplitAndSwapColumn(0), poLastUsedBand(nullptr) |
1354 | 1.71k | { |
1355 | 1.71k | adfGeoTransform[0] = 0.0; |
1356 | 1.71k | adfGeoTransform[1] = 1.0; |
1357 | 1.71k | adfGeoTransform[2] = 0.0; |
1358 | 1.71k | adfGeoTransform[3] = 0.0; |
1359 | 1.71k | adfGeoTransform[4] = 0.0; |
1360 | 1.71k | adfGeoTransform[5] = 1.0; |
1361 | 1.71k | } |
1362 | | |
1363 | | /************************************************************************/ |
1364 | | /* ~GRIBDataset() */ |
1365 | | /************************************************************************/ |
1366 | | |
1367 | | GRIBDataset::~GRIBDataset() |
1368 | | |
1369 | 1.71k | { |
1370 | 1.71k | FlushCache(true); |
1371 | 1.71k | if (fp != nullptr) |
1372 | 1.71k | VSIFCloseL(fp); |
1373 | 1.71k | } |
1374 | | |
1375 | | /************************************************************************/ |
1376 | | /* GetGeoTransform() */ |
1377 | | /************************************************************************/ |
1378 | | |
1379 | | CPLErr GRIBDataset::GetGeoTransform(double *padfTransform) |
1380 | | |
1381 | 0 | { |
1382 | 0 | memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6); |
1383 | 0 | return CE_None; |
1384 | 0 | } |
1385 | | |
1386 | | /************************************************************************/ |
1387 | | /* Inventory() */ |
1388 | | /************************************************************************/ |
1389 | | |
1390 | | std::unique_ptr<gdal::grib::InventoryWrapper> |
1391 | | GRIBDataset::Inventory(GDALOpenInfo *poOpenInfo) |
1392 | 1.71k | { |
1393 | 1.71k | std::unique_ptr<gdal::grib::InventoryWrapper> pInventories; |
1394 | | |
1395 | 1.71k | VSIFSeekL(fp, 0, SEEK_SET); |
1396 | 1.71k | std::string osSideCarFilename(poOpenInfo->pszFilename); |
1397 | 1.71k | uint64_t nStartOffset = 0; |
1398 | 1.71k | int64_t nSize = -1; |
1399 | 1.71k | if (STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/")) |
1400 | 0 | { |
1401 | 0 | const char *pszPtr = poOpenInfo->pszFilename + strlen("/vsisubfile/"); |
1402 | 0 | const char *pszComma = strchr(pszPtr, ','); |
1403 | 0 | if (pszComma) |
1404 | 0 | { |
1405 | 0 | const CPLStringList aosTokens(CSLTokenizeString2( |
1406 | 0 | std::string(pszPtr, pszComma - pszPtr).c_str(), "_", 0)); |
1407 | 0 | if (aosTokens.size() == 2) |
1408 | 0 | { |
1409 | 0 | nStartOffset = std::strtoull(aosTokens[0], nullptr, 10); |
1410 | 0 | nSize = std::strtoll(aosTokens[1], nullptr, 10); |
1411 | 0 | osSideCarFilename = pszComma + 1; |
1412 | 0 | } |
1413 | 0 | } |
1414 | 0 | } |
1415 | 1.71k | osSideCarFilename += ".idx"; |
1416 | 1.71k | VSILFILE *fpSideCar = nullptr; |
1417 | 1.71k | if (CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, |
1418 | 1.71k | "USE_IDX", "YES")) && |
1419 | 1.71k | ((fpSideCar = VSIFOpenL(osSideCarFilename.c_str(), "rb")) != nullptr)) |
1420 | 0 | { |
1421 | 0 | CPLDebug("GRIB", "Reading inventories from sidecar file %s", |
1422 | 0 | osSideCarFilename.c_str()); |
1423 | | // Contains an GRIB2 message inventory of the file. |
1424 | 0 | pInventories = std::make_unique<InventoryWrapperSidecar>( |
1425 | 0 | fpSideCar, nStartOffset, nSize); |
1426 | 0 | if (pInventories->result() <= 0 || pInventories->length() == 0) |
1427 | 0 | pInventories = nullptr; |
1428 | 0 | VSIFCloseL(fpSideCar); |
1429 | | #ifdef BUILD_APPS |
1430 | | m_bSideCarIdxUsed = true; |
1431 | | #endif |
1432 | 0 | } |
1433 | 1.71k | else |
1434 | 1.71k | CPLDebug("GRIB", "Failed opening sidecar %s", |
1435 | 1.71k | osSideCarFilename.c_str()); |
1436 | | |
1437 | 1.71k | if (pInventories == nullptr) |
1438 | 1.71k | { |
1439 | 1.71k | CPLDebug("GRIB", "Reading inventories from GRIB file %s", |
1440 | 1.71k | poOpenInfo->pszFilename); |
1441 | | // Contains an GRIB2 message inventory of the file. |
1442 | 1.71k | pInventories = std::make_unique<InventoryWrapperGrib>(fp); |
1443 | 1.71k | } |
1444 | | |
1445 | 1.71k | return pInventories; |
1446 | 1.71k | } |
1447 | | |
1448 | | /************************************************************************/ |
1449 | | /* Open() */ |
1450 | | /************************************************************************/ |
1451 | | |
1452 | | GDALDataset *GRIBDataset::Open(GDALOpenInfo *poOpenInfo) |
1453 | | |
1454 | 1.83k | { |
1455 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
1456 | | // During fuzzing, do not use Identify to reject crazy content. |
1457 | | if (!GRIBDriverIdentify(poOpenInfo)) |
1458 | | return nullptr; |
1459 | | #endif |
1460 | 1.83k | if (poOpenInfo->fpL == nullptr) |
1461 | 0 | return nullptr; |
1462 | | |
1463 | | // A fast "probe" on the header that is partially read in memory. |
1464 | 1.83k | char *buff = nullptr; |
1465 | 1.83k | uInt4 buffLen = 0; |
1466 | 1.83k | sInt4 sect0[SECT0LEN_WORD] = {0}; |
1467 | 1.83k | uInt4 gribLen = 0; |
1468 | 1.83k | int version = 0; |
1469 | | |
1470 | | // grib is not thread safe, make sure not to cause problems |
1471 | | // for other thread safe formats |
1472 | 1.83k | CPLMutexHolderD(&hGRIBMutex); |
1473 | | |
1474 | 1.83k | VSILFILE *memfp = VSIFileFromMemBuffer(nullptr, poOpenInfo->pabyHeader, |
1475 | 1.83k | poOpenInfo->nHeaderBytes, FALSE); |
1476 | 1.83k | if (memfp == nullptr || |
1477 | 1.83k | ReadSECT0(memfp, &buff, &buffLen, -1, sect0, &gribLen, &version) < 0) |
1478 | 127 | { |
1479 | 127 | if (memfp != nullptr) |
1480 | 127 | { |
1481 | 127 | VSIFCloseL(memfp); |
1482 | 127 | } |
1483 | 127 | free(buff); |
1484 | 127 | char *errMsg = errSprintf(nullptr); |
1485 | 127 | if (errMsg != nullptr && strstr(errMsg, "Ran out of file") == nullptr) |
1486 | 4 | CPLDebug("GRIB", "%s", errMsg); |
1487 | 127 | free(errMsg); |
1488 | 127 | return nullptr; |
1489 | 127 | } |
1490 | 1.71k | VSIFCloseL(memfp); |
1491 | 1.71k | free(buff); |
1492 | | |
1493 | | // Confirm the requested access is supported. |
1494 | 1.71k | if (poOpenInfo->eAccess == GA_Update) |
1495 | 0 | { |
1496 | 0 | ReportUpdateNotSupportedByDriver("GRIB"); |
1497 | 0 | return nullptr; |
1498 | 0 | } |
1499 | | |
1500 | 1.71k | if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) |
1501 | 0 | { |
1502 | 0 | return OpenMultiDim(poOpenInfo); |
1503 | 0 | } |
1504 | | |
1505 | | // Create a corresponding GDALDataset. |
1506 | 1.71k | GRIBDataset *poDS = new GRIBDataset(); |
1507 | | |
1508 | 1.71k | poDS->fp = poOpenInfo->fpL; |
1509 | 1.71k | poOpenInfo->fpL = nullptr; |
1510 | | |
1511 | | // Make an inventory of the GRIB file. |
1512 | | // The inventory does not contain all the information needed for |
1513 | | // creating the RasterBands (especially the x and y size), therefore |
1514 | | // the first GRIB band is also read for some additional metadata. |
1515 | | // The band-data that is read is stored into the first RasterBand, |
1516 | | // simply so that the same portion of the file is not read twice. |
1517 | | |
1518 | 1.71k | auto pInventories = poDS->Inventory(poOpenInfo); |
1519 | 1.71k | if (pInventories->result() <= 0) |
1520 | 893 | { |
1521 | 893 | char *errMsg = errSprintf(nullptr); |
1522 | 893 | if (errMsg != nullptr) |
1523 | 893 | CPLDebug("GRIB", "%s", errMsg); |
1524 | 893 | free(errMsg); |
1525 | | |
1526 | 893 | CPLError(CE_Failure, CPLE_OpenFailed, |
1527 | 893 | "%s is a grib file, " |
1528 | 893 | "but no raster dataset was successfully identified.", |
1529 | 893 | poOpenInfo->pszFilename); |
1530 | | // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own |
1531 | | // hGRIBMutex. |
1532 | 893 | CPLReleaseMutex(hGRIBMutex); |
1533 | 893 | delete poDS; |
1534 | 893 | CPLAcquireMutex(hGRIBMutex, 1000.0); |
1535 | 893 | return nullptr; |
1536 | 893 | } |
1537 | | |
1538 | | // Create band objects. |
1539 | 819 | const uInt4 nCount = std::min(pInventories->length(), 65536U); |
1540 | 14.7k | for (uInt4 i = 0; i < nCount; ++i) |
1541 | 14.5k | { |
1542 | 14.5k | inventoryType *psInv = pInventories->get(i); |
1543 | 14.5k | GRIBRasterBand *gribBand = nullptr; |
1544 | 14.5k | const uInt4 bandNr = i + 1; |
1545 | 14.5k | assert(bandNr <= 65536); |
1546 | | |
1547 | 14.5k | if (bandNr == 1) |
1548 | 819 | { |
1549 | | // Important: set DataSet extents before creating first RasterBand |
1550 | | // in it. |
1551 | 819 | grib_MetaData *metaData = nullptr; |
1552 | 819 | GRIBRasterBand::ReadGribData(poDS->fp, 0, psInv->subgNum, nullptr, |
1553 | 819 | &metaData); |
1554 | 819 | if (metaData == nullptr || metaData->gds.Nx < 1 || |
1555 | 819 | metaData->gds.Ny < 1) |
1556 | 602 | { |
1557 | 602 | CPLError(CE_Failure, CPLE_OpenFailed, |
1558 | 602 | "%s is a grib file, " |
1559 | 602 | "but no raster dataset was successfully identified.", |
1560 | 602 | poOpenInfo->pszFilename); |
1561 | | // Release hGRIBMutex otherwise we'll deadlock with GDALDataset |
1562 | | // own hGRIBMutex. |
1563 | 602 | CPLReleaseMutex(hGRIBMutex); |
1564 | 602 | delete poDS; |
1565 | 602 | CPLAcquireMutex(hGRIBMutex, 1000.0); |
1566 | 602 | if (metaData != nullptr) |
1567 | 602 | { |
1568 | 602 | MetaFree(metaData); |
1569 | 602 | delete metaData; |
1570 | 602 | } |
1571 | 602 | return nullptr; |
1572 | 602 | } |
1573 | 217 | psInv->GribVersion = metaData->GribVersion; |
1574 | | |
1575 | | // Set the DataSet's x,y size, georeference and projection from |
1576 | | // the first GRIB band. |
1577 | 217 | poDS->SetGribMetaData(metaData); |
1578 | 217 | gribBand = new GRIBRasterBand(poDS, bandNr, psInv); |
1579 | | |
1580 | 217 | if (psInv->GribVersion == 2) |
1581 | 0 | gribBand->FindPDSTemplateGRIB2(); |
1582 | | |
1583 | 217 | gribBand->m_Grib_MetaData = metaData; |
1584 | 217 | } |
1585 | 13.7k | else |
1586 | 13.7k | { |
1587 | 13.7k | gribBand = new GRIBRasterBand(poDS, bandNr, psInv); |
1588 | 13.7k | } |
1589 | 13.9k | poDS->SetBand(bandNr, gribBand); |
1590 | 13.9k | } |
1591 | | |
1592 | | // Initialize any PAM information. |
1593 | 217 | poDS->SetDescription(poOpenInfo->pszFilename); |
1594 | | |
1595 | | // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own |
1596 | | // hGRIBMutex. |
1597 | 217 | CPLReleaseMutex(hGRIBMutex); |
1598 | 217 | poDS->TryLoadXML(poOpenInfo->GetSiblingFiles()); |
1599 | | |
1600 | | // Check for external overviews. |
1601 | 217 | poDS->oOvManager.Initialize(poDS, poOpenInfo); |
1602 | 217 | CPLAcquireMutex(hGRIBMutex, 1000.0); |
1603 | | |
1604 | 217 | return poDS; |
1605 | 819 | } |
1606 | | |
1607 | | /************************************************************************/ |
1608 | | /* GRIBSharedResource */ |
1609 | | /************************************************************************/ |
1610 | | |
1611 | | struct GRIBSharedResource |
1612 | | { |
1613 | | VSILFILE *m_fp = nullptr; |
1614 | | vsi_l_offset m_nOffsetCurData = static_cast<vsi_l_offset>(-1); |
1615 | | std::vector<double> m_adfCurData{}; |
1616 | | std::string m_osFilename; |
1617 | | std::shared_ptr<GDALPamMultiDim> m_poPAM{}; |
1618 | | |
1619 | | GRIBSharedResource(const std::string &osFilename, VSILFILE *fp); |
1620 | | ~GRIBSharedResource(); |
1621 | | |
1622 | | const std::vector<double> &LoadData(vsi_l_offset nOffset, int subgNum); |
1623 | | |
1624 | | const std::shared_ptr<GDALPamMultiDim> &GetPAM() |
1625 | 0 | { |
1626 | 0 | return m_poPAM; |
1627 | 0 | } |
1628 | | }; |
1629 | | |
1630 | | GRIBSharedResource::GRIBSharedResource(const std::string &osFilename, |
1631 | | VSILFILE *fp) |
1632 | 0 | : m_fp(fp), m_osFilename(osFilename), |
1633 | 0 | m_poPAM(std::make_shared<GDALPamMultiDim>(osFilename)) |
1634 | 0 | { |
1635 | 0 | } |
1636 | | |
1637 | | GRIBSharedResource::~GRIBSharedResource() |
1638 | 0 | { |
1639 | 0 | if (m_fp) |
1640 | 0 | VSIFCloseL(m_fp); |
1641 | 0 | } |
1642 | | |
1643 | | /************************************************************************/ |
1644 | | /* GRIBGroup */ |
1645 | | /************************************************************************/ |
1646 | | |
1647 | | class GRIBArray; |
1648 | | |
1649 | | class GRIBGroup final : public GDALGroup |
1650 | | { |
1651 | | friend class GRIBArray; |
1652 | | std::shared_ptr<GRIBSharedResource> m_poShared{}; |
1653 | | std::vector<std::shared_ptr<GDALMDArray>> m_poArrays{}; |
1654 | | std::vector<std::shared_ptr<GDALDimension>> m_dims{}; |
1655 | | std::map<std::string, std::shared_ptr<GDALDimension>> m_oMapDims{}; |
1656 | | int m_nHorizDimCounter = 0; |
1657 | | std::shared_ptr<GDALGroup> m_memRootGroup{}; |
1658 | | |
1659 | | public: |
1660 | | explicit GRIBGroup(const std::shared_ptr<GRIBSharedResource> &poShared) |
1661 | 0 | : GDALGroup(std::string(), "/"), m_poShared(poShared) |
1662 | 0 | { |
1663 | 0 | std::unique_ptr<GDALDataset> poTmpDS( |
1664 | 0 | MEMDataset::CreateMultiDimensional("", nullptr, nullptr)); |
1665 | 0 | m_memRootGroup = poTmpDS->GetRootGroup(); |
1666 | 0 | } |
1667 | | |
1668 | | void AddArray(const std::shared_ptr<GDALMDArray> &array) |
1669 | 0 | { |
1670 | 0 | m_poArrays.emplace_back(array); |
1671 | 0 | } |
1672 | | |
1673 | | std::vector<std::string> |
1674 | | GetMDArrayNames(CSLConstList papszOptions) const override; |
1675 | | std::shared_ptr<GDALMDArray> |
1676 | | OpenMDArray(const std::string &osName, |
1677 | | CSLConstList papszOptions) const override; |
1678 | | |
1679 | | std::vector<std::shared_ptr<GDALDimension>> |
1680 | | GetDimensions(CSLConstList) const override |
1681 | 0 | { |
1682 | 0 | return m_dims; |
1683 | 0 | } |
1684 | | }; |
1685 | | |
1686 | | /************************************************************************/ |
1687 | | /* GRIBArray */ |
1688 | | /************************************************************************/ |
1689 | | |
1690 | | class GRIBArray final : public GDALPamMDArray |
1691 | | { |
1692 | | std::shared_ptr<GRIBSharedResource> m_poShared; |
1693 | | std::vector<std::shared_ptr<GDALDimension>> m_dims{}; |
1694 | | GDALExtendedDataType m_dt = GDALExtendedDataType::Create(GDT_Float64); |
1695 | | std::shared_ptr<OGRSpatialReference> m_poSRS{}; |
1696 | | std::vector<vsi_l_offset> m_anOffsets{}; |
1697 | | std::vector<int> m_anSubgNums{}; |
1698 | | std::vector<double> m_adfTimes{}; |
1699 | | std::vector<std::shared_ptr<GDALAttribute>> m_attributes{}; |
1700 | | std::string m_osUnit{}; |
1701 | | std::vector<GByte> m_abyNoData{}; |
1702 | | |
1703 | | GRIBArray(const std::string &osName, |
1704 | | const std::shared_ptr<GRIBSharedResource> &poShared); |
1705 | | |
1706 | | protected: |
1707 | | bool IRead(const GUInt64 *arrayStartIdx, const size_t *count, |
1708 | | const GInt64 *arrayStep, const GPtrDiff_t *bufferStride, |
1709 | | const GDALExtendedDataType &bufferDataType, |
1710 | | void *pDstBuffer) const override; |
1711 | | |
1712 | | public: |
1713 | | static std::shared_ptr<GRIBArray> |
1714 | | Create(const std::string &osName, |
1715 | | const std::shared_ptr<GRIBSharedResource> &poShared) |
1716 | 0 | { |
1717 | 0 | auto ar(std::shared_ptr<GRIBArray>(new GRIBArray(osName, poShared))); |
1718 | 0 | ar->SetSelf(ar); |
1719 | 0 | return ar; |
1720 | 0 | } |
1721 | | |
1722 | | void Init(GRIBGroup *poGroup, GRIBDataset *poDS, GRIBRasterBand *poBand, |
1723 | | inventoryType *psInv); |
1724 | | void ExtendTimeDim(vsi_l_offset nOffset, int subgNum, double dfValidTime); |
1725 | | void Finalize(GRIBGroup *poGroup, inventoryType *psInv); |
1726 | | |
1727 | | bool IsWritable() const override |
1728 | 0 | { |
1729 | 0 | return false; |
1730 | 0 | } |
1731 | | |
1732 | | const std::string &GetFilename() const override |
1733 | 0 | { |
1734 | 0 | return m_poShared->m_osFilename; |
1735 | 0 | } |
1736 | | |
1737 | | const std::vector<std::shared_ptr<GDALDimension>> & |
1738 | | GetDimensions() const override |
1739 | 0 | { |
1740 | 0 | return m_dims; |
1741 | 0 | } |
1742 | | |
1743 | | const GDALExtendedDataType &GetDataType() const override |
1744 | 0 | { |
1745 | 0 | return m_dt; |
1746 | 0 | } |
1747 | | |
1748 | | std::shared_ptr<OGRSpatialReference> GetSpatialRef() const override |
1749 | 0 | { |
1750 | 0 | return m_poSRS; |
1751 | 0 | } |
1752 | | |
1753 | | std::vector<std::shared_ptr<GDALAttribute>> |
1754 | | GetAttributes(CSLConstList) const override |
1755 | 0 | { |
1756 | 0 | return m_attributes; |
1757 | 0 | } |
1758 | | |
1759 | | const std::string &GetUnit() const override |
1760 | 0 | { |
1761 | 0 | return m_osUnit; |
1762 | 0 | } |
1763 | | |
1764 | | const void *GetRawNoDataValue() const override |
1765 | 0 | { |
1766 | 0 | return m_abyNoData.empty() ? nullptr : m_abyNoData.data(); |
1767 | 0 | } |
1768 | | }; |
1769 | | |
1770 | | /************************************************************************/ |
1771 | | /* GetMDArrayNames() */ |
1772 | | /************************************************************************/ |
1773 | | |
1774 | | std::vector<std::string> GRIBGroup::GetMDArrayNames(CSLConstList) const |
1775 | 0 | { |
1776 | 0 | std::vector<std::string> ret; |
1777 | 0 | for (const auto &array : m_poArrays) |
1778 | 0 | { |
1779 | 0 | ret.push_back(array->GetName()); |
1780 | 0 | } |
1781 | 0 | return ret; |
1782 | 0 | } |
1783 | | |
1784 | | /************************************************************************/ |
1785 | | /* OpenMDArray() */ |
1786 | | /************************************************************************/ |
1787 | | |
1788 | | std::shared_ptr<GDALMDArray> GRIBGroup::OpenMDArray(const std::string &osName, |
1789 | | CSLConstList) const |
1790 | 0 | { |
1791 | 0 | for (const auto &array : m_poArrays) |
1792 | 0 | { |
1793 | 0 | if (array->GetName() == osName) |
1794 | 0 | return array; |
1795 | 0 | } |
1796 | 0 | return nullptr; |
1797 | 0 | } |
1798 | | |
1799 | | /************************************************************************/ |
1800 | | /* GRIBArray() */ |
1801 | | /************************************************************************/ |
1802 | | |
1803 | | GRIBArray::GRIBArray(const std::string &osName, |
1804 | | const std::shared_ptr<GRIBSharedResource> &poShared) |
1805 | 0 | : GDALAbstractMDArray("/", osName), |
1806 | 0 | GDALPamMDArray("/", osName, poShared->GetPAM()), m_poShared(poShared) |
1807 | 0 | { |
1808 | 0 | } Unexecuted instantiation: GRIBArray::GRIBArray(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::shared_ptr<GRIBSharedResource> const&) Unexecuted instantiation: GRIBArray::GRIBArray(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::shared_ptr<GRIBSharedResource> const&) |
1809 | | |
1810 | | /************************************************************************/ |
1811 | | /* Init() */ |
1812 | | /************************************************************************/ |
1813 | | |
1814 | | void GRIBArray::Init(GRIBGroup *poGroup, GRIBDataset *poDS, |
1815 | | GRIBRasterBand *poBand, inventoryType *psInv) |
1816 | 0 | { |
1817 | 0 | std::shared_ptr<GDALDimension> poDimX; |
1818 | 0 | std::shared_ptr<GDALDimension> poDimY; |
1819 | |
|
1820 | 0 | double adfGT[6]; |
1821 | 0 | poDS->GetGeoTransform(adfGT); |
1822 | |
|
1823 | 0 | for (int i = 1; i <= poGroup->m_nHorizDimCounter; i++) |
1824 | 0 | { |
1825 | 0 | std::string osXLookup("X"); |
1826 | 0 | std::string osYLookup("Y"); |
1827 | 0 | if (i > 1) |
1828 | 0 | { |
1829 | 0 | osXLookup += CPLSPrintf("%d", i); |
1830 | 0 | osYLookup += CPLSPrintf("%d", i); |
1831 | 0 | } |
1832 | 0 | auto oIterX = poGroup->m_oMapDims.find(osXLookup); |
1833 | 0 | auto oIterY = poGroup->m_oMapDims.find(osYLookup); |
1834 | 0 | CPLAssert(oIterX != poGroup->m_oMapDims.end()); |
1835 | 0 | CPLAssert(oIterY != poGroup->m_oMapDims.end()); |
1836 | 0 | if (oIterX->second->GetSize() == |
1837 | 0 | static_cast<size_t>(poDS->GetRasterXSize()) && |
1838 | 0 | oIterY->second->GetSize() == |
1839 | 0 | static_cast<size_t>(poDS->GetRasterYSize())) |
1840 | 0 | { |
1841 | 0 | bool bOK = true; |
1842 | 0 | auto poVar = oIterX->second->GetIndexingVariable(); |
1843 | 0 | constexpr double EPSILON = 1e-10; |
1844 | 0 | if (poVar) |
1845 | 0 | { |
1846 | 0 | GUInt64 nStart = 0; |
1847 | 0 | size_t nCount = 1; |
1848 | 0 | double dfVal = 0; |
1849 | 0 | poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt, &dfVal); |
1850 | 0 | if (std::fabs(dfVal - (adfGT[0] + 0.5 * adfGT[1])) > |
1851 | 0 | EPSILON * std::fabs(dfVal)) |
1852 | 0 | { |
1853 | 0 | bOK = false; |
1854 | 0 | } |
1855 | 0 | } |
1856 | 0 | if (bOK) |
1857 | 0 | { |
1858 | 0 | poVar = oIterY->second->GetIndexingVariable(); |
1859 | 0 | if (poVar) |
1860 | 0 | { |
1861 | 0 | GUInt64 nStart = 0; |
1862 | 0 | size_t nCount = 1; |
1863 | 0 | double dfVal = 0; |
1864 | 0 | poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt, |
1865 | 0 | &dfVal); |
1866 | 0 | if (std::fabs(dfVal - |
1867 | 0 | (adfGT[3] + poDS->nRasterYSize * adfGT[5] - |
1868 | 0 | 0.5 * adfGT[5])) > |
1869 | 0 | EPSILON * std::fabs(dfVal)) |
1870 | 0 | { |
1871 | 0 | bOK = false; |
1872 | 0 | } |
1873 | 0 | } |
1874 | 0 | } |
1875 | 0 | if (bOK) |
1876 | 0 | { |
1877 | 0 | poDimX = oIterX->second; |
1878 | 0 | poDimY = oIterY->second; |
1879 | 0 | break; |
1880 | 0 | } |
1881 | 0 | } |
1882 | 0 | } |
1883 | |
|
1884 | 0 | if (!poDimX || !poDimY) |
1885 | 0 | { |
1886 | 0 | poGroup->m_nHorizDimCounter++; |
1887 | 0 | { |
1888 | 0 | std::string osName("Y"); |
1889 | 0 | if (poGroup->m_nHorizDimCounter >= 2) |
1890 | 0 | { |
1891 | 0 | osName = CPLSPrintf("Y%d", poGroup->m_nHorizDimCounter); |
1892 | 0 | } |
1893 | |
|
1894 | 0 | poDimY = std::make_shared<GDALDimensionWeakIndexingVar>( |
1895 | 0 | poGroup->GetFullName(), osName, GDAL_DIM_TYPE_HORIZONTAL_Y, |
1896 | 0 | std::string(), poDS->GetRasterYSize()); |
1897 | 0 | poGroup->m_oMapDims[osName] = poDimY; |
1898 | 0 | poGroup->m_dims.emplace_back(poDimY); |
1899 | |
|
1900 | 0 | auto var = GDALMDArrayRegularlySpaced::Create( |
1901 | 0 | "/", poDimY->GetName(), poDimY, |
1902 | 0 | adfGT[3] + poDS->GetRasterYSize() * adfGT[5], -adfGT[5], 0.5); |
1903 | 0 | poDimY->SetIndexingVariable(var); |
1904 | 0 | poGroup->AddArray(var); |
1905 | 0 | } |
1906 | |
|
1907 | 0 | { |
1908 | 0 | std::string osName("X"); |
1909 | 0 | if (poGroup->m_nHorizDimCounter >= 2) |
1910 | 0 | { |
1911 | 0 | osName = CPLSPrintf("X%d", poGroup->m_nHorizDimCounter); |
1912 | 0 | } |
1913 | |
|
1914 | 0 | poDimX = std::make_shared<GDALDimensionWeakIndexingVar>( |
1915 | 0 | poGroup->GetFullName(), osName, GDAL_DIM_TYPE_HORIZONTAL_X, |
1916 | 0 | std::string(), poDS->GetRasterXSize()); |
1917 | 0 | poGroup->m_oMapDims[osName] = poDimX; |
1918 | 0 | poGroup->m_dims.emplace_back(poDimX); |
1919 | |
|
1920 | 0 | auto var = GDALMDArrayRegularlySpaced::Create( |
1921 | 0 | "/", poDimX->GetName(), poDimX, adfGT[0], adfGT[1], 0.5); |
1922 | 0 | poDimX->SetIndexingVariable(var); |
1923 | 0 | poGroup->AddArray(var); |
1924 | 0 | } |
1925 | 0 | } |
1926 | |
|
1927 | 0 | m_dims.emplace_back(poDimY); |
1928 | 0 | m_dims.emplace_back(poDimX); |
1929 | 0 | if (poDS->m_poSRS.get()) |
1930 | 0 | { |
1931 | 0 | m_poSRS.reset(poDS->m_poSRS->Clone()); |
1932 | 0 | if (poDS->m_poSRS->GetDataAxisToSRSAxisMapping() == |
1933 | 0 | std::vector<int>{2, 1}) |
1934 | 0 | m_poSRS->SetDataAxisToSRSAxisMapping({1, 2}); |
1935 | 0 | else |
1936 | 0 | m_poSRS->SetDataAxisToSRSAxisMapping({2, 1}); |
1937 | 0 | } |
1938 | |
|
1939 | 0 | const char *pszGribNormalizeUnits = |
1940 | 0 | CPLGetConfigOption("GRIB_NORMALIZE_UNITS", "YES"); |
1941 | 0 | const bool bMetricUnits = CPLTestBool(pszGribNormalizeUnits); |
1942 | |
|
1943 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1944 | 0 | GetFullName(), "name", psInv->element)); |
1945 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1946 | 0 | GetFullName(), "long_name", |
1947 | 0 | ConvertUnitInText(bMetricUnits, psInv->comment))); |
1948 | 0 | m_osUnit = ConvertUnitInText(bMetricUnits, psInv->unitName); |
1949 | 0 | if (!m_osUnit.empty() && m_osUnit[0] == '[' && m_osUnit.back() == ']') |
1950 | 0 | { |
1951 | 0 | m_osUnit = m_osUnit.substr(1, m_osUnit.size() - 2); |
1952 | 0 | } |
1953 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1954 | 0 | GetFullName(), "first_level", psInv->shortFstLevel)); |
1955 | |
|
1956 | 0 | if (poBand->m_nDisciplineCode >= 0) |
1957 | 0 | { |
1958 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
1959 | 0 | GetFullName(), "discipline_code", poBand->m_nDisciplineCode)); |
1960 | 0 | } |
1961 | 0 | if (!poBand->m_osDisciplineName.empty()) |
1962 | 0 | { |
1963 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1964 | 0 | GetFullName(), "discipline_name", poBand->m_osDisciplineName)); |
1965 | 0 | } |
1966 | 0 | if (poBand->m_nCenter >= 0) |
1967 | 0 | { |
1968 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
1969 | 0 | GetFullName(), "center_code", poBand->m_nCenter)); |
1970 | 0 | } |
1971 | 0 | if (!poBand->m_osCenterName.empty()) |
1972 | 0 | { |
1973 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1974 | 0 | GetFullName(), "center_name", poBand->m_osCenterName)); |
1975 | 0 | } |
1976 | 0 | if (poBand->m_nSubCenter >= 0) |
1977 | 0 | { |
1978 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
1979 | 0 | GetFullName(), "subcenter_code", poBand->m_nSubCenter)); |
1980 | 0 | } |
1981 | 0 | if (!poBand->m_osSubCenterName.empty()) |
1982 | 0 | { |
1983 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1984 | 0 | GetFullName(), "subcenter_name", poBand->m_osSubCenterName)); |
1985 | 0 | } |
1986 | 0 | if (!poBand->m_osSignRefTimeName.empty()) |
1987 | 0 | { |
1988 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1989 | 0 | GetFullName(), "signification_of_ref_time", |
1990 | 0 | poBand->m_osSignRefTimeName)); |
1991 | 0 | } |
1992 | 0 | if (!poBand->m_osRefTime.empty()) |
1993 | 0 | { |
1994 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
1995 | 0 | GetFullName(), "reference_time_iso8601", poBand->m_osRefTime)); |
1996 | 0 | } |
1997 | 0 | if (!poBand->m_osProductionStatus.empty()) |
1998 | 0 | { |
1999 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
2000 | 0 | GetFullName(), "production_status", poBand->m_osProductionStatus)); |
2001 | 0 | } |
2002 | 0 | if (!poBand->m_osType.empty()) |
2003 | 0 | { |
2004 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
2005 | 0 | GetFullName(), "type", poBand->m_osType)); |
2006 | 0 | } |
2007 | 0 | if (poBand->m_nPDTN >= 0) |
2008 | 0 | { |
2009 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
2010 | 0 | GetFullName(), "product_definition_template_number", |
2011 | 0 | poBand->m_nPDTN)); |
2012 | 0 | } |
2013 | 0 | if (!poBand->m_anPDSTemplateAssembledValues.empty()) |
2014 | 0 | { |
2015 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
2016 | 0 | GetFullName(), "product_definition_numbers", |
2017 | 0 | poBand->m_anPDSTemplateAssembledValues)); |
2018 | 0 | } |
2019 | |
|
2020 | 0 | int bHasNoData = FALSE; |
2021 | 0 | double dfNoData = poBand->GetNoDataValue(&bHasNoData); |
2022 | 0 | if (bHasNoData) |
2023 | 0 | { |
2024 | 0 | m_abyNoData.resize(sizeof(double)); |
2025 | 0 | memcpy(&m_abyNoData[0], &dfNoData, sizeof(double)); |
2026 | 0 | } |
2027 | 0 | } |
2028 | | |
2029 | | /************************************************************************/ |
2030 | | /* ExtendTimeDim() */ |
2031 | | /************************************************************************/ |
2032 | | |
2033 | | void GRIBArray::ExtendTimeDim(vsi_l_offset nOffset, int subgNum, |
2034 | | double dfValidTime) |
2035 | 0 | { |
2036 | 0 | m_anOffsets.push_back(nOffset); |
2037 | 0 | m_anSubgNums.push_back(subgNum); |
2038 | 0 | m_adfTimes.push_back(dfValidTime); |
2039 | 0 | } |
2040 | | |
2041 | | /************************************************************************/ |
2042 | | /* Finalize() */ |
2043 | | /************************************************************************/ |
2044 | | |
2045 | | void GRIBArray::Finalize(GRIBGroup *poGroup, inventoryType *psInv) |
2046 | 0 | { |
2047 | 0 | CPLAssert(!m_adfTimes.empty()); |
2048 | 0 | CPLAssert(m_anOffsets.size() == m_adfTimes.size()); |
2049 | |
|
2050 | 0 | if (m_adfTimes.size() == 1) |
2051 | 0 | { |
2052 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
2053 | 0 | GetFullName(), "forecast_time", psInv->foreSec)); |
2054 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
2055 | 0 | GetFullName(), "forecast_time_unit", "sec")); |
2056 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
2057 | 0 | GetFullName(), "reference_time", psInv->refTime)); |
2058 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
2059 | 0 | GetFullName(), "reference_time_unit", "sec UTC")); |
2060 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeNumeric>( |
2061 | 0 | GetFullName(), "validity_time", m_adfTimes[0])); |
2062 | 0 | m_attributes.emplace_back(std::make_shared<GDALAttributeString>( |
2063 | 0 | GetFullName(), "validity_time_unit", "sec UTC")); |
2064 | 0 | return; |
2065 | 0 | } |
2066 | | |
2067 | 0 | std::shared_ptr<GDALDimension> poDimTime; |
2068 | |
|
2069 | 0 | for (const auto &poDim : poGroup->m_dims) |
2070 | 0 | { |
2071 | 0 | if (STARTS_WITH(poDim->GetName().c_str(), "TIME") && |
2072 | 0 | poDim->GetSize() == m_adfTimes.size()) |
2073 | 0 | { |
2074 | 0 | auto poVar = poDim->GetIndexingVariable(); |
2075 | 0 | if (poVar) |
2076 | 0 | { |
2077 | 0 | GUInt64 nStart = 0; |
2078 | 0 | size_t nCount = 1; |
2079 | 0 | double dfStartTime = 0; |
2080 | 0 | poVar->Read(&nStart, &nCount, nullptr, nullptr, m_dt, |
2081 | 0 | &dfStartTime); |
2082 | 0 | if (dfStartTime == m_adfTimes[0]) |
2083 | 0 | { |
2084 | 0 | poDimTime = poDim; |
2085 | 0 | break; |
2086 | 0 | } |
2087 | 0 | } |
2088 | 0 | } |
2089 | 0 | } |
2090 | |
|
2091 | 0 | if (!poDimTime) |
2092 | 0 | { |
2093 | 0 | std::string osName("TIME"); |
2094 | 0 | int counter = 2; |
2095 | 0 | while (poGroup->m_oMapDims.find(osName) != poGroup->m_oMapDims.end()) |
2096 | 0 | { |
2097 | 0 | osName = CPLSPrintf("TIME%d", counter); |
2098 | 0 | counter++; |
2099 | 0 | } |
2100 | |
|
2101 | 0 | poDimTime = std::make_shared<GDALDimensionWeakIndexingVar>( |
2102 | 0 | poGroup->GetFullName(), osName, GDAL_DIM_TYPE_TEMPORAL, |
2103 | 0 | std::string(), m_adfTimes.size()); |
2104 | 0 | poGroup->m_oMapDims[osName] = poDimTime; |
2105 | 0 | poGroup->m_dims.emplace_back(poDimTime); |
2106 | |
|
2107 | 0 | auto var = poGroup->m_memRootGroup->CreateMDArray( |
2108 | 0 | poDimTime->GetName(), |
2109 | 0 | std::vector<std::shared_ptr<GDALDimension>>{poDimTime}, |
2110 | 0 | GDALExtendedDataType::Create(GDT_Float64), nullptr); |
2111 | 0 | poDimTime->SetIndexingVariable(var); |
2112 | 0 | poGroup->AddArray(var); |
2113 | |
|
2114 | 0 | GUInt64 nStart = 0; |
2115 | 0 | size_t nCount = m_adfTimes.size(); |
2116 | 0 | var->SetUnit("sec UTC"); |
2117 | 0 | const GUInt64 anStart[] = {nStart}; |
2118 | 0 | const size_t anCount[] = {nCount}; |
2119 | 0 | var->Write(anStart, anCount, nullptr, nullptr, var->GetDataType(), |
2120 | 0 | &m_adfTimes[0]); |
2121 | 0 | auto attr = var->CreateAttribute("long_name", {}, |
2122 | 0 | GDALExtendedDataType::CreateString()); |
2123 | 0 | attr->Write("validity_time"); |
2124 | 0 | } |
2125 | |
|
2126 | 0 | #if defined(__GNUC__) |
2127 | 0 | #pragma GCC diagnostic push |
2128 | 0 | #pragma GCC diagnostic ignored "-Wnull-dereference" |
2129 | 0 | #endif |
2130 | 0 | m_dims.insert(m_dims.begin(), poDimTime); |
2131 | 0 | #if defined(__GNUC__) |
2132 | 0 | #pragma GCC diagnostic pop |
2133 | 0 | #endif |
2134 | 0 | if (m_poSRS) |
2135 | 0 | { |
2136 | 0 | auto mapping = m_poSRS->GetDataAxisToSRSAxisMapping(); |
2137 | 0 | for (auto &v : mapping) |
2138 | 0 | v += 1; |
2139 | 0 | m_poSRS->SetDataAxisToSRSAxisMapping(mapping); |
2140 | 0 | } |
2141 | 0 | } |
2142 | | |
2143 | | /************************************************************************/ |
2144 | | /* LoadData() */ |
2145 | | /************************************************************************/ |
2146 | | |
2147 | | const std::vector<double> &GRIBSharedResource::LoadData(vsi_l_offset nOffset, |
2148 | | int subgNum) |
2149 | 0 | { |
2150 | 0 | if (m_nOffsetCurData == nOffset) |
2151 | 0 | return m_adfCurData; |
2152 | | |
2153 | 0 | grib_MetaData *metadata = nullptr; |
2154 | 0 | double *data = nullptr; |
2155 | 0 | GRIBRasterBand::ReadGribData(m_fp, nOffset, subgNum, &data, &metadata); |
2156 | 0 | if (data == nullptr || metadata == nullptr) |
2157 | 0 | { |
2158 | 0 | if (metadata != nullptr) |
2159 | 0 | { |
2160 | 0 | MetaFree(metadata); |
2161 | 0 | delete metadata; |
2162 | 0 | } |
2163 | 0 | free(data); |
2164 | 0 | m_adfCurData.clear(); |
2165 | 0 | return m_adfCurData; |
2166 | 0 | } |
2167 | 0 | const int nx = metadata->gds.Nx; |
2168 | 0 | const int ny = metadata->gds.Ny; |
2169 | 0 | MetaFree(metadata); |
2170 | 0 | delete metadata; |
2171 | 0 | if (nx <= 0 || ny <= 0) |
2172 | 0 | { |
2173 | 0 | free(data); |
2174 | 0 | m_adfCurData.clear(); |
2175 | 0 | return m_adfCurData; |
2176 | 0 | } |
2177 | 0 | const size_t nPointCount = static_cast<size_t>(nx) * ny; |
2178 | 0 | const size_t nByteCount = nPointCount * sizeof(double); |
2179 | 0 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
2180 | 0 | if (nByteCount > static_cast<size_t>(INT_MAX)) |
2181 | 0 | { |
2182 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, |
2183 | 0 | "Too large memory allocation attempt"); |
2184 | 0 | free(data); |
2185 | 0 | m_adfCurData.clear(); |
2186 | 0 | return m_adfCurData; |
2187 | 0 | } |
2188 | 0 | #endif |
2189 | 0 | try |
2190 | 0 | { |
2191 | 0 | m_adfCurData.resize(nPointCount); |
2192 | 0 | } |
2193 | 0 | catch (const std::exception &e) |
2194 | 0 | { |
2195 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what()); |
2196 | 0 | free(data); |
2197 | 0 | m_adfCurData.clear(); |
2198 | 0 | return m_adfCurData; |
2199 | 0 | } |
2200 | 0 | m_nOffsetCurData = nOffset; |
2201 | 0 | memcpy(&m_adfCurData[0], data, nByteCount); |
2202 | 0 | free(data); |
2203 | 0 | return m_adfCurData; |
2204 | 0 | } |
2205 | | |
2206 | | /************************************************************************/ |
2207 | | /* IRead() */ |
2208 | | /************************************************************************/ |
2209 | | |
2210 | | bool GRIBArray::IRead(const GUInt64 *arrayStartIdx, const size_t *count, |
2211 | | const GInt64 *arrayStep, const GPtrDiff_t *bufferStride, |
2212 | | const GDALExtendedDataType &bufferDataType, |
2213 | | void *pDstBuffer) const |
2214 | 0 | { |
2215 | 0 | const size_t nBufferDTSize(bufferDataType.GetSize()); |
2216 | 0 | if (m_dims.size() == 2) |
2217 | 0 | { |
2218 | 0 | auto &vals = m_poShared->LoadData(m_anOffsets[0], m_anSubgNums[0]); |
2219 | 0 | constexpr int Y_IDX = 0; |
2220 | 0 | constexpr int X_IDX = 1; |
2221 | 0 | if (vals.empty() || |
2222 | 0 | vals.size() != m_dims[Y_IDX]->GetSize() * m_dims[X_IDX]->GetSize()) |
2223 | 0 | return false; |
2224 | 0 | const size_t nWidth(static_cast<size_t>(m_dims[X_IDX]->GetSize())); |
2225 | 0 | const bool bDirectCopy = m_dt == bufferDataType && |
2226 | 0 | arrayStep[X_IDX] == 1 && |
2227 | 0 | bufferStride[X_IDX] == 1; |
2228 | 0 | for (size_t j = 0; j < count[Y_IDX]; j++) |
2229 | 0 | { |
2230 | 0 | const size_t y = static_cast<size_t>(arrayStartIdx[Y_IDX] + |
2231 | 0 | j * arrayStep[Y_IDX]); |
2232 | 0 | GByte *pabyDstPtr = static_cast<GByte *>(pDstBuffer) + |
2233 | 0 | j * bufferStride[Y_IDX] * nBufferDTSize; |
2234 | 0 | const size_t x = static_cast<size_t>(arrayStartIdx[X_IDX]); |
2235 | 0 | const double *srcPtr = &vals[y * nWidth + x]; |
2236 | 0 | if (bDirectCopy) |
2237 | 0 | { |
2238 | 0 | memcpy(pabyDstPtr, srcPtr, count[X_IDX] * sizeof(double)); |
2239 | 0 | } |
2240 | 0 | else |
2241 | 0 | { |
2242 | 0 | const auto dstPtrInc = bufferStride[X_IDX] * nBufferDTSize; |
2243 | 0 | for (size_t i = 0; i < count[X_IDX]; i++) |
2244 | 0 | { |
2245 | 0 | GDALExtendedDataType::CopyValue(srcPtr, m_dt, pabyDstPtr, |
2246 | 0 | bufferDataType); |
2247 | 0 | srcPtr += static_cast<std::ptrdiff_t>(arrayStep[X_IDX]); |
2248 | 0 | pabyDstPtr += dstPtrInc; |
2249 | 0 | } |
2250 | 0 | } |
2251 | 0 | } |
2252 | 0 | return true; |
2253 | 0 | } |
2254 | | |
2255 | 0 | constexpr int T_IDX = 0; |
2256 | 0 | constexpr int Y_IDX = 1; |
2257 | 0 | constexpr int X_IDX = 2; |
2258 | 0 | const size_t nWidth(static_cast<size_t>(m_dims[X_IDX]->GetSize())); |
2259 | 0 | const bool bDirectCopy = m_dt == bufferDataType && arrayStep[X_IDX] == 1 && |
2260 | 0 | bufferStride[X_IDX] == 1; |
2261 | 0 | for (size_t k = 0; k < count[T_IDX]; k++) |
2262 | 0 | { |
2263 | 0 | const size_t tIdx = |
2264 | 0 | static_cast<size_t>(arrayStartIdx[T_IDX] + k * arrayStep[T_IDX]); |
2265 | 0 | CPLAssert(tIdx < m_anOffsets.size()); |
2266 | 0 | auto &vals = |
2267 | 0 | m_poShared->LoadData(m_anOffsets[tIdx], m_anSubgNums[tIdx]); |
2268 | 0 | if (vals.empty() || |
2269 | 0 | vals.size() != m_dims[Y_IDX]->GetSize() * m_dims[X_IDX]->GetSize()) |
2270 | 0 | return false; |
2271 | 0 | for (size_t j = 0; j < count[Y_IDX]; j++) |
2272 | 0 | { |
2273 | 0 | const size_t y = static_cast<size_t>(arrayStartIdx[Y_IDX] + |
2274 | 0 | j * arrayStep[Y_IDX]); |
2275 | 0 | GByte *pabyDstPtr = |
2276 | 0 | static_cast<GByte *>(pDstBuffer) + |
2277 | 0 | (k * bufferStride[T_IDX] + j * bufferStride[Y_IDX]) * |
2278 | 0 | nBufferDTSize; |
2279 | 0 | const size_t x = static_cast<size_t>(arrayStartIdx[X_IDX]); |
2280 | 0 | const double *srcPtr = &vals[y * nWidth + x]; |
2281 | 0 | if (bDirectCopy) |
2282 | 0 | { |
2283 | 0 | memcpy(pabyDstPtr, srcPtr, count[X_IDX] * sizeof(double)); |
2284 | 0 | } |
2285 | 0 | else |
2286 | 0 | { |
2287 | 0 | const auto dstPtrInc = bufferStride[X_IDX] * nBufferDTSize; |
2288 | 0 | for (size_t i = 0; i < count[X_IDX]; i++) |
2289 | 0 | { |
2290 | 0 | GDALExtendedDataType::CopyValue(srcPtr, m_dt, pabyDstPtr, |
2291 | 0 | bufferDataType); |
2292 | 0 | srcPtr += static_cast<std::ptrdiff_t>(arrayStep[X_IDX]); |
2293 | 0 | pabyDstPtr += dstPtrInc; |
2294 | 0 | } |
2295 | 0 | } |
2296 | 0 | } |
2297 | 0 | } |
2298 | | |
2299 | 0 | return true; |
2300 | 0 | } |
2301 | | |
2302 | | /************************************************************************/ |
2303 | | /* OpenMultiDim() */ |
2304 | | /************************************************************************/ |
2305 | | |
2306 | | GDALDataset *GRIBDataset::OpenMultiDim(GDALOpenInfo *poOpenInfo) |
2307 | | |
2308 | 0 | { |
2309 | 0 | auto poShared = std::make_shared<GRIBSharedResource>( |
2310 | 0 | poOpenInfo->pszFilename, poOpenInfo->fpL); |
2311 | 0 | auto poRootGroup = std::make_shared<GRIBGroup>(poShared); |
2312 | 0 | poOpenInfo->fpL = nullptr; |
2313 | |
|
2314 | 0 | VSIFSeekL(poShared->m_fp, 0, SEEK_SET); |
2315 | | |
2316 | | // Contains an GRIB2 message inventory of the file. |
2317 | | // We can't use the potential .idx file |
2318 | 0 | auto pInventories = std::make_unique<InventoryWrapperGrib>(poShared->m_fp); |
2319 | |
|
2320 | 0 | if (pInventories->result() <= 0) |
2321 | 0 | { |
2322 | 0 | char *errMsg = errSprintf(nullptr); |
2323 | 0 | if (errMsg != nullptr) |
2324 | 0 | CPLDebug("GRIB", "%s", errMsg); |
2325 | 0 | free(errMsg); |
2326 | |
|
2327 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
2328 | 0 | "%s is a grib file, " |
2329 | 0 | "but no raster dataset was successfully identified.", |
2330 | 0 | poOpenInfo->pszFilename); |
2331 | 0 | return nullptr; |
2332 | 0 | } |
2333 | | |
2334 | | // Create band objects. |
2335 | 0 | GRIBDataset *poDS = new GRIBDataset(); |
2336 | 0 | poDS->fp = poShared->m_fp; |
2337 | |
|
2338 | 0 | std::shared_ptr<GRIBArray> poArray; |
2339 | 0 | std::set<std::string> oSetArrayNames; |
2340 | 0 | std::string osElement; |
2341 | 0 | std::string osShortFstLevel; |
2342 | 0 | double dfRefTime = 0; |
2343 | 0 | double dfForecastTime = 0; |
2344 | 0 | for (uInt4 i = 0; i < pInventories->length(); ++i) |
2345 | 0 | { |
2346 | 0 | inventoryType *psInv = pInventories->get(i); |
2347 | 0 | uInt4 bandNr = i + 1; |
2348 | | |
2349 | | // GRIB messages can be preceded by "garbage". GRIB2Inventory() |
2350 | | // does not return the offset to the real start of the message |
2351 | 0 | GByte abyHeader[1024 + 1]; |
2352 | 0 | VSIFSeekL(poShared->m_fp, psInv->start, SEEK_SET); |
2353 | 0 | size_t nRead = |
2354 | 0 | VSIFReadL(abyHeader, 1, sizeof(abyHeader) - 1, poShared->m_fp); |
2355 | 0 | abyHeader[nRead] = 0; |
2356 | | // Find the real offset of the fist message |
2357 | 0 | const char *pasHeader = reinterpret_cast<char *>(abyHeader); |
2358 | 0 | int nOffsetFirstMessage = 0; |
2359 | 0 | for (int j = 0; j < poOpenInfo->nHeaderBytes - 3; j++) |
2360 | 0 | { |
2361 | 0 | if (STARTS_WITH_CI(pasHeader + j, "GRIB") |
2362 | | #ifdef ENABLE_TDLP |
2363 | | || STARTS_WITH_CI(pasHeader + j, "TDLP") |
2364 | | #endif |
2365 | 0 | ) |
2366 | 0 | { |
2367 | 0 | nOffsetFirstMessage = j; |
2368 | 0 | break; |
2369 | 0 | } |
2370 | 0 | } |
2371 | 0 | psInv->start += nOffsetFirstMessage; |
2372 | |
|
2373 | 0 | if (poArray == nullptr || osElement != psInv->element || |
2374 | 0 | osShortFstLevel != psInv->shortFstLevel || |
2375 | 0 | !((dfRefTime == psInv->refTime && |
2376 | 0 | dfForecastTime != psInv->foreSec) || |
2377 | 0 | (dfRefTime != psInv->refTime && |
2378 | 0 | dfForecastTime == psInv->foreSec))) |
2379 | 0 | { |
2380 | 0 | if (poArray) |
2381 | 0 | { |
2382 | 0 | poArray->Finalize(poRootGroup.get(), pInventories->get(i - 1)); |
2383 | 0 | poRootGroup->AddArray(poArray); |
2384 | 0 | } |
2385 | |
|
2386 | 0 | grib_MetaData *metaData = nullptr; |
2387 | 0 | GRIBRasterBand::ReadGribData(poShared->m_fp, psInv->start, |
2388 | 0 | psInv->subgNum, nullptr, &metaData); |
2389 | 0 | if (metaData == nullptr || metaData->gds.Nx < 1 || |
2390 | 0 | metaData->gds.Ny < 1) |
2391 | 0 | { |
2392 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
2393 | 0 | "%s is a grib file, " |
2394 | 0 | "but no raster dataset was successfully identified.", |
2395 | 0 | poOpenInfo->pszFilename); |
2396 | |
|
2397 | 0 | if (metaData != nullptr) |
2398 | 0 | { |
2399 | 0 | MetaFree(metaData); |
2400 | 0 | delete metaData; |
2401 | 0 | } |
2402 | 0 | poDS->fp = nullptr; |
2403 | 0 | CPLReleaseMutex(hGRIBMutex); |
2404 | 0 | delete poDS; |
2405 | 0 | CPLAcquireMutex(hGRIBMutex, 1000.0); |
2406 | 0 | return nullptr; |
2407 | 0 | } |
2408 | 0 | psInv->GribVersion = metaData->GribVersion; |
2409 | | |
2410 | | // Set the DataSet's x,y size, georeference and projection from |
2411 | | // the first GRIB band. |
2412 | 0 | poDS->SetGribMetaData(metaData); |
2413 | | |
2414 | | // coverity[tainted_data] |
2415 | 0 | GRIBRasterBand gribBand(poDS, bandNr, psInv); |
2416 | 0 | if (psInv->GribVersion == 2) |
2417 | 0 | gribBand.FindPDSTemplateGRIB2(); |
2418 | 0 | osElement = psInv->element; |
2419 | 0 | osShortFstLevel = psInv->shortFstLevel; |
2420 | 0 | dfRefTime = psInv->refTime; |
2421 | 0 | dfForecastTime = psInv->foreSec; |
2422 | |
|
2423 | 0 | std::string osRadix(osElement + '_' + osShortFstLevel); |
2424 | 0 | std::string osName(osRadix); |
2425 | 0 | int nCounter = 2; |
2426 | 0 | while (oSetArrayNames.find(osName) != oSetArrayNames.end()) |
2427 | 0 | { |
2428 | 0 | osName = osRadix + CPLSPrintf("_%d", nCounter); |
2429 | 0 | nCounter++; |
2430 | 0 | } |
2431 | 0 | oSetArrayNames.insert(osName); |
2432 | 0 | poArray = GRIBArray::Create(osName, poShared); |
2433 | 0 | poArray->Init(poRootGroup.get(), poDS, &gribBand, psInv); |
2434 | 0 | poArray->ExtendTimeDim(psInv->start, psInv->subgNum, |
2435 | 0 | psInv->validTime); |
2436 | |
|
2437 | 0 | MetaFree(metaData); |
2438 | 0 | delete metaData; |
2439 | 0 | } |
2440 | 0 | else |
2441 | 0 | { |
2442 | 0 | poArray->ExtendTimeDim(psInv->start, psInv->subgNum, |
2443 | 0 | psInv->validTime); |
2444 | 0 | } |
2445 | 0 | } |
2446 | | |
2447 | 0 | if (poArray) |
2448 | 0 | { |
2449 | 0 | poArray->Finalize(poRootGroup.get(), |
2450 | 0 | pInventories->get(pInventories->length() - 1)); |
2451 | 0 | poRootGroup->AddArray(poArray); |
2452 | 0 | } |
2453 | |
|
2454 | 0 | poDS->fp = nullptr; |
2455 | 0 | poDS->m_poRootGroup = poRootGroup; |
2456 | |
|
2457 | 0 | poDS->SetDescription(poOpenInfo->pszFilename); |
2458 | | |
2459 | | // Release hGRIBMutex otherwise we'll deadlock with GDALDataset own |
2460 | | // hGRIBMutex. |
2461 | 0 | CPLReleaseMutex(hGRIBMutex); |
2462 | 0 | poDS->TryLoadXML(); |
2463 | 0 | CPLAcquireMutex(hGRIBMutex, 1000.0); |
2464 | |
|
2465 | 0 | return poDS; |
2466 | 0 | } |
2467 | | |
2468 | | /************************************************************************/ |
2469 | | /* SetMetadata() */ |
2470 | | /************************************************************************/ |
2471 | | |
2472 | | void GRIBDataset::SetGribMetaData(grib_MetaData *meta) |
2473 | 217 | { |
2474 | 217 | nRasterXSize = meta->gds.Nx; |
2475 | 217 | nRasterYSize = meta->gds.Ny; |
2476 | | |
2477 | | // Image projection. |
2478 | 217 | OGRSpatialReference oSRS; |
2479 | 217 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
2480 | | |
2481 | 217 | switch (meta->gds.projType) |
2482 | 217 | { |
2483 | 79 | case GS3_LATLON: |
2484 | 79 | case GS3_GAUSSIAN_LATLON: |
2485 | | // No projection, only latlon system (geographic). |
2486 | 79 | break; |
2487 | 67 | case GS3_ROTATED_LATLON: |
2488 | | // will apply pole rotation afterwards |
2489 | 67 | break; |
2490 | 12 | case GS3_MERCATOR: |
2491 | 12 | if (meta->gds.orientLon == 0.0) |
2492 | 12 | { |
2493 | 12 | if (meta->gds.meshLat == 0.0) |
2494 | 1 | oSRS.SetMercator(0.0, 0.0, 1.0, 0.0, 0.0); |
2495 | 11 | else |
2496 | 11 | oSRS.SetMercator2SP(meta->gds.meshLat, 0.0, 0.0, 0.0, 0.0); |
2497 | 12 | } |
2498 | 0 | else |
2499 | 0 | { |
2500 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
2501 | 0 | "Orientation of the grid != 0 not supported"); |
2502 | 0 | return; |
2503 | 0 | } |
2504 | 12 | break; |
2505 | 12 | case GS3_TRANSVERSE_MERCATOR: |
2506 | 0 | oSRS.SetTM(meta->gds.latitude_of_origin, |
2507 | 0 | Lon360to180(meta->gds.central_meridian), |
2508 | 0 | std::abs(meta->gds.scaleLat1 - 0.9996) < 1e-8 |
2509 | 0 | ? 0.9996 |
2510 | 0 | : meta->gds.scaleLat1, |
2511 | 0 | meta->gds.x0, meta->gds.y0); |
2512 | 0 | break; |
2513 | 2 | case GS3_POLAR: |
2514 | 2 | oSRS.SetPS(meta->gds.meshLat, meta->gds.orientLon, 1.0, 0.0, 0.0); |
2515 | 2 | break; |
2516 | 57 | case GS3_LAMBERT: |
2517 | 57 | oSRS.SetLCC(meta->gds.scaleLat1, meta->gds.scaleLat2, |
2518 | 57 | meta->gds.meshLat, Lon360to180(meta->gds.orientLon), |
2519 | 57 | 0.0, 0.0); |
2520 | 57 | break; |
2521 | 0 | case GS3_ALBERS_EQUAL_AREA: |
2522 | 0 | oSRS.SetACEA(meta->gds.scaleLat1, meta->gds.scaleLat2, |
2523 | 0 | meta->gds.meshLat, Lon360to180(meta->gds.orientLon), |
2524 | 0 | 0.0, 0.0); |
2525 | 0 | break; |
2526 | | |
2527 | 0 | case GS3_ORTHOGRAPHIC: |
2528 | | |
2529 | | // oSRS.SetOrthographic( 0.0, meta->gds.orientLon, |
2530 | | // meta->gds.lon2, meta->gds.lat2); |
2531 | | |
2532 | | // oSRS.SetGEOS( meta->gds.orientLon, meta->gds.stretchFactor, |
2533 | | // meta->gds.lon2, meta->gds.lat2); |
2534 | | |
2535 | | // TODO: Hardcoded for now. How to parse the meta->gds section? |
2536 | 0 | oSRS.SetGEOS(0, 35785831, 0, 0); |
2537 | 0 | break; |
2538 | 0 | case GS3_LAMBERT_AZIMUTHAL: |
2539 | 0 | oSRS.SetLAEA(meta->gds.meshLat, Lon360to180(meta->gds.orientLon), |
2540 | 0 | 0.0, 0.0); |
2541 | 0 | break; |
2542 | | |
2543 | 0 | case GS3_EQUATOR_EQUIDIST: |
2544 | 0 | break; |
2545 | 0 | case GS3_AZIMUTH_RANGE: |
2546 | 0 | break; |
2547 | 217 | } |
2548 | | |
2549 | 217 | if (oSRS.IsProjected()) |
2550 | 71 | { |
2551 | 71 | oSRS.SetLinearUnits("Metre", 1.0); |
2552 | 71 | } |
2553 | | |
2554 | 217 | const bool bHaveEarthModel = |
2555 | 217 | meta->gds.majEarth > 0.0 && meta->gds.minEarth > 0.0; |
2556 | | // In meters. |
2557 | 217 | const double a = bHaveEarthModel |
2558 | 217 | ? meta->gds.majEarth * 1.0e3 |
2559 | 217 | : CPLAtof(CPLGetConfigOption("GRIB_DEFAULT_SEMI_MAJOR", |
2560 | 0 | "6377563.396")); |
2561 | 217 | const double b = |
2562 | 217 | bHaveEarthModel |
2563 | 217 | ? meta->gds.minEarth * 1.0e3 |
2564 | 217 | : (meta->gds.f_sphere |
2565 | 0 | ? a |
2566 | 0 | : CPLAtof(CPLGetConfigOption("GRIB_DEFAULT_SEMI_MINOR", |
2567 | 0 | "6356256.910"))); |
2568 | 217 | if (meta->gds.majEarth == 0 || meta->gds.minEarth == 0) |
2569 | 0 | { |
2570 | 0 | CPLDebug("GRIB", "No earth model. Assuming a=%f and b=%f", a, b); |
2571 | 0 | } |
2572 | 217 | else if (meta->gds.majEarth < 0 || meta->gds.minEarth < 0) |
2573 | 0 | { |
2574 | 0 | const char *pszUseDefaultSpheroid = |
2575 | 0 | CPLGetConfigOption("GRIB_USE_DEFAULT_SPHEROID", nullptr); |
2576 | 0 | if (!pszUseDefaultSpheroid) |
2577 | 0 | { |
2578 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2579 | 0 | "The GRIB file contains invalid values for the spheroid. " |
2580 | 0 | "You may set the GRIB_USE_DEFAULT_SPHEROID configuration " |
2581 | 0 | "option to YES to use a default spheroid with " |
2582 | 0 | "a=%f and b=%f", |
2583 | 0 | a, b); |
2584 | 0 | return; |
2585 | 0 | } |
2586 | 0 | else if (!CPLTestBool(pszUseDefaultSpheroid)) |
2587 | 0 | { |
2588 | 0 | return; |
2589 | 0 | } |
2590 | 0 | CPLDebug("GRIB", "Invalid earth model. Assuming a=%f and b=%f", a, b); |
2591 | 0 | } |
2592 | | |
2593 | 217 | if (meta->gds.f_sphere || (a == b)) |
2594 | 154 | { |
2595 | 154 | oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr, |
2596 | 154 | "Sphere", a, 0.0); |
2597 | 154 | } |
2598 | 63 | else |
2599 | 63 | { |
2600 | 63 | const double fInv = a / (a - b); |
2601 | 63 | if (std::abs(a - 6378137.0) < 0.01 && |
2602 | 63 | std::abs(fInv - 298.257223563) < 1e-9) // WGS84 |
2603 | 0 | { |
2604 | 0 | if (meta->gds.projType == GS3_LATLON) |
2605 | 0 | oSRS.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); |
2606 | 0 | else |
2607 | 0 | { |
2608 | 0 | oSRS.SetGeogCS("Coordinate System imported from GRIB file", |
2609 | 0 | "WGS_1984", "WGS 84", 6378137., 298.257223563); |
2610 | 0 | } |
2611 | 0 | } |
2612 | 63 | else if (std::abs(a - 6378137.0) < 0.01 && |
2613 | 63 | std::abs(fInv - 298.257222101) < 1e-9) // GRS80 |
2614 | 0 | { |
2615 | 0 | oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr, |
2616 | 0 | "GRS80", 6378137., 298.257222101); |
2617 | 0 | } |
2618 | 63 | else |
2619 | 63 | { |
2620 | 63 | oSRS.SetGeogCS("Coordinate System imported from GRIB file", nullptr, |
2621 | 63 | "Spheroid imported from GRIB file", a, fInv); |
2622 | 63 | } |
2623 | 63 | } |
2624 | | |
2625 | 217 | if (meta->gds.projType == GS3_ROTATED_LATLON) |
2626 | 67 | { |
2627 | 67 | oSRS.SetDerivedGeogCRSWithPoleRotationGRIBConvention( |
2628 | 67 | oSRS.GetName(), meta->gds.southLat, Lon360to180(meta->gds.southLon), |
2629 | 67 | meta->gds.angleRotate); |
2630 | 67 | } |
2631 | | |
2632 | 217 | OGRSpatialReference oLL; // Construct the "geographic" part of oSRS. |
2633 | 217 | oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
2634 | 217 | oLL.CopyGeogCSFrom(&oSRS); |
2635 | | |
2636 | 217 | double rMinX = 0.0; |
2637 | 217 | double rMaxY = 0.0; |
2638 | 217 | double rPixelSizeX = 0.0; |
2639 | 217 | double rPixelSizeY = 0.0; |
2640 | 217 | bool bError = false; |
2641 | 217 | if (meta->gds.projType == GS3_ORTHOGRAPHIC) |
2642 | 0 | { |
2643 | | // This is what should work, but it doesn't. Dx seems to have an |
2644 | | // inverse relation with pixel size. |
2645 | | // rMinX = -meta->gds.Dx * (meta->gds.Nx / 2); |
2646 | | // rMaxY = meta->gds.Dy * (meta->gds.Ny / 2); |
2647 | | // Hardcoded for now, assumption: GEOS projection, full disc (like MSG). |
2648 | 0 | const double geosExtentInMeters = 11137496.552; |
2649 | 0 | rMinX = -(geosExtentInMeters / 2); |
2650 | 0 | rMaxY = geosExtentInMeters / 2; |
2651 | 0 | rPixelSizeX = geosExtentInMeters / meta->gds.Nx; |
2652 | 0 | rPixelSizeY = geosExtentInMeters / meta->gds.Ny; |
2653 | 0 | } |
2654 | 217 | else if (meta->gds.projType == GS3_TRANSVERSE_MERCATOR) |
2655 | 0 | { |
2656 | 0 | rMinX = meta->gds.x1; |
2657 | 0 | rMaxY = meta->gds.y2; |
2658 | 0 | rPixelSizeX = meta->gds.Dx; |
2659 | 0 | rPixelSizeY = meta->gds.Dy; |
2660 | 0 | } |
2661 | 217 | else if (oSRS.IsProjected() && meta->gds.projType != GS3_ROTATED_LATLON) |
2662 | 71 | { |
2663 | | // Longitude in degrees, to be transformed to meters (or degrees in |
2664 | | // case of latlon). |
2665 | 71 | rMinX = Lon360to180(meta->gds.lon1); |
2666 | | // Latitude in degrees, to be transformed to meters. |
2667 | 71 | double dfGridOriY = meta->gds.lat1; |
2668 | | |
2669 | 71 | if (m_poSRS == nullptr || m_poLL == nullptr || |
2670 | 71 | !m_poSRS->IsSame(&oSRS) || !m_poLL->IsSame(&oLL)) |
2671 | 71 | { |
2672 | 71 | m_poCT = std::unique_ptr<OGRCoordinateTransformation>( |
2673 | 71 | OGRCreateCoordinateTransformation(&(oLL), &(oSRS))); |
2674 | 71 | } |
2675 | | |
2676 | | // Transform it to meters. |
2677 | 71 | if ((m_poCT != nullptr) && m_poCT->Transform(1, &rMinX, &dfGridOriY)) |
2678 | 4 | { |
2679 | 4 | if (meta->gds.scan == GRIB2BIT_2) // Y is minY, GDAL wants maxY. |
2680 | 0 | { |
2681 | 0 | const char *pszConfigOpt = CPLGetConfigOption( |
2682 | 0 | "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_MOST", |
2683 | 0 | nullptr); |
2684 | 0 | bool bLatOfFirstPointIsSouthernMost = |
2685 | 0 | !pszConfigOpt || CPLTestBool(pszConfigOpt); |
2686 | | |
2687 | | // Hack for a file called MANAL_2023030103.grb2 that |
2688 | | // uses LCC and has Latitude of false origin = 30 |
2689 | | // Longitude of false origin = 140 |
2690 | | // Latitude of 1st standard parallel = 60 |
2691 | | // Latitude of 2nd standard parallel = 30 |
2692 | | // but whose (meta->gds.lon1, meta->gds.lat1) qualifies the |
2693 | | // northern-most point of the grid and not the bottom-most one |
2694 | | // as it should given the scan == GRIB2BIT_2 |
2695 | 0 | if (!pszConfigOpt && meta->gds.projType == GS3_LAMBERT && |
2696 | 0 | std::fabs(meta->gds.scaleLat1 - 60) <= 1e-8 && |
2697 | 0 | std::fabs(meta->gds.scaleLat2 - 30) <= 1e-8 && |
2698 | 0 | std::fabs(meta->gds.meshLat - 30) <= 1e-8 && |
2699 | 0 | std::fabs(Lon360to180(meta->gds.orientLon) - 140) <= 1e-8) |
2700 | 0 | { |
2701 | 0 | double dfXCenterProj = Lon360to180(meta->gds.orientLon); |
2702 | 0 | double dfYCenterProj = meta->gds.meshLat; |
2703 | 0 | if (m_poCT->Transform(1, &dfXCenterProj, &dfYCenterProj)) |
2704 | 0 | { |
2705 | 0 | double dfXCenterGridNominal = |
2706 | 0 | rMinX + nRasterXSize * meta->gds.Dx / 2; |
2707 | 0 | double dfYCenterGridNominal = |
2708 | 0 | dfGridOriY + nRasterYSize * meta->gds.Dy / 2; |
2709 | 0 | double dfXCenterGridBuggy = dfXCenterGridNominal; |
2710 | 0 | double dfYCenterGridBuggy = |
2711 | 0 | dfGridOriY - nRasterYSize * meta->gds.Dy / 2; |
2712 | 0 | const auto SQR = [](double x) { return x * x; }; |
2713 | 0 | if (SQR(dfXCenterGridBuggy - dfXCenterProj) + |
2714 | 0 | SQR(dfYCenterGridBuggy - dfYCenterProj) < |
2715 | 0 | SQR(10) * |
2716 | 0 | (SQR(dfXCenterGridNominal - dfXCenterProj) + |
2717 | 0 | SQR(dfYCenterGridNominal - dfYCenterProj))) |
2718 | 0 | { |
2719 | 0 | CPLError( |
2720 | 0 | CE_Warning, CPLE_AppDefined, |
2721 | 0 | "Likely buggy grid registration for GRIB2 " |
2722 | 0 | "product: heuristics shows that the " |
2723 | 0 | "latitudeOfFirstGridPoint is likely to qualify " |
2724 | 0 | "the latitude of the northern-most grid point " |
2725 | 0 | "instead of the southern-most grid point as " |
2726 | 0 | "expected. Please report to data producer. " |
2727 | 0 | "This heuristics can be disabled by setting " |
2728 | 0 | "the " |
2729 | 0 | "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_" |
2730 | 0 | "MOST configuration option to YES."); |
2731 | 0 | bLatOfFirstPointIsSouthernMost = false; |
2732 | 0 | } |
2733 | 0 | } |
2734 | 0 | } |
2735 | 0 | if (bLatOfFirstPointIsSouthernMost) |
2736 | 0 | { |
2737 | | // -1 because we GDAL needs the coordinates of the centre of |
2738 | | // the pixel. |
2739 | 0 | rMaxY = dfGridOriY + (meta->gds.Ny - 1) * meta->gds.Dy; |
2740 | 0 | } |
2741 | 0 | else |
2742 | 0 | { |
2743 | 0 | rMaxY = dfGridOriY; |
2744 | 0 | } |
2745 | 0 | } |
2746 | 4 | else |
2747 | 4 | { |
2748 | 4 | rMaxY = dfGridOriY; |
2749 | 4 | } |
2750 | 4 | rPixelSizeX = meta->gds.Dx; |
2751 | 4 | rPixelSizeY = meta->gds.Dy; |
2752 | 4 | } |
2753 | 67 | else |
2754 | 67 | { |
2755 | 67 | rMinX = 0.0; |
2756 | | // rMaxY = 0.0; |
2757 | | |
2758 | 67 | rPixelSizeX = 1.0; |
2759 | 67 | rPixelSizeY = -1.0; |
2760 | | |
2761 | 67 | bError = true; |
2762 | 67 | CPLError(CE_Warning, CPLE_AppDefined, |
2763 | 67 | "Unable to perform coordinate transformations, so the " |
2764 | 67 | "correct projected geotransform could not be deduced " |
2765 | 67 | "from the lat/long control points. " |
2766 | 67 | "Defaulting to ungeoreferenced."); |
2767 | 67 | } |
2768 | 71 | } |
2769 | 146 | else |
2770 | 146 | { |
2771 | | // Longitude in degrees, to be transformed to meters (or degrees in |
2772 | | // case of latlon). |
2773 | 146 | rMinX = meta->gds.lon1; |
2774 | | // Latitude in degrees, to be transformed to meters. |
2775 | 146 | rMaxY = meta->gds.lat1; |
2776 | | |
2777 | 146 | double rMinY = meta->gds.lat2; |
2778 | 146 | double rMaxX = meta->gds.lon2; |
2779 | 146 | if (meta->gds.lat2 > rMaxY) |
2780 | 59 | { |
2781 | 59 | rMaxY = meta->gds.lat2; |
2782 | 59 | rMinY = meta->gds.lat1; |
2783 | 59 | } |
2784 | | |
2785 | 146 | if (meta->gds.Nx == 1) |
2786 | 0 | rPixelSizeX = meta->gds.Dx; |
2787 | 146 | else if (meta->gds.lon1 > meta->gds.lon2) |
2788 | 58 | rPixelSizeX = (360.0 - (meta->gds.lon1 - meta->gds.lon2)) / |
2789 | 58 | (meta->gds.Nx - 1); |
2790 | 88 | else |
2791 | 88 | rPixelSizeX = |
2792 | 88 | (meta->gds.lon2 - meta->gds.lon1) / (meta->gds.Nx - 1); |
2793 | | |
2794 | 146 | if (meta->gds.Ny == 1) |
2795 | 19 | rPixelSizeY = meta->gds.Dy; |
2796 | 127 | else |
2797 | 127 | rPixelSizeY = (rMaxY - rMinY) / (meta->gds.Ny - 1); |
2798 | | |
2799 | | // Do some sanity checks for cases that can't be handled by the above |
2800 | | // pixel size corrections. GRIB1 has a minimum precision of 0.001 |
2801 | | // for latitudes and longitudes, so we'll allow a bit higher than that. |
2802 | 146 | if (rPixelSizeX < 0 || fabs(rPixelSizeX - meta->gds.Dx) > 0.002) |
2803 | 122 | rPixelSizeX = meta->gds.Dx; |
2804 | | |
2805 | 146 | if (rPixelSizeY < 0 || fabs(rPixelSizeY - meta->gds.Dy) > 0.002) |
2806 | 80 | rPixelSizeY = meta->gds.Dy; |
2807 | | |
2808 | | // GRIB2 files have longitudes in the [0-360] range |
2809 | | // Shift them to the traditional [-180,180] longitude range |
2810 | | // See https://github.com/OSGeo/gdal/issues/4524 |
2811 | 146 | if ((rMinX + rPixelSizeX >= 180 || rMaxX - rPixelSizeX >= 180) && |
2812 | 146 | CPLTestBool( |
2813 | 100 | CPLGetConfigOption("GRIB_ADJUST_LONGITUDE_RANGE", "YES"))) |
2814 | 100 | { |
2815 | 100 | if (rPixelSizeX * nRasterXSize > 360 + rPixelSizeX / 4) |
2816 | 62 | CPLDebug("GRIB", "Cannot properly handle GRIB2 files with " |
2817 | 62 | "overlaps and 0-360 longitudes"); |
2818 | 38 | else if (rMinX == 180) |
2819 | 0 | { |
2820 | | // Case of https://github.com/OSGeo/gdal/issues/10655 |
2821 | 0 | CPLDebug("GRIB", "Shifting longitudes from %lf:%lf to %lf:%lf", |
2822 | 0 | rMinX, rMaxX, -180.0, Lon360to180(rMaxX)); |
2823 | 0 | rMinX = -180; |
2824 | 0 | } |
2825 | 38 | else if (fabs(360 - rPixelSizeX * nRasterXSize) < rPixelSizeX / 4 && |
2826 | 38 | rMinX <= 180 && meta->gds.projType == GS3_LATLON) |
2827 | 0 | { |
2828 | | // Find the first row number east of the antimeridian |
2829 | 0 | const int nSplitAndSwapColumnCandidate = |
2830 | 0 | static_cast<int>(ceil((180 - rMinX) / rPixelSizeX)); |
2831 | 0 | if (nSplitAndSwapColumnCandidate < nRasterXSize) |
2832 | 0 | { |
2833 | 0 | nSplitAndSwapColumn = nSplitAndSwapColumnCandidate; |
2834 | 0 | CPLDebug("GRIB", |
2835 | 0 | "Rewrapping around the antimeridian at column %d", |
2836 | 0 | nSplitAndSwapColumn); |
2837 | 0 | rMinX = -180; |
2838 | 0 | } |
2839 | 0 | } |
2840 | 38 | else if (Lon360to180(rMinX) > Lon360to180(rMaxX)) |
2841 | 20 | { |
2842 | 20 | CPLDebug("GRIB", "GRIB with 0-360 longitudes spanning across " |
2843 | 20 | "the antimeridian"); |
2844 | 20 | rMinX = Lon360to180(rMinX); |
2845 | 20 | } |
2846 | 18 | else |
2847 | 18 | { |
2848 | 18 | CPLDebug("GRIB", "Shifting longitudes from %lf:%lf to %lf:%lf", |
2849 | 18 | rMinX, rMaxX, Lon360to180(rMinX), Lon360to180(rMaxX)); |
2850 | 18 | rMinX = Lon360to180(rMinX); |
2851 | 18 | } |
2852 | 100 | } |
2853 | 146 | } |
2854 | | |
2855 | | // https://gdal.org/user/raster_data_model.html : |
2856 | | // we need the top left corner of the top left pixel. |
2857 | | // At the moment we have the center of the pixel. |
2858 | 217 | rMinX -= rPixelSizeX / 2; |
2859 | 217 | rMaxY += rPixelSizeY / 2; |
2860 | | |
2861 | 217 | adfGeoTransform[0] = rMinX; |
2862 | 217 | adfGeoTransform[3] = rMaxY; |
2863 | 217 | adfGeoTransform[1] = rPixelSizeX; |
2864 | 217 | adfGeoTransform[5] = -rPixelSizeY; |
2865 | | |
2866 | 217 | if (bError) |
2867 | 67 | m_poSRS.reset(); |
2868 | 150 | else |
2869 | 150 | m_poSRS.reset(oSRS.Clone()); |
2870 | 217 | m_poLL = std::unique_ptr<OGRSpatialReference>(oLL.Clone()); |
2871 | 217 | } |
2872 | | |
2873 | | /************************************************************************/ |
2874 | | /* GDALDeregister_GRIB() */ |
2875 | | /************************************************************************/ |
2876 | | |
2877 | | static void GDALDeregister_GRIB(GDALDriver *) |
2878 | 0 | { |
2879 | 0 | if (hGRIBMutex != nullptr) |
2880 | 0 | { |
2881 | 0 | MetanameCleanup(); |
2882 | 0 | CPLDestroyMutex(hGRIBMutex); |
2883 | 0 | hGRIBMutex = nullptr; |
2884 | 0 | } |
2885 | 0 | } |
2886 | | |
2887 | | /************************************************************************/ |
2888 | | /* GDALGRIBDriver */ |
2889 | | /************************************************************************/ |
2890 | | |
2891 | | class GDALGRIBDriver : public GDALDriver |
2892 | | { |
2893 | | std::mutex m_oMutex{}; |
2894 | | bool m_bHasFullInitMetadata = false; |
2895 | | void InitializeMetadata(); |
2896 | | |
2897 | | public: |
2898 | 2 | GDALGRIBDriver() = default; |
2899 | | |
2900 | | char **GetMetadata(const char *pszDomain = "") override; |
2901 | | const char *GetMetadataItem(const char *pszName, |
2902 | | const char *pszDomain) override; |
2903 | | }; |
2904 | | |
2905 | | /************************************************************************/ |
2906 | | /* InitializeMetadata() */ |
2907 | | /************************************************************************/ |
2908 | | |
2909 | | void GDALGRIBDriver::InitializeMetadata() |
2910 | 0 | { |
2911 | 0 | { |
2912 | | // Defer until necessary the setting of the CreationOptionList |
2913 | | // to let a chance to JPEG2000 drivers to have been loaded. |
2914 | 0 | if (!m_bHasFullInitMetadata) |
2915 | 0 | { |
2916 | 0 | m_bHasFullInitMetadata = true; |
2917 | |
|
2918 | 0 | std::vector<CPLString> aosJ2KDrivers; |
2919 | 0 | for (size_t i = 0; i < CPL_ARRAYSIZE(apszJ2KDrivers); i++) |
2920 | 0 | { |
2921 | 0 | if (GDALGetDriverByName(apszJ2KDrivers[i]) != nullptr) |
2922 | 0 | { |
2923 | 0 | aosJ2KDrivers.push_back(apszJ2KDrivers[i]); |
2924 | 0 | } |
2925 | 0 | } |
2926 | |
|
2927 | 0 | CPLString osCreationOptionList( |
2928 | 0 | "<CreationOptionList>" |
2929 | 0 | " <Option name='DATA_ENCODING' type='string-select' " |
2930 | 0 | "default='AUTO' " |
2931 | 0 | "description='How data is encoded internally'>" |
2932 | 0 | " <Value>AUTO</Value>" |
2933 | 0 | " <Value>SIMPLE_PACKING</Value>" |
2934 | 0 | " <Value>COMPLEX_PACKING</Value>" |
2935 | 0 | " <Value>IEEE_FLOATING_POINT</Value>"); |
2936 | 0 | if (GDALGetDriverByName("PNG") != nullptr) |
2937 | 0 | osCreationOptionList += " <Value>PNG</Value>"; |
2938 | 0 | if (!aosJ2KDrivers.empty()) |
2939 | 0 | osCreationOptionList += " <Value>JPEG2000</Value>"; |
2940 | 0 | osCreationOptionList += |
2941 | 0 | " </Option>" |
2942 | 0 | " <Option name='NBITS' type='int' default='0' " |
2943 | 0 | "description='Number of bits per value'/>" |
2944 | 0 | " <Option name='DECIMAL_SCALE_FACTOR' type='int' default='0' " |
2945 | 0 | "description='Value such that raw values are multiplied by " |
2946 | 0 | "10^DECIMAL_SCALE_FACTOR before integer encoding'/>" |
2947 | 0 | " <Option name='SPATIAL_DIFFERENCING_ORDER' type='int' " |
2948 | 0 | "default='0' " |
2949 | 0 | "description='Order of spatial difference' min='0' max='2'/>"; |
2950 | 0 | if (!aosJ2KDrivers.empty()) |
2951 | 0 | { |
2952 | 0 | osCreationOptionList += |
2953 | 0 | " <Option name='COMPRESSION_RATIO' type='int' " |
2954 | 0 | "default='1' min='1' max='100' " |
2955 | 0 | "description='N:1 target compression ratio for JPEG2000'/>" |
2956 | 0 | " <Option name='JPEG2000_DRIVER' type='string-select' " |
2957 | 0 | "description='Explicitly select a JPEG2000 driver'>"; |
2958 | 0 | for (size_t i = 0; i < aosJ2KDrivers.size(); i++) |
2959 | 0 | { |
2960 | 0 | osCreationOptionList += |
2961 | 0 | " <Value>" + aosJ2KDrivers[i] + "</Value>"; |
2962 | 0 | } |
2963 | 0 | osCreationOptionList += " </Option>"; |
2964 | 0 | } |
2965 | 0 | osCreationOptionList += |
2966 | 0 | " <Option name='DISCIPLINE' type='int' " |
2967 | 0 | "description='Discipline of the processed data'/>" |
2968 | 0 | " <Option name='IDS' type='string' " |
2969 | 0 | "description='String equivalent to the GRIB_IDS metadata " |
2970 | 0 | "item'/>" |
2971 | 0 | " <Option name='IDS_CENTER' type='int' " |
2972 | 0 | "description='Originating/generating center'/>" |
2973 | 0 | " <Option name='IDS_SUBCENTER' type='int' " |
2974 | 0 | "description='Originating/generating subcenter'/>" |
2975 | 0 | " <Option name='IDS_MASTER_TABLE' type='int' " |
2976 | 0 | "description='GRIB master tables version number'/>" |
2977 | 0 | " <Option name='IDS_SIGNF_REF_TIME' type='int' " |
2978 | 0 | "description='Significance of Reference Time'/>" |
2979 | 0 | " <Option name='IDS_REF_TIME' type='string' " |
2980 | 0 | "description='Reference time as YYYY-MM-DDTHH:MM:SSZ'/>" |
2981 | 0 | " <Option name='IDS_PROD_STATUS' type='int' " |
2982 | 0 | "description='Production Status of Processed data'/>" |
2983 | 0 | " <Option name='IDS_TYPE' type='int' " |
2984 | 0 | "description='Type of processed data'/>" |
2985 | 0 | " <Option name='PDS_PDTN' type='int' " |
2986 | 0 | "description='Product Definition Template Number'/>" |
2987 | 0 | " <Option name='PDS_TEMPLATE_NUMBERS' type='string' " |
2988 | 0 | "description='Product definition template raw numbers'/>" |
2989 | 0 | " <Option name='PDS_TEMPLATE_ASSEMBLED_VALUES' type='string' " |
2990 | 0 | "description='Product definition template assembled values'/>" |
2991 | 0 | " <Option name='INPUT_UNIT' type='string' " |
2992 | 0 | "description='Unit of input values. Only for temperatures. C " |
2993 | 0 | "or K'/>" |
2994 | 0 | " <Option name='BAND_*' type='string' " |
2995 | 0 | "description='Override options at band level'/>" |
2996 | 0 | "</CreationOptionList>"; |
2997 | |
|
2998 | 0 | SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST, osCreationOptionList); |
2999 | 0 | } |
3000 | 0 | } |
3001 | 0 | } |
3002 | | |
3003 | | /************************************************************************/ |
3004 | | /* GetMetadata() */ |
3005 | | /************************************************************************/ |
3006 | | |
3007 | | char **GDALGRIBDriver::GetMetadata(const char *pszDomain) |
3008 | 0 | { |
3009 | 0 | std::lock_guard oLock(m_oMutex); |
3010 | 0 | if (pszDomain == nullptr || EQUAL(pszDomain, "")) |
3011 | 0 | { |
3012 | 0 | InitializeMetadata(); |
3013 | 0 | } |
3014 | 0 | return GDALDriver::GetMetadata(pszDomain); |
3015 | 0 | } |
3016 | | |
3017 | | /************************************************************************/ |
3018 | | /* GetMetadataItem() */ |
3019 | | /************************************************************************/ |
3020 | | |
3021 | | const char *GDALGRIBDriver::GetMetadataItem(const char *pszName, |
3022 | | const char *pszDomain) |
3023 | 23.8k | { |
3024 | 23.8k | std::lock_guard oLock(m_oMutex); |
3025 | 23.8k | if (pszDomain == nullptr || EQUAL(pszDomain, "")) |
3026 | 23.8k | { |
3027 | | // Defer until necessary the setting of the CreationOptionList |
3028 | | // to let a chance to JPEG2000 drivers to have been loaded. |
3029 | 23.8k | if (EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST)) |
3030 | 0 | InitializeMetadata(); |
3031 | 23.8k | } |
3032 | 23.8k | return GDALDriver::GetMetadataItem(pszName, pszDomain); |
3033 | 23.8k | } |
3034 | | |
3035 | | /************************************************************************/ |
3036 | | /* GDALRegister_GRIB() */ |
3037 | | /************************************************************************/ |
3038 | | |
3039 | | void GDALRegister_GRIB() |
3040 | | |
3041 | 2 | { |
3042 | 2 | if (GDALGetDriverByName(DRIVER_NAME) != nullptr) |
3043 | 0 | return; |
3044 | | |
3045 | 2 | GDALDriver *poDriver = new GDALGRIBDriver(); |
3046 | 2 | GRIBDriverSetCommonMetadata(poDriver); |
3047 | | |
3048 | 2 | poDriver->pfnOpen = GRIBDataset::Open; |
3049 | 2 | poDriver->pfnCreateCopy = GRIBDataset::CreateCopy; |
3050 | 2 | poDriver->pfnUnloadDriver = GDALDeregister_GRIB; |
3051 | | |
3052 | 2 | #ifdef USE_AEC |
3053 | 2 | poDriver->SetMetadataItem("HAVE_AEC", "YES"); |
3054 | 2 | #endif |
3055 | | |
3056 | 2 | GetGDALDriverManager()->RegisterDriver(poDriver); |
3057 | 2 | } |