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