/src/gdal/frmts/netcdf/netcdfdataset.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: netCDF read/write Driver |
4 | | * Purpose: GDAL bindings over netCDF library. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * Even Rouault <even.rouault at spatialys.com> |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2004, Frank Warmerdam |
10 | | * Copyright (c) 2007-2016, Even Rouault <even.rouault at spatialys.com> |
11 | | * Copyright (c) 2010, Kyle Shannon <kyle at pobox dot com> |
12 | | * Copyright (c) 2021, CLS |
13 | | * |
14 | | * SPDX-License-Identifier: MIT |
15 | | ****************************************************************************/ |
16 | | |
17 | | #include "cpl_port.h" |
18 | | |
19 | | #include <array> |
20 | | #include <cassert> |
21 | | #include <cctype> |
22 | | #include <cerrno> |
23 | | #include <climits> |
24 | | #include <cmath> |
25 | | #include <cstdio> |
26 | | #include <cstdlib> |
27 | | #include <cstring> |
28 | | #include <ctime> |
29 | | #include <algorithm> |
30 | | #include <limits> |
31 | | #include <map> |
32 | | #include <mutex> |
33 | | #include <set> |
34 | | #include <queue> |
35 | | #include <string> |
36 | | #include <tuple> |
37 | | #include <utility> |
38 | | #include <vector> |
39 | | |
40 | | // Must be included after standard includes, otherwise VS2015 fails when |
41 | | // including <ctime> |
42 | | #include "netcdfdataset.h" |
43 | | #include "netcdfdrivercore.h" |
44 | | #include "netcdfsg.h" |
45 | | #include "netcdfuffd.h" |
46 | | |
47 | | #include "netcdf_mem.h" |
48 | | |
49 | | #include "cpl_conv.h" |
50 | | #include "cpl_error.h" |
51 | | #include "cpl_float.h" |
52 | | #include "cpl_json.h" |
53 | | #include "cpl_minixml.h" |
54 | | #include "cpl_multiproc.h" |
55 | | #include "cpl_progress.h" |
56 | | #include "cpl_time.h" |
57 | | #include "gdal.h" |
58 | | #include "gdal_frmts.h" |
59 | | #include "gdal_priv_templates.hpp" |
60 | | #include "ogr_core.h" |
61 | | #include "ogr_srs_api.h" |
62 | | |
63 | | // netCDF 4.8 switched to expecting filenames in UTF-8 on Windows |
64 | | // But with netCDF 4.9 and https://github.com/Unidata/netcdf-c/pull/2277/, |
65 | | // this is apparently back to expecting filenames in current codepage... |
66 | | // Detect netCDF 4.8 with NC_ENCZARR |
67 | | // Detect netCDF 4.9 with NC_NOATTCREORD |
68 | | #if defined(NC_ENCZARR) && !defined(NC_NOATTCREORD) |
69 | | #define NETCDF_USES_UTF8 |
70 | | #endif |
71 | | |
72 | | // Internal function declarations. |
73 | | |
74 | | static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget); |
75 | | |
76 | | static void |
77 | | NCDFAddGDALHistory(int fpImage, const char *pszFilename, bool bWriteGDALVersion, |
78 | | bool bWriteGDALHistory, const char *pszOldHist, |
79 | | const char *pszFunctionName, |
80 | | const char *pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS); |
81 | | |
82 | | static void NCDFAddHistory(int fpImage, const char *pszAddHist, |
83 | | const char *pszOldHist); |
84 | | |
85 | | static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc, |
86 | | size_t *nDestSize); |
87 | | |
88 | | // Var / attribute helper functions. |
89 | | static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName, |
90 | | const char *pszValue); |
91 | | |
92 | | // Replace this where used. |
93 | | static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue); |
94 | | static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue); |
95 | | |
96 | | // Replace this where used. |
97 | | static char **NCDFTokenizeArray(const char *pszValue); |
98 | | static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand, |
99 | | GDALRasterBand *poDstBand, int fpImage, int CDFVarID, |
100 | | const char *pszMatchPrefix = nullptr); |
101 | | |
102 | | // NetCDF-4 groups helper functions. |
103 | | // They all work also for NetCDF-3 files which are considered as |
104 | | // NetCDF-4 file with only one group. |
105 | | static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName, |
106 | | int *pnGroupId, int *pnVarId); |
107 | | static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds); |
108 | | static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups, |
109 | | int **ppanSubGroupIds); |
110 | | static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName, |
111 | | bool bNC3Compat = true); |
112 | | static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName, |
113 | | bool bNC3Compat = true); |
114 | | static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId); |
115 | | |
116 | | static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar, |
117 | | char **ppszFullName, |
118 | | bool bMandatory = false); |
119 | | static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId, |
120 | | const char *pszAtt, int *pnAtt, |
121 | | bool bMandatory = false); |
122 | | static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars); |
123 | | |
124 | | // Uncomment this for more debug output. |
125 | | // #define NCDF_DEBUG 1 |
126 | | |
127 | | CPLMutex *hNCMutex = nullptr; |
128 | | |
129 | | // Workaround https://github.com/OSGeo/gdal/issues/6253 |
130 | | // Having 2 netCDF handles on the same file doesn't work in a multi-threaded |
131 | | // way. Apparently having the same handle works better (this is OK since |
132 | | // we have a global mutex on the netCDF library) |
133 | | static std::map<std::string, int> goMapNameToNetCDFId; |
134 | | static std::map<int, std::pair<std::string, int>> goMapNetCDFIdToKeyAndCount; |
135 | | |
136 | | int GDAL_nc_open(const char *pszFilename, int nMode, int *pID) |
137 | 399 | { |
138 | 399 | std::string osKey(pszFilename); |
139 | 399 | osKey += "#####"; |
140 | 399 | osKey += std::to_string(nMode); |
141 | 399 | auto oIter = goMapNameToNetCDFId.find(osKey); |
142 | 399 | if (oIter == goMapNameToNetCDFId.end()) |
143 | 399 | { |
144 | 399 | int ret = nc_open(pszFilename, nMode, pID); |
145 | 399 | if (ret != NC_NOERR) |
146 | 0 | return ret; |
147 | 399 | goMapNameToNetCDFId[osKey] = *pID; |
148 | 399 | goMapNetCDFIdToKeyAndCount[*pID] = |
149 | 399 | std::pair<std::string, int>(osKey, 1); |
150 | 399 | return ret; |
151 | 399 | } |
152 | 0 | else |
153 | 0 | { |
154 | 0 | *pID = oIter->second; |
155 | 0 | goMapNetCDFIdToKeyAndCount[oIter->second].second++; |
156 | 0 | return NC_NOERR; |
157 | 0 | } |
158 | 399 | } |
159 | | |
160 | | int GDAL_nc_close(int cdfid) |
161 | 798 | { |
162 | 798 | int ret = NC_NOERR; |
163 | 798 | auto oIter = goMapNetCDFIdToKeyAndCount.find(cdfid); |
164 | 798 | if (oIter != goMapNetCDFIdToKeyAndCount.end()) |
165 | 399 | { |
166 | 399 | if (--oIter->second.second == 0) |
167 | 399 | { |
168 | 399 | ret = nc_close(cdfid); |
169 | 399 | goMapNameToNetCDFId.erase(oIter->second.first); |
170 | 399 | goMapNetCDFIdToKeyAndCount.erase(oIter); |
171 | 399 | } |
172 | 399 | } |
173 | 399 | else |
174 | 399 | { |
175 | | // we can go here if file opened with nc_open_mem() or nc_create() |
176 | 399 | ret = nc_close(cdfid); |
177 | 399 | } |
178 | 798 | return ret; |
179 | 798 | } |
180 | | |
181 | | /************************************************************************/ |
182 | | /* ==================================================================== */ |
183 | | /* netCDFRasterBand */ |
184 | | /* ==================================================================== */ |
185 | | /************************************************************************/ |
186 | | |
187 | | class netCDFRasterBand final : public GDALPamRasterBand |
188 | | { |
189 | | friend class netCDFDataset; |
190 | | |
191 | | nc_type nc_datatype; |
192 | | int cdfid; |
193 | | int nZId; |
194 | | int nZDim; |
195 | | int nLevel; |
196 | | int nBandXPos; |
197 | | int nBandYPos; |
198 | | int *panBandZPos; |
199 | | int *panBandZLev; |
200 | | bool m_bNoDataSet = false; |
201 | | double m_dfNoDataValue = 0; |
202 | | bool m_bNoDataSetAsInt64 = false; |
203 | | int64_t m_nNodataValueInt64 = 0; |
204 | | bool m_bNoDataSetAsUInt64 = false; |
205 | | uint64_t m_nNodataValueUInt64 = 0; |
206 | | bool bValidRangeValid = false; |
207 | | double adfValidRange[2]{0, 0}; |
208 | | bool m_bHaveScale = false; |
209 | | bool m_bHaveOffset = false; |
210 | | double m_dfScale = 1; |
211 | | double m_dfOffset = 0; |
212 | | CPLString m_osUnitType{}; |
213 | | bool bSignedData; |
214 | | bool bCheckLongitude; |
215 | | bool m_bCreateMetadataFromOtherVarsDone = false; |
216 | | |
217 | | void CreateMetadataFromAttributes(); |
218 | | void CreateMetadataFromOtherVars(); |
219 | | |
220 | | template <class T> |
221 | | void CheckData(void *pImage, void *pImageNC, size_t nTmpBlockXSize, |
222 | | size_t nTmpBlockYSize, bool bCheckIsNan = false); |
223 | | template <class T> |
224 | | void CheckDataCpx(void *pImage, void *pImageNC, size_t nTmpBlockXSize, |
225 | | size_t nTmpBlockYSize, bool bCheckIsNan = false); |
226 | | void SetBlockSize(); |
227 | | |
228 | | bool FetchNetcdfChunk(size_t xstart, size_t ystart, void *pImage); |
229 | | |
230 | | void SetNoDataValueNoUpdate(double dfNoData); |
231 | | void SetNoDataValueNoUpdate(int64_t nNoData); |
232 | | void SetNoDataValueNoUpdate(uint64_t nNoData); |
233 | | |
234 | | void SetOffsetNoUpdate(double dfVal); |
235 | | void SetScaleNoUpdate(double dfVal); |
236 | | void SetUnitTypeNoUpdate(const char *pszNewValue); |
237 | | |
238 | | protected: |
239 | | CPLXMLNode *SerializeToXML(const char *pszUnused) override; |
240 | | |
241 | | public: |
242 | | struct CONSTRUCTOR_OPEN |
243 | | { |
244 | | }; |
245 | | |
246 | | struct CONSTRUCTOR_CREATE |
247 | | { |
248 | | }; |
249 | | |
250 | | netCDFRasterBand(const CONSTRUCTOR_OPEN &, netCDFDataset *poDS, |
251 | | int nGroupId, int nZId, int nZDim, int nLevel, |
252 | | const int *panBandZLen, const int *panBandPos, int nBand); |
253 | | netCDFRasterBand(const CONSTRUCTOR_CREATE &, netCDFDataset *poDS, |
254 | | GDALDataType eType, int nBand, bool bSigned = true, |
255 | | const char *pszBandName = nullptr, |
256 | | const char *pszLongName = nullptr, int nZId = -1, |
257 | | int nZDim = 2, int nLevel = 0, |
258 | | const int *panBandZLev = nullptr, |
259 | | const int *panBandZPos = nullptr, |
260 | | const int *paDimIds = nullptr); |
261 | | virtual ~netCDFRasterBand(); |
262 | | |
263 | | virtual double GetNoDataValue(int *) override; |
264 | | virtual int64_t GetNoDataValueAsInt64(int *pbSuccess = nullptr) override; |
265 | | virtual uint64_t GetNoDataValueAsUInt64(int *pbSuccess = nullptr) override; |
266 | | virtual CPLErr SetNoDataValue(double) override; |
267 | | virtual CPLErr SetNoDataValueAsInt64(int64_t nNoData) override; |
268 | | virtual CPLErr SetNoDataValueAsUInt64(uint64_t nNoData) override; |
269 | | // virtual CPLErr DeleteNoDataValue(); |
270 | | virtual double GetOffset(int *) override; |
271 | | virtual CPLErr SetOffset(double) override; |
272 | | virtual double GetScale(int *) override; |
273 | | virtual CPLErr SetScale(double) override; |
274 | | virtual const char *GetUnitType() override; |
275 | | virtual CPLErr SetUnitType(const char *) override; |
276 | | virtual CPLErr IReadBlock(int, int, void *) override; |
277 | | virtual CPLErr IWriteBlock(int, int, void *) override; |
278 | | |
279 | | char **GetMetadata(const char *pszDomain = "") override; |
280 | | const char *GetMetadataItem(const char *pszName, |
281 | | const char *pszDomain = "") override; |
282 | | |
283 | | virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue, |
284 | | const char *pszDomain = "") override; |
285 | | virtual CPLErr SetMetadata(char **papszMD, |
286 | | const char *pszDomain = "") override; |
287 | | }; |
288 | | |
289 | | /************************************************************************/ |
290 | | /* netCDFRasterBand() */ |
291 | | /************************************************************************/ |
292 | | |
293 | | netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &, |
294 | | netCDFDataset *poNCDFDS, int nGroupId, |
295 | | int nZIdIn, int nZDimIn, int nLevelIn, |
296 | | const int *panBandZLevIn, |
297 | | const int *panBandZPosIn, int nBandIn) |
298 | 0 | : nc_datatype(NC_NAT), cdfid(nGroupId), nZId(nZIdIn), nZDim(nZDimIn), |
299 | 0 | nLevel(nLevelIn), nBandXPos(panBandZPosIn[0]), |
300 | 0 | nBandYPos(nZDim == 1 ? -1 : panBandZPosIn[1]), panBandZPos(nullptr), |
301 | 0 | panBandZLev(nullptr), |
302 | 0 | bSignedData(true), // Default signed, except for Byte. |
303 | 0 | bCheckLongitude(false) |
304 | 0 | { |
305 | 0 | poDS = poNCDFDS; |
306 | 0 | nBand = nBandIn; |
307 | | |
308 | | // Take care of all other dimensions. |
309 | 0 | if (nZDim > 2) |
310 | 0 | { |
311 | 0 | panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int))); |
312 | 0 | panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int))); |
313 | |
|
314 | 0 | for (int i = 0; i < nZDim - 2; i++) |
315 | 0 | { |
316 | 0 | panBandZPos[i] = panBandZPosIn[i + 2]; |
317 | 0 | panBandZLev[i] = panBandZLevIn[i]; |
318 | 0 | } |
319 | 0 | } |
320 | |
|
321 | 0 | nRasterXSize = poDS->GetRasterXSize(); |
322 | 0 | nRasterYSize = poDS->GetRasterYSize(); |
323 | 0 | nBlockXSize = poDS->GetRasterXSize(); |
324 | 0 | nBlockYSize = 1; |
325 | | |
326 | | // Get the type of the "z" variable, our target raster array. |
327 | 0 | if (nc_inq_var(cdfid, nZId, nullptr, &nc_datatype, nullptr, nullptr, |
328 | 0 | nullptr) != NC_NOERR) |
329 | 0 | { |
330 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Error in nc_var_inq() on 'z'."); |
331 | 0 | return; |
332 | 0 | } |
333 | | |
334 | 0 | if (NCDFIsUserDefinedType(cdfid, nc_datatype)) |
335 | 0 | { |
336 | | // First enquire and check that the number of fields is 2 |
337 | 0 | size_t nfields, compoundsize; |
338 | 0 | if (nc_inq_compound(cdfid, nc_datatype, nullptr, &compoundsize, |
339 | 0 | &nfields) != NC_NOERR) |
340 | 0 | { |
341 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
342 | 0 | "Error in nc_inq_compound() on 'z'."); |
343 | 0 | return; |
344 | 0 | } |
345 | | |
346 | 0 | if (nfields != 2) |
347 | 0 | { |
348 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
349 | 0 | "Unsupported data type encountered in nc_inq_compound() " |
350 | 0 | "on 'z'."); |
351 | 0 | return; |
352 | 0 | } |
353 | | |
354 | | // Now check that that two types are the same in the struct. |
355 | 0 | nc_type field_type1, field_type2; |
356 | 0 | int field_dims1, field_dims2; |
357 | 0 | if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr, |
358 | 0 | &field_type1, &field_dims1, |
359 | 0 | nullptr) != NC_NOERR) |
360 | 0 | { |
361 | 0 | CPLError( |
362 | 0 | CE_Failure, CPLE_AppDefined, |
363 | 0 | "Error in querying Field 1 in nc_inq_compound_field() on 'z'."); |
364 | 0 | return; |
365 | 0 | } |
366 | | |
367 | 0 | if (nc_inq_compound_field(cdfid, nc_datatype, 0, nullptr, nullptr, |
368 | 0 | &field_type2, &field_dims2, |
369 | 0 | nullptr) != NC_NOERR) |
370 | 0 | { |
371 | 0 | CPLError( |
372 | 0 | CE_Failure, CPLE_AppDefined, |
373 | 0 | "Error in querying Field 2 in nc_inq_compound_field() on 'z'."); |
374 | 0 | return; |
375 | 0 | } |
376 | | |
377 | 0 | if ((field_type1 != field_type2) || (field_dims1 != field_dims2) || |
378 | 0 | (field_dims1 != 0)) |
379 | 0 | { |
380 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
381 | 0 | "Error in interpreting compound data type on 'z'."); |
382 | 0 | return; |
383 | 0 | } |
384 | | |
385 | 0 | if (field_type1 == NC_SHORT) |
386 | 0 | eDataType = GDT_CInt16; |
387 | 0 | else if (field_type1 == NC_INT) |
388 | 0 | eDataType = GDT_CInt32; |
389 | 0 | else if (field_type1 == NC_FLOAT) |
390 | 0 | eDataType = GDT_CFloat32; |
391 | 0 | else if (field_type1 == NC_DOUBLE) |
392 | 0 | eDataType = GDT_CFloat64; |
393 | 0 | else |
394 | 0 | { |
395 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
396 | 0 | "Unsupported netCDF compound data type encountered."); |
397 | 0 | return; |
398 | 0 | } |
399 | 0 | } |
400 | 0 | else |
401 | 0 | { |
402 | 0 | if (nc_datatype == NC_BYTE) |
403 | 0 | eDataType = GDT_Byte; |
404 | 0 | else if (nc_datatype == NC_CHAR) |
405 | 0 | eDataType = GDT_Byte; |
406 | 0 | else if (nc_datatype == NC_SHORT) |
407 | 0 | eDataType = GDT_Int16; |
408 | 0 | else if (nc_datatype == NC_INT) |
409 | 0 | eDataType = GDT_Int32; |
410 | 0 | else if (nc_datatype == NC_FLOAT) |
411 | 0 | eDataType = GDT_Float32; |
412 | 0 | else if (nc_datatype == NC_DOUBLE) |
413 | 0 | eDataType = GDT_Float64; |
414 | 0 | else if (nc_datatype == NC_UBYTE) |
415 | 0 | eDataType = GDT_Byte; |
416 | 0 | else if (nc_datatype == NC_USHORT) |
417 | 0 | eDataType = GDT_UInt16; |
418 | 0 | else if (nc_datatype == NC_UINT) |
419 | 0 | eDataType = GDT_UInt32; |
420 | 0 | else if (nc_datatype == NC_INT64) |
421 | 0 | eDataType = GDT_Int64; |
422 | 0 | else if (nc_datatype == NC_UINT64) |
423 | 0 | eDataType = GDT_UInt64; |
424 | 0 | else |
425 | 0 | { |
426 | 0 | if (nBand == 1) |
427 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
428 | 0 | "Unsupported netCDF datatype (%d), treat as Float32.", |
429 | 0 | static_cast<int>(nc_datatype)); |
430 | 0 | eDataType = GDT_Float32; |
431 | 0 | nc_datatype = NC_FLOAT; |
432 | 0 | } |
433 | 0 | } |
434 | | |
435 | | // Find and set No Data for this variable. |
436 | 0 | nc_type atttype = NC_NAT; |
437 | 0 | size_t attlen = 0; |
438 | 0 | const char *pszNoValueName = nullptr; |
439 | | |
440 | | // Find attribute name, either _FillValue or missing_value. |
441 | 0 | int status = nc_inq_att(cdfid, nZId, NCDF_FillValue, &atttype, &attlen); |
442 | 0 | if (status == NC_NOERR) |
443 | 0 | { |
444 | 0 | pszNoValueName = NCDF_FillValue; |
445 | 0 | } |
446 | 0 | else |
447 | 0 | { |
448 | 0 | status = nc_inq_att(cdfid, nZId, "missing_value", &atttype, &attlen); |
449 | 0 | if (status == NC_NOERR) |
450 | 0 | { |
451 | 0 | pszNoValueName = "missing_value"; |
452 | 0 | } |
453 | 0 | } |
454 | | |
455 | | // Fetch missing value. |
456 | 0 | double dfNoData = 0.0; |
457 | 0 | bool bGotNoData = false; |
458 | 0 | int64_t nNoDataAsInt64 = 0; |
459 | 0 | bool bGotNoDataAsInt64 = false; |
460 | 0 | uint64_t nNoDataAsUInt64 = 0; |
461 | 0 | bool bGotNoDataAsUInt64 = false; |
462 | 0 | if (status == NC_NOERR) |
463 | 0 | { |
464 | 0 | nc_type nAttrType = NC_NAT; |
465 | 0 | size_t nAttrLen = 0; |
466 | 0 | status = nc_inq_att(cdfid, nZId, pszNoValueName, &nAttrType, &nAttrLen); |
467 | 0 | if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_INT64) |
468 | 0 | { |
469 | 0 | long long v; |
470 | 0 | nc_get_att_longlong(cdfid, nZId, pszNoValueName, &v); |
471 | 0 | bGotNoData = true; |
472 | 0 | bGotNoDataAsInt64 = true; |
473 | 0 | nNoDataAsInt64 = static_cast<int64_t>(v); |
474 | 0 | } |
475 | 0 | else if (status == NC_NOERR && nAttrLen == 1 && nAttrType == NC_UINT64) |
476 | 0 | { |
477 | 0 | unsigned long long v; |
478 | 0 | nc_get_att_ulonglong(cdfid, nZId, pszNoValueName, &v); |
479 | 0 | bGotNoData = true; |
480 | 0 | bGotNoDataAsUInt64 = true; |
481 | 0 | nNoDataAsUInt64 = static_cast<uint64_t>(v); |
482 | 0 | } |
483 | 0 | else if (NCDFGetAttr(cdfid, nZId, pszNoValueName, &dfNoData) == CE_None) |
484 | 0 | { |
485 | 0 | bGotNoData = true; |
486 | 0 | } |
487 | 0 | } |
488 | | |
489 | | // If NoData was not found, use the default value, but for non-Byte types |
490 | | // as it is not recommended: |
491 | | // https://www.unidata.ucar.edu/software/netcdf/docs/attribute_conventions.html |
492 | 0 | nc_type vartype = NC_NAT; |
493 | 0 | if (!bGotNoData) |
494 | 0 | { |
495 | 0 | nc_inq_vartype(cdfid, nZId, &vartype); |
496 | 0 | if (vartype == NC_INT64) |
497 | 0 | { |
498 | 0 | nNoDataAsInt64 = |
499 | 0 | NCDFGetDefaultNoDataValueAsInt64(cdfid, nZId, bGotNoData); |
500 | 0 | bGotNoDataAsInt64 = bGotNoData; |
501 | 0 | } |
502 | 0 | else if (vartype == NC_UINT64) |
503 | 0 | { |
504 | 0 | nNoDataAsUInt64 = |
505 | 0 | NCDFGetDefaultNoDataValueAsUInt64(cdfid, nZId, bGotNoData); |
506 | 0 | bGotNoDataAsUInt64 = bGotNoData; |
507 | 0 | } |
508 | 0 | else if (vartype != NC_CHAR && vartype != NC_BYTE && |
509 | 0 | vartype != NC_UBYTE) |
510 | 0 | { |
511 | 0 | dfNoData = |
512 | 0 | NCDFGetDefaultNoDataValue(cdfid, nZId, vartype, bGotNoData); |
513 | 0 | if (bGotNoData) |
514 | 0 | { |
515 | 0 | CPLDebug("GDAL_netCDF", |
516 | 0 | "did not get nodata value for variable #%d, using " |
517 | 0 | "default %f", |
518 | 0 | nZId, dfNoData); |
519 | 0 | } |
520 | 0 | } |
521 | 0 | } |
522 | |
|
523 | 0 | bool bHasUnderscoreUnsignedAttr = false; |
524 | 0 | bool bUnderscoreUnsignedAttrVal = false; |
525 | 0 | { |
526 | 0 | char *pszTemp = nullptr; |
527 | 0 | if (NCDFGetAttr(cdfid, nZId, "_Unsigned", &pszTemp) == CE_None) |
528 | 0 | { |
529 | 0 | if (EQUAL(pszTemp, "true")) |
530 | 0 | { |
531 | 0 | bHasUnderscoreUnsignedAttr = true; |
532 | 0 | bUnderscoreUnsignedAttrVal = true; |
533 | 0 | } |
534 | 0 | else if (EQUAL(pszTemp, "false")) |
535 | 0 | { |
536 | 0 | bHasUnderscoreUnsignedAttr = true; |
537 | 0 | bUnderscoreUnsignedAttrVal = false; |
538 | 0 | } |
539 | 0 | CPLFree(pszTemp); |
540 | 0 | } |
541 | 0 | } |
542 | | |
543 | | // Look for valid_range or valid_min/valid_max. |
544 | | |
545 | | // First look for valid_range. |
546 | 0 | if (CPLFetchBool(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE", true)) |
547 | 0 | { |
548 | 0 | char *pszValidRange = nullptr; |
549 | 0 | if (NCDFGetAttr(cdfid, nZId, "valid_range", &pszValidRange) == |
550 | 0 | CE_None && |
551 | 0 | pszValidRange[0] == '{' && |
552 | 0 | pszValidRange[strlen(pszValidRange) - 1] == '}') |
553 | 0 | { |
554 | 0 | const std::string osValidRange = |
555 | 0 | std::string(pszValidRange).substr(1, strlen(pszValidRange) - 2); |
556 | 0 | const CPLStringList aosValidRange( |
557 | 0 | CSLTokenizeString2(osValidRange.c_str(), ",", 0)); |
558 | 0 | if (aosValidRange.size() == 2 && |
559 | 0 | CPLGetValueType(aosValidRange[0]) != CPL_VALUE_STRING && |
560 | 0 | CPLGetValueType(aosValidRange[1]) != CPL_VALUE_STRING) |
561 | 0 | { |
562 | 0 | bValidRangeValid = true; |
563 | 0 | adfValidRange[0] = CPLAtof(aosValidRange[0]); |
564 | 0 | adfValidRange[1] = CPLAtof(aosValidRange[1]); |
565 | 0 | } |
566 | 0 | } |
567 | 0 | CPLFree(pszValidRange); |
568 | | |
569 | | // If not found look for valid_min and valid_max. |
570 | 0 | if (!bValidRangeValid) |
571 | 0 | { |
572 | 0 | double dfMin = 0; |
573 | 0 | double dfMax = 0; |
574 | 0 | if (NCDFGetAttr(cdfid, nZId, "valid_min", &dfMin) == CE_None && |
575 | 0 | NCDFGetAttr(cdfid, nZId, "valid_max", &dfMax) == CE_None) |
576 | 0 | { |
577 | 0 | adfValidRange[0] = dfMin; |
578 | 0 | adfValidRange[1] = dfMax; |
579 | 0 | bValidRangeValid = true; |
580 | 0 | } |
581 | 0 | } |
582 | |
|
583 | 0 | if (bValidRangeValid && |
584 | 0 | (adfValidRange[0] < 0 || adfValidRange[1] < 0) && |
585 | 0 | nc_datatype == NC_SHORT && bHasUnderscoreUnsignedAttr && |
586 | 0 | bUnderscoreUnsignedAttrVal) |
587 | 0 | { |
588 | 0 | if (adfValidRange[0] < 0) |
589 | 0 | adfValidRange[0] += 65536; |
590 | 0 | if (adfValidRange[1] < 0) |
591 | 0 | adfValidRange[1] += 65536; |
592 | 0 | if (adfValidRange[0] <= adfValidRange[1]) |
593 | 0 | { |
594 | | // Updating metadata item |
595 | 0 | GDALPamRasterBand::SetMetadataItem( |
596 | 0 | "valid_range", |
597 | 0 | CPLSPrintf("{%d,%d}", static_cast<int>(adfValidRange[0]), |
598 | 0 | static_cast<int>(adfValidRange[1]))); |
599 | 0 | } |
600 | 0 | } |
601 | |
|
602 | 0 | if (bValidRangeValid && adfValidRange[0] > adfValidRange[1]) |
603 | 0 | { |
604 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
605 | 0 | "netCDFDataset::valid_range: min > max:\n" |
606 | 0 | " min: %lf\n max: %lf\n", |
607 | 0 | adfValidRange[0], adfValidRange[1]); |
608 | 0 | bValidRangeValid = false; |
609 | 0 | adfValidRange[0] = 0.0; |
610 | 0 | adfValidRange[1] = 0.0; |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | | // Special For Byte Bands: check for signed/unsigned byte. |
615 | 0 | if (nc_datatype == NC_BYTE) |
616 | 0 | { |
617 | | // netcdf uses signed byte by default, but GDAL uses unsigned by default |
618 | | // This may cause unexpected results, but is needed for back-compat. |
619 | 0 | if (poNCDFDS->bIsGdalFile) |
620 | 0 | bSignedData = false; |
621 | 0 | else |
622 | 0 | bSignedData = true; |
623 | | |
624 | | // For NC4 format NC_BYTE is (normally) signed, NC_UBYTE is unsigned. |
625 | | // But in case a NC3 file was converted automatically and has hints |
626 | | // that it is unsigned, take them into account |
627 | 0 | if (poNCDFDS->eFormat == NCDF_FORMAT_NC4) |
628 | 0 | { |
629 | 0 | bSignedData = true; |
630 | 0 | } |
631 | | |
632 | | // If we got valid_range, test for signed/unsigned range. |
633 | | // https://docs.unidata.ucar.edu/netcdf-c/current/attribute_conventions.html |
634 | 0 | if (bValidRangeValid) |
635 | 0 | { |
636 | | // If we got valid_range={0,255}, treat as unsigned. |
637 | 0 | if (adfValidRange[0] == 0 && adfValidRange[1] == 255) |
638 | 0 | { |
639 | 0 | bSignedData = false; |
640 | | // Reset valid_range. |
641 | 0 | bValidRangeValid = false; |
642 | 0 | } |
643 | | // If we got valid_range={-128,127}, treat as signed. |
644 | 0 | else if (adfValidRange[0] == -128 && adfValidRange[1] == 127) |
645 | 0 | { |
646 | 0 | bSignedData = true; |
647 | | // Reset valid_range. |
648 | 0 | bValidRangeValid = false; |
649 | 0 | } |
650 | 0 | } |
651 | | // Else test for _Unsigned. |
652 | | // https://docs.unidata.ucar.edu/nug/current/best_practices.html |
653 | 0 | else |
654 | 0 | { |
655 | 0 | if (bHasUnderscoreUnsignedAttr) |
656 | 0 | bSignedData = !bUnderscoreUnsignedAttrVal; |
657 | 0 | } |
658 | |
|
659 | 0 | if (bSignedData) |
660 | 0 | { |
661 | 0 | eDataType = GDT_Int8; |
662 | 0 | } |
663 | 0 | else if (dfNoData < 0) |
664 | 0 | { |
665 | | // Fix nodata value as it was stored signed. |
666 | 0 | dfNoData += 256; |
667 | 0 | if (pszNoValueName) |
668 | 0 | { |
669 | | // Updating metadata item |
670 | 0 | GDALPamRasterBand::SetMetadataItem( |
671 | 0 | pszNoValueName, |
672 | 0 | CPLSPrintf("%d", static_cast<int>(dfNoData))); |
673 | 0 | } |
674 | 0 | } |
675 | 0 | } |
676 | 0 | else if (nc_datatype == NC_SHORT) |
677 | 0 | { |
678 | 0 | if (bHasUnderscoreUnsignedAttr) |
679 | 0 | { |
680 | 0 | bSignedData = !bUnderscoreUnsignedAttrVal; |
681 | 0 | if (!bSignedData) |
682 | 0 | eDataType = GDT_UInt16; |
683 | 0 | } |
684 | | |
685 | | // Fix nodata value as it was stored signed. |
686 | 0 | if (!bSignedData && dfNoData < 0) |
687 | 0 | { |
688 | 0 | dfNoData += 65536; |
689 | 0 | if (pszNoValueName) |
690 | 0 | { |
691 | | // Updating metadata item |
692 | 0 | GDALPamRasterBand::SetMetadataItem( |
693 | 0 | pszNoValueName, |
694 | 0 | CPLSPrintf("%d", static_cast<int>(dfNoData))); |
695 | 0 | } |
696 | 0 | } |
697 | 0 | } |
698 | | |
699 | 0 | else if (nc_datatype == NC_UBYTE || nc_datatype == NC_USHORT || |
700 | 0 | nc_datatype == NC_UINT || nc_datatype == NC_UINT64) |
701 | 0 | { |
702 | 0 | bSignedData = false; |
703 | 0 | } |
704 | |
|
705 | 0 | CPLDebug("GDAL_netCDF", "netcdf type=%d gdal type=%d signedByte=%d", |
706 | 0 | nc_datatype, eDataType, static_cast<int>(bSignedData)); |
707 | |
|
708 | 0 | if (bGotNoData) |
709 | 0 | { |
710 | | // Set nodata value. |
711 | 0 | if (bGotNoDataAsInt64) |
712 | 0 | { |
713 | 0 | if (eDataType == GDT_Int64) |
714 | 0 | { |
715 | 0 | SetNoDataValueNoUpdate(nNoDataAsInt64); |
716 | 0 | } |
717 | 0 | else if (eDataType == GDT_UInt64 && nNoDataAsInt64 >= 0) |
718 | 0 | { |
719 | 0 | SetNoDataValueNoUpdate(static_cast<uint64_t>(nNoDataAsInt64)); |
720 | 0 | } |
721 | 0 | else |
722 | 0 | { |
723 | 0 | SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsInt64)); |
724 | 0 | } |
725 | 0 | } |
726 | 0 | else if (bGotNoDataAsUInt64) |
727 | 0 | { |
728 | 0 | if (eDataType == GDT_UInt64) |
729 | 0 | { |
730 | 0 | SetNoDataValueNoUpdate(nNoDataAsUInt64); |
731 | 0 | } |
732 | 0 | else if (eDataType == GDT_Int64 && |
733 | 0 | nNoDataAsUInt64 <= |
734 | 0 | static_cast<uint64_t>( |
735 | 0 | std::numeric_limits<int64_t>::max())) |
736 | 0 | { |
737 | 0 | SetNoDataValueNoUpdate(static_cast<int64_t>(nNoDataAsUInt64)); |
738 | 0 | } |
739 | 0 | else |
740 | 0 | { |
741 | 0 | SetNoDataValueNoUpdate(static_cast<double>(nNoDataAsUInt64)); |
742 | 0 | } |
743 | 0 | } |
744 | 0 | else |
745 | 0 | { |
746 | | #ifdef NCDF_DEBUG |
747 | | CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData); |
748 | | #endif |
749 | 0 | if (eDataType == GDT_Int64 && GDALIsValueExactAs<int64_t>(dfNoData)) |
750 | 0 | { |
751 | 0 | SetNoDataValueNoUpdate(static_cast<int64_t>(dfNoData)); |
752 | 0 | } |
753 | 0 | else if (eDataType == GDT_UInt64 && |
754 | 0 | GDALIsValueExactAs<uint64_t>(dfNoData)) |
755 | 0 | { |
756 | 0 | SetNoDataValueNoUpdate(static_cast<uint64_t>(dfNoData)); |
757 | 0 | } |
758 | 0 | else |
759 | 0 | { |
760 | 0 | SetNoDataValueNoUpdate(dfNoData); |
761 | 0 | } |
762 | 0 | } |
763 | 0 | } |
764 | |
|
765 | 0 | CreateMetadataFromAttributes(); |
766 | | |
767 | | // Attempt to fetch the scale_factor and add_offset attributes for the |
768 | | // variable and set them. If these values are not available, set |
769 | | // offset to 0 and scale to 1. |
770 | 0 | if (nc_inq_attid(cdfid, nZId, CF_ADD_OFFSET, nullptr) == NC_NOERR) |
771 | 0 | { |
772 | 0 | double dfOffset = 0; |
773 | 0 | status = nc_get_att_double(cdfid, nZId, CF_ADD_OFFSET, &dfOffset); |
774 | 0 | CPLDebug("GDAL_netCDF", "got add_offset=%.16g, status=%d", dfOffset, |
775 | 0 | status); |
776 | 0 | SetOffsetNoUpdate(dfOffset); |
777 | 0 | } |
778 | |
|
779 | 0 | bool bHasScale = false; |
780 | 0 | if (nc_inq_attid(cdfid, nZId, CF_SCALE_FACTOR, nullptr) == NC_NOERR) |
781 | 0 | { |
782 | 0 | bHasScale = true; |
783 | 0 | double dfScale = 1; |
784 | 0 | status = nc_get_att_double(cdfid, nZId, CF_SCALE_FACTOR, &dfScale); |
785 | 0 | CPLDebug("GDAL_netCDF", "got scale_factor=%.16g, status=%d", dfScale, |
786 | 0 | status); |
787 | 0 | SetScaleNoUpdate(dfScale); |
788 | 0 | } |
789 | |
|
790 | 0 | if (bValidRangeValid && GDALDataTypeIsInteger(eDataType) && |
791 | 0 | eDataType != GDT_Int64 && eDataType != GDT_UInt64 && |
792 | 0 | (std::fabs(std::round(adfValidRange[0]) - adfValidRange[0]) > 1e-5 || |
793 | 0 | std::fabs(std::round(adfValidRange[1]) - adfValidRange[1]) > 1e-5) && |
794 | 0 | CSLFetchNameValue(poNCDFDS->GetOpenOptions(), "HONOUR_VALID_RANGE") == |
795 | 0 | nullptr) |
796 | 0 | { |
797 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
798 | 0 | "validity range = %f, %f contains floating-point values, " |
799 | 0 | "whereas data type is integer. valid_range is thus likely " |
800 | 0 | "wrong%s. Ignoring it.", |
801 | 0 | adfValidRange[0], adfValidRange[1], |
802 | 0 | bHasScale ? " (likely scaled using scale_factor/add_factor " |
803 | 0 | "whereas it should be using the packed data type)" |
804 | 0 | : ""); |
805 | 0 | bValidRangeValid = false; |
806 | 0 | adfValidRange[0] = 0.0; |
807 | 0 | adfValidRange[1] = 0.0; |
808 | 0 | } |
809 | | |
810 | | // Should we check for longitude values > 360? |
811 | 0 | bCheckLongitude = |
812 | 0 | CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES")) && |
813 | 0 | NCDFIsVarLongitude(cdfid, nZId, nullptr); |
814 | | |
815 | | // Attempt to fetch the units attribute for the variable and set it. |
816 | 0 | SetUnitTypeNoUpdate(netCDFRasterBand::GetMetadataItem(CF_UNITS)); |
817 | |
|
818 | 0 | SetBlockSize(); |
819 | 0 | } |
820 | | |
821 | | void netCDFRasterBand::SetBlockSize() |
822 | 0 | { |
823 | | // Check for variable chunking (netcdf-4 only). |
824 | | // GDAL block size should be set to hdf5 chunk size. |
825 | 0 | int nTmpFormat = 0; |
826 | 0 | int status = nc_inq_format(cdfid, &nTmpFormat); |
827 | 0 | NetCDFFormatEnum eTmpFormat = static_cast<NetCDFFormatEnum>(nTmpFormat); |
828 | 0 | if ((status == NC_NOERR) && |
829 | 0 | (eTmpFormat == NCDF_FORMAT_NC4 || eTmpFormat == NCDF_FORMAT_NC4C)) |
830 | 0 | { |
831 | 0 | size_t chunksize[MAX_NC_DIMS] = {}; |
832 | | // Check for chunksize and set it as the blocksize (optimizes read). |
833 | 0 | status = nc_inq_var_chunking(cdfid, nZId, &nTmpFormat, chunksize); |
834 | 0 | if ((status == NC_NOERR) && (nTmpFormat == NC_CHUNKED)) |
835 | 0 | { |
836 | 0 | nBlockXSize = (int)chunksize[nZDim - 1]; |
837 | 0 | if (nZDim >= 2) |
838 | 0 | nBlockYSize = (int)chunksize[nZDim - 2]; |
839 | 0 | else |
840 | 0 | nBlockYSize = 1; |
841 | 0 | } |
842 | 0 | } |
843 | | |
844 | | // Deal with bottom-up datasets and nBlockYSize != 1. |
845 | 0 | auto poGDS = static_cast<netCDFDataset *>(poDS); |
846 | 0 | if (poGDS->bBottomUp && nBlockYSize != 1 && poGDS->poChunkCache == nullptr) |
847 | 0 | { |
848 | 0 | if (poGDS->eAccess == GA_ReadOnly) |
849 | 0 | { |
850 | | // Try to cache 1 or 2 'rows' of netCDF chunks along the whole |
851 | | // width of the raster |
852 | 0 | size_t nChunks = |
853 | 0 | static_cast<size_t>(DIV_ROUND_UP(nRasterXSize, nBlockXSize)); |
854 | 0 | if ((nRasterYSize % nBlockYSize) != 0) |
855 | 0 | nChunks *= 2; |
856 | 0 | const size_t nChunkSize = |
857 | 0 | static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) * |
858 | 0 | nBlockXSize * nBlockYSize; |
859 | 0 | constexpr size_t MAX_CACHE_SIZE = 100 * 1024 * 1024; |
860 | 0 | nChunks = std::min(nChunks, MAX_CACHE_SIZE / nChunkSize); |
861 | 0 | if (nChunks) |
862 | 0 | { |
863 | 0 | poGDS->poChunkCache.reset( |
864 | 0 | new netCDFDataset::ChunkCacheType(nChunks)); |
865 | 0 | } |
866 | 0 | } |
867 | 0 | else |
868 | 0 | { |
869 | 0 | nBlockYSize = 1; |
870 | 0 | } |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | | // Constructor in create mode. |
875 | | // If nZId and following variables are not passed, the band will have 2 |
876 | | // dimensions. |
877 | | // TODO: Get metadata, missing val from band #1 if nZDim > 2. |
878 | | netCDFRasterBand::netCDFRasterBand( |
879 | | const netCDFRasterBand::CONSTRUCTOR_CREATE &, netCDFDataset *poNCDFDS, |
880 | | const GDALDataType eTypeIn, int nBandIn, bool bSigned, |
881 | | const char *pszBandName, const char *pszLongName, int nZIdIn, int nZDimIn, |
882 | | int nLevelIn, const int *panBandZLevIn, const int *panBandZPosIn, |
883 | | const int *paDimIds) |
884 | 0 | : nc_datatype(NC_NAT), cdfid(poNCDFDS->GetCDFID()), nZId(nZIdIn), |
885 | 0 | nZDim(nZDimIn), nLevel(nLevelIn), nBandXPos(1), nBandYPos(0), |
886 | 0 | panBandZPos(nullptr), panBandZLev(nullptr), bSignedData(bSigned), |
887 | 0 | bCheckLongitude(false), m_bCreateMetadataFromOtherVarsDone(true) |
888 | 0 | { |
889 | 0 | poDS = poNCDFDS; |
890 | 0 | nBand = nBandIn; |
891 | |
|
892 | 0 | nRasterXSize = poDS->GetRasterXSize(); |
893 | 0 | nRasterYSize = poDS->GetRasterYSize(); |
894 | 0 | nBlockXSize = poDS->GetRasterXSize(); |
895 | 0 | nBlockYSize = 1; |
896 | |
|
897 | 0 | if (poDS->GetAccess() != GA_Update) |
898 | 0 | { |
899 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
900 | 0 | "Dataset is not in update mode, " |
901 | 0 | "wrong netCDFRasterBand constructor"); |
902 | 0 | return; |
903 | 0 | } |
904 | | |
905 | | // Take care of all other dimensions. |
906 | 0 | if (nZDim > 2 && paDimIds != nullptr) |
907 | 0 | { |
908 | 0 | nBandXPos = panBandZPosIn[0]; |
909 | 0 | nBandYPos = panBandZPosIn[1]; |
910 | |
|
911 | 0 | panBandZPos = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int))); |
912 | 0 | panBandZLev = static_cast<int *>(CPLCalloc(nZDim - 1, sizeof(int))); |
913 | |
|
914 | 0 | for (int i = 0; i < nZDim - 2; i++) |
915 | 0 | { |
916 | 0 | panBandZPos[i] = panBandZPosIn[i + 2]; |
917 | 0 | panBandZLev[i] = panBandZLevIn[i]; |
918 | 0 | } |
919 | 0 | } |
920 | | |
921 | | // Get the type of the "z" variable, our target raster array. |
922 | 0 | eDataType = eTypeIn; |
923 | |
|
924 | 0 | switch (eDataType) |
925 | 0 | { |
926 | 0 | case GDT_Byte: |
927 | 0 | nc_datatype = NC_BYTE; |
928 | | // NC_UBYTE (unsigned byte) is only available for NC4. |
929 | 0 | if (poNCDFDS->eFormat == NCDF_FORMAT_NC4) |
930 | 0 | nc_datatype = NC_UBYTE; |
931 | 0 | break; |
932 | 0 | case GDT_Int8: |
933 | 0 | nc_datatype = NC_BYTE; |
934 | 0 | break; |
935 | 0 | case GDT_Int16: |
936 | 0 | nc_datatype = NC_SHORT; |
937 | 0 | break; |
938 | 0 | case GDT_Int32: |
939 | 0 | nc_datatype = NC_INT; |
940 | 0 | break; |
941 | 0 | case GDT_Float32: |
942 | 0 | nc_datatype = NC_FLOAT; |
943 | 0 | break; |
944 | 0 | case GDT_Float64: |
945 | 0 | nc_datatype = NC_DOUBLE; |
946 | 0 | break; |
947 | 0 | case GDT_Int64: |
948 | 0 | if (poNCDFDS->eFormat == NCDF_FORMAT_NC4) |
949 | 0 | { |
950 | 0 | nc_datatype = NC_INT64; |
951 | 0 | } |
952 | 0 | else |
953 | 0 | { |
954 | 0 | if (nBand == 1) |
955 | 0 | CPLError( |
956 | 0 | CE_Warning, CPLE_AppDefined, |
957 | 0 | "Unsupported GDAL datatype %s, treat as NC_DOUBLE.", |
958 | 0 | "Int64"); |
959 | 0 | nc_datatype = NC_DOUBLE; |
960 | 0 | eDataType = GDT_Float64; |
961 | 0 | } |
962 | 0 | break; |
963 | 0 | case GDT_UInt64: |
964 | 0 | if (poNCDFDS->eFormat == NCDF_FORMAT_NC4) |
965 | 0 | { |
966 | 0 | nc_datatype = NC_UINT64; |
967 | 0 | } |
968 | 0 | else |
969 | 0 | { |
970 | 0 | if (nBand == 1) |
971 | 0 | CPLError( |
972 | 0 | CE_Warning, CPLE_AppDefined, |
973 | 0 | "Unsupported GDAL datatype %s, treat as NC_DOUBLE.", |
974 | 0 | "UInt64"); |
975 | 0 | nc_datatype = NC_DOUBLE; |
976 | 0 | eDataType = GDT_Float64; |
977 | 0 | } |
978 | 0 | break; |
979 | 0 | case GDT_UInt16: |
980 | 0 | if (poNCDFDS->eFormat == NCDF_FORMAT_NC4) |
981 | 0 | { |
982 | 0 | nc_datatype = NC_USHORT; |
983 | 0 | break; |
984 | 0 | } |
985 | 0 | [[fallthrough]]; |
986 | 0 | case GDT_UInt32: |
987 | 0 | if (poNCDFDS->eFormat == NCDF_FORMAT_NC4) |
988 | 0 | { |
989 | 0 | nc_datatype = NC_UINT; |
990 | 0 | break; |
991 | 0 | } |
992 | 0 | [[fallthrough]]; |
993 | 0 | default: |
994 | 0 | if (nBand == 1) |
995 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
996 | 0 | "Unsupported GDAL datatype (%d), treat as NC_FLOAT.", |
997 | 0 | static_cast<int>(eDataType)); |
998 | 0 | nc_datatype = NC_FLOAT; |
999 | 0 | eDataType = GDT_Float32; |
1000 | 0 | break; |
1001 | 0 | } |
1002 | | |
1003 | | // Define the variable if necessary (if nZId == -1). |
1004 | 0 | bool bDefineVar = false; |
1005 | |
|
1006 | 0 | if (nZId == -1) |
1007 | 0 | { |
1008 | 0 | bDefineVar = true; |
1009 | | |
1010 | | // Make sure we are in define mode. |
1011 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1012 | |
|
1013 | 0 | char szTempPrivate[256 + 1]; |
1014 | 0 | const char *pszTemp = nullptr; |
1015 | 0 | if (!pszBandName || EQUAL(pszBandName, "")) |
1016 | 0 | { |
1017 | 0 | snprintf(szTempPrivate, sizeof(szTempPrivate), "Band%d", nBand); |
1018 | 0 | pszTemp = szTempPrivate; |
1019 | 0 | } |
1020 | 0 | else |
1021 | 0 | { |
1022 | 0 | pszTemp = pszBandName; |
1023 | 0 | } |
1024 | |
|
1025 | 0 | int status; |
1026 | 0 | if (nZDim > 2 && paDimIds != nullptr) |
1027 | 0 | { |
1028 | 0 | status = |
1029 | 0 | nc_def_var(cdfid, pszTemp, nc_datatype, nZDim, paDimIds, &nZId); |
1030 | 0 | } |
1031 | 0 | else |
1032 | 0 | { |
1033 | 0 | int anBandDims[2] = {poNCDFDS->nYDimID, poNCDFDS->nXDimID}; |
1034 | 0 | status = |
1035 | 0 | nc_def_var(cdfid, pszTemp, nc_datatype, 2, anBandDims, &nZId); |
1036 | 0 | } |
1037 | 0 | NCDF_ERR(status); |
1038 | 0 | CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d) id=%d", cdfid, pszTemp, |
1039 | 0 | nc_datatype, nZId); |
1040 | |
|
1041 | 0 | if (!pszLongName || EQUAL(pszLongName, "")) |
1042 | 0 | { |
1043 | 0 | snprintf(szTempPrivate, sizeof(szTempPrivate), |
1044 | 0 | "GDAL Band Number %d", nBand); |
1045 | 0 | pszTemp = szTempPrivate; |
1046 | 0 | } |
1047 | 0 | else |
1048 | 0 | { |
1049 | 0 | pszTemp = pszLongName; |
1050 | 0 | } |
1051 | 0 | status = |
1052 | 0 | nc_put_att_text(cdfid, nZId, CF_LNG_NAME, strlen(pszTemp), pszTemp); |
1053 | 0 | NCDF_ERR(status); |
1054 | |
|
1055 | 0 | poNCDFDS->DefVarDeflate(nZId, true); |
1056 | 0 | } |
1057 | | |
1058 | | // For Byte data add signed/unsigned info. |
1059 | 0 | if (eDataType == GDT_Byte || eDataType == GDT_Int8) |
1060 | 0 | { |
1061 | 0 | if (bDefineVar) |
1062 | 0 | { |
1063 | | // Only add attributes if creating variable. |
1064 | | // For unsigned NC_BYTE (except NC4 format), |
1065 | | // add valid_range and _Unsigned ( defined in CF-1 and NUG ). |
1066 | 0 | if (nc_datatype == NC_BYTE && poNCDFDS->eFormat != NCDF_FORMAT_NC4) |
1067 | 0 | { |
1068 | 0 | CPLDebug("GDAL_netCDF", |
1069 | 0 | "adding valid_range attributes for Byte Band"); |
1070 | 0 | short l_adfValidRange[2] = {0, 0}; |
1071 | 0 | int status; |
1072 | 0 | if (bSignedData || eDataType == GDT_Int8) |
1073 | 0 | { |
1074 | 0 | l_adfValidRange[0] = -128; |
1075 | 0 | l_adfValidRange[1] = 127; |
1076 | 0 | status = |
1077 | 0 | nc_put_att_text(cdfid, nZId, "_Unsigned", 5, "false"); |
1078 | 0 | } |
1079 | 0 | else |
1080 | 0 | { |
1081 | 0 | l_adfValidRange[0] = 0; |
1082 | 0 | l_adfValidRange[1] = 255; |
1083 | 0 | status = |
1084 | 0 | nc_put_att_text(cdfid, nZId, "_Unsigned", 4, "true"); |
1085 | 0 | } |
1086 | 0 | NCDF_ERR(status); |
1087 | 0 | status = nc_put_att_short(cdfid, nZId, "valid_range", NC_SHORT, |
1088 | 0 | 2, l_adfValidRange); |
1089 | 0 | NCDF_ERR(status); |
1090 | 0 | } |
1091 | 0 | } |
1092 | 0 | } |
1093 | |
|
1094 | 0 | if (nc_datatype != NC_BYTE && nc_datatype != NC_CHAR && |
1095 | 0 | nc_datatype != NC_UBYTE) |
1096 | 0 | { |
1097 | | // Set default nodata. |
1098 | 0 | bool bIgnored = false; |
1099 | 0 | double dfNoData = |
1100 | 0 | NCDFGetDefaultNoDataValue(cdfid, nZId, nc_datatype, bIgnored); |
1101 | | #ifdef NCDF_DEBUG |
1102 | | CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) default", dfNoData); |
1103 | | #endif |
1104 | 0 | netCDFRasterBand::SetNoDataValue(dfNoData); |
1105 | 0 | } |
1106 | |
|
1107 | 0 | SetBlockSize(); |
1108 | 0 | } |
1109 | | |
1110 | | /************************************************************************/ |
1111 | | /* ~netCDFRasterBand() */ |
1112 | | /************************************************************************/ |
1113 | | |
1114 | | netCDFRasterBand::~netCDFRasterBand() |
1115 | 0 | { |
1116 | 0 | netCDFRasterBand::FlushCache(true); |
1117 | 0 | CPLFree(panBandZPos); |
1118 | 0 | CPLFree(panBandZLev); |
1119 | 0 | } |
1120 | | |
1121 | | /************************************************************************/ |
1122 | | /* GetMetadata() */ |
1123 | | /************************************************************************/ |
1124 | | |
1125 | | char **netCDFRasterBand::GetMetadata(const char *pszDomain) |
1126 | 0 | { |
1127 | 0 | if (!m_bCreateMetadataFromOtherVarsDone) |
1128 | 0 | CreateMetadataFromOtherVars(); |
1129 | 0 | return GDALPamRasterBand::GetMetadata(pszDomain); |
1130 | 0 | } |
1131 | | |
1132 | | /************************************************************************/ |
1133 | | /* GetMetadataItem() */ |
1134 | | /************************************************************************/ |
1135 | | |
1136 | | const char *netCDFRasterBand::GetMetadataItem(const char *pszName, |
1137 | | const char *pszDomain) |
1138 | 0 | { |
1139 | 0 | if (!m_bCreateMetadataFromOtherVarsDone && |
1140 | 0 | STARTS_WITH(pszName, "NETCDF_DIM_") && |
1141 | 0 | (!pszDomain || pszDomain[0] == 0)) |
1142 | 0 | CreateMetadataFromOtherVars(); |
1143 | 0 | return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain); |
1144 | 0 | } |
1145 | | |
1146 | | /************************************************************************/ |
1147 | | /* SetMetadataItem() */ |
1148 | | /************************************************************************/ |
1149 | | |
1150 | | CPLErr netCDFRasterBand::SetMetadataItem(const char *pszName, |
1151 | | const char *pszValue, |
1152 | | const char *pszDomain) |
1153 | 0 | { |
1154 | 0 | if (GetAccess() == GA_Update && |
1155 | 0 | (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr) |
1156 | 0 | { |
1157 | | // Same logic as in CopyMetadata() |
1158 | |
|
1159 | 0 | const char *const papszIgnoreBand[] = { |
1160 | 0 | CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned", |
1161 | 0 | NCDF_FillValue, "coordinates", nullptr}; |
1162 | | // Do not copy varname, stats, NETCDF_DIM_*, nodata |
1163 | | // and items in papszIgnoreBand. |
1164 | 0 | if (STARTS_WITH(pszName, "NETCDF_VARNAME") || |
1165 | 0 | STARTS_WITH(pszName, "STATISTICS_") || |
1166 | 0 | STARTS_WITH(pszName, "NETCDF_DIM_") || |
1167 | 0 | STARTS_WITH(pszName, "missing_value") || |
1168 | 0 | STARTS_WITH(pszName, "_FillValue") || |
1169 | 0 | CSLFindString(papszIgnoreBand, pszName) != -1) |
1170 | 0 | { |
1171 | | // do nothing |
1172 | 0 | } |
1173 | 0 | else |
1174 | 0 | { |
1175 | 0 | cpl::down_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1176 | |
|
1177 | 0 | if (!NCDFPutAttr(cdfid, nZId, pszName, pszValue)) |
1178 | 0 | return CE_Failure; |
1179 | 0 | } |
1180 | 0 | } |
1181 | | |
1182 | 0 | return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain); |
1183 | 0 | } |
1184 | | |
1185 | | /************************************************************************/ |
1186 | | /* SetMetadata() */ |
1187 | | /************************************************************************/ |
1188 | | |
1189 | | CPLErr netCDFRasterBand::SetMetadata(char **papszMD, const char *pszDomain) |
1190 | 0 | { |
1191 | 0 | if (GetAccess() == GA_Update && |
1192 | 0 | (pszDomain == nullptr || pszDomain[0] == '\0')) |
1193 | 0 | { |
1194 | | // We don't handle metadata item removal for now |
1195 | 0 | for (const char *const *papszIter = papszMD; papszIter && *papszIter; |
1196 | 0 | ++papszIter) |
1197 | 0 | { |
1198 | 0 | char *pszName = nullptr; |
1199 | 0 | const char *pszValue = CPLParseNameValue(*papszIter, &pszName); |
1200 | 0 | if (pszName && pszValue) |
1201 | 0 | SetMetadataItem(pszName, pszValue); |
1202 | 0 | CPLFree(pszName); |
1203 | 0 | } |
1204 | 0 | } |
1205 | 0 | return GDALPamRasterBand::SetMetadata(papszMD, pszDomain); |
1206 | 0 | } |
1207 | | |
1208 | | /************************************************************************/ |
1209 | | /* GetOffset() */ |
1210 | | /************************************************************************/ |
1211 | | double netCDFRasterBand::GetOffset(int *pbSuccess) |
1212 | 0 | { |
1213 | 0 | if (pbSuccess != nullptr) |
1214 | 0 | *pbSuccess = static_cast<int>(m_bHaveOffset); |
1215 | |
|
1216 | 0 | return m_dfOffset; |
1217 | 0 | } |
1218 | | |
1219 | | /************************************************************************/ |
1220 | | /* SetOffset() */ |
1221 | | /************************************************************************/ |
1222 | | CPLErr netCDFRasterBand::SetOffset(double dfNewOffset) |
1223 | 0 | { |
1224 | 0 | CPLMutexHolderD(&hNCMutex); |
1225 | | |
1226 | | // Write value if in update mode. |
1227 | 0 | if (poDS->GetAccess() == GA_Update) |
1228 | 0 | { |
1229 | | // Make sure we are in define mode. |
1230 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1231 | |
|
1232 | 0 | const int status = nc_put_att_double(cdfid, nZId, CF_ADD_OFFSET, |
1233 | 0 | NC_DOUBLE, 1, &dfNewOffset); |
1234 | |
|
1235 | 0 | NCDF_ERR(status); |
1236 | 0 | if (status == NC_NOERR) |
1237 | 0 | { |
1238 | 0 | SetOffsetNoUpdate(dfNewOffset); |
1239 | 0 | return CE_None; |
1240 | 0 | } |
1241 | | |
1242 | 0 | return CE_Failure; |
1243 | 0 | } |
1244 | | |
1245 | 0 | SetOffsetNoUpdate(dfNewOffset); |
1246 | 0 | return CE_None; |
1247 | 0 | } |
1248 | | |
1249 | | /************************************************************************/ |
1250 | | /* SetOffsetNoUpdate() */ |
1251 | | /************************************************************************/ |
1252 | | void netCDFRasterBand::SetOffsetNoUpdate(double dfVal) |
1253 | 0 | { |
1254 | 0 | m_dfOffset = dfVal; |
1255 | 0 | m_bHaveOffset = true; |
1256 | 0 | } |
1257 | | |
1258 | | /************************************************************************/ |
1259 | | /* GetScale() */ |
1260 | | /************************************************************************/ |
1261 | | double netCDFRasterBand::GetScale(int *pbSuccess) |
1262 | 0 | { |
1263 | 0 | if (pbSuccess != nullptr) |
1264 | 0 | *pbSuccess = static_cast<int>(m_bHaveScale); |
1265 | |
|
1266 | 0 | return m_dfScale; |
1267 | 0 | } |
1268 | | |
1269 | | /************************************************************************/ |
1270 | | /* SetScale() */ |
1271 | | /************************************************************************/ |
1272 | | CPLErr netCDFRasterBand::SetScale(double dfNewScale) |
1273 | 0 | { |
1274 | 0 | CPLMutexHolderD(&hNCMutex); |
1275 | | |
1276 | | // Write value if in update mode. |
1277 | 0 | if (poDS->GetAccess() == GA_Update) |
1278 | 0 | { |
1279 | | // Make sure we are in define mode. |
1280 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1281 | |
|
1282 | 0 | const int status = nc_put_att_double(cdfid, nZId, CF_SCALE_FACTOR, |
1283 | 0 | NC_DOUBLE, 1, &dfNewScale); |
1284 | |
|
1285 | 0 | NCDF_ERR(status); |
1286 | 0 | if (status == NC_NOERR) |
1287 | 0 | { |
1288 | 0 | SetScaleNoUpdate(dfNewScale); |
1289 | 0 | return CE_None; |
1290 | 0 | } |
1291 | | |
1292 | 0 | return CE_Failure; |
1293 | 0 | } |
1294 | | |
1295 | 0 | SetScaleNoUpdate(dfNewScale); |
1296 | 0 | return CE_None; |
1297 | 0 | } |
1298 | | |
1299 | | /************************************************************************/ |
1300 | | /* SetScaleNoUpdate() */ |
1301 | | /************************************************************************/ |
1302 | | void netCDFRasterBand::SetScaleNoUpdate(double dfVal) |
1303 | 0 | { |
1304 | 0 | m_dfScale = dfVal; |
1305 | 0 | m_bHaveScale = true; |
1306 | 0 | } |
1307 | | |
1308 | | /************************************************************************/ |
1309 | | /* GetUnitType() */ |
1310 | | /************************************************************************/ |
1311 | | |
1312 | | const char *netCDFRasterBand::GetUnitType() |
1313 | | |
1314 | 0 | { |
1315 | 0 | if (!m_osUnitType.empty()) |
1316 | 0 | return m_osUnitType; |
1317 | | |
1318 | 0 | return GDALRasterBand::GetUnitType(); |
1319 | 0 | } |
1320 | | |
1321 | | /************************************************************************/ |
1322 | | /* SetUnitType() */ |
1323 | | /************************************************************************/ |
1324 | | |
1325 | | CPLErr netCDFRasterBand::SetUnitType(const char *pszNewValue) |
1326 | | |
1327 | 0 | { |
1328 | 0 | CPLMutexHolderD(&hNCMutex); |
1329 | |
|
1330 | 0 | const std::string osUnitType = (pszNewValue != nullptr ? pszNewValue : ""); |
1331 | |
|
1332 | 0 | if (!osUnitType.empty()) |
1333 | 0 | { |
1334 | | // Write value if in update mode. |
1335 | 0 | if (poDS->GetAccess() == GA_Update) |
1336 | 0 | { |
1337 | | // Make sure we are in define mode. |
1338 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(TRUE); |
1339 | |
|
1340 | 0 | const int status = nc_put_att_text( |
1341 | 0 | cdfid, nZId, CF_UNITS, osUnitType.size(), osUnitType.c_str()); |
1342 | |
|
1343 | 0 | NCDF_ERR(status); |
1344 | 0 | if (status == NC_NOERR) |
1345 | 0 | { |
1346 | 0 | SetUnitTypeNoUpdate(pszNewValue); |
1347 | 0 | return CE_None; |
1348 | 0 | } |
1349 | | |
1350 | 0 | return CE_Failure; |
1351 | 0 | } |
1352 | 0 | } |
1353 | | |
1354 | 0 | SetUnitTypeNoUpdate(pszNewValue); |
1355 | |
|
1356 | 0 | return CE_None; |
1357 | 0 | } |
1358 | | |
1359 | | /************************************************************************/ |
1360 | | /* SetUnitTypeNoUpdate() */ |
1361 | | /************************************************************************/ |
1362 | | |
1363 | | void netCDFRasterBand::SetUnitTypeNoUpdate(const char *pszNewValue) |
1364 | 0 | { |
1365 | 0 | m_osUnitType = (pszNewValue != nullptr ? pszNewValue : ""); |
1366 | 0 | } |
1367 | | |
1368 | | /************************************************************************/ |
1369 | | /* GetNoDataValue() */ |
1370 | | /************************************************************************/ |
1371 | | |
1372 | | double netCDFRasterBand::GetNoDataValue(int *pbSuccess) |
1373 | | |
1374 | 0 | { |
1375 | 0 | if (m_bNoDataSetAsInt64) |
1376 | 0 | { |
1377 | 0 | if (pbSuccess) |
1378 | 0 | *pbSuccess = TRUE; |
1379 | 0 | return GDALGetNoDataValueCastToDouble(m_nNodataValueInt64); |
1380 | 0 | } |
1381 | | |
1382 | 0 | if (m_bNoDataSetAsUInt64) |
1383 | 0 | { |
1384 | 0 | if (pbSuccess) |
1385 | 0 | *pbSuccess = TRUE; |
1386 | 0 | return GDALGetNoDataValueCastToDouble(m_nNodataValueUInt64); |
1387 | 0 | } |
1388 | | |
1389 | 0 | if (m_bNoDataSet) |
1390 | 0 | { |
1391 | 0 | if (pbSuccess) |
1392 | 0 | *pbSuccess = TRUE; |
1393 | 0 | return m_dfNoDataValue; |
1394 | 0 | } |
1395 | | |
1396 | 0 | return GDALPamRasterBand::GetNoDataValue(pbSuccess); |
1397 | 0 | } |
1398 | | |
1399 | | /************************************************************************/ |
1400 | | /* GetNoDataValueAsInt64() */ |
1401 | | /************************************************************************/ |
1402 | | |
1403 | | int64_t netCDFRasterBand::GetNoDataValueAsInt64(int *pbSuccess) |
1404 | | |
1405 | 0 | { |
1406 | 0 | if (m_bNoDataSetAsInt64) |
1407 | 0 | { |
1408 | 0 | if (pbSuccess) |
1409 | 0 | *pbSuccess = TRUE; |
1410 | |
|
1411 | 0 | return m_nNodataValueInt64; |
1412 | 0 | } |
1413 | | |
1414 | 0 | return GDALPamRasterBand::GetNoDataValueAsInt64(pbSuccess); |
1415 | 0 | } |
1416 | | |
1417 | | /************************************************************************/ |
1418 | | /* GetNoDataValueAsUInt64() */ |
1419 | | /************************************************************************/ |
1420 | | |
1421 | | uint64_t netCDFRasterBand::GetNoDataValueAsUInt64(int *pbSuccess) |
1422 | | |
1423 | 0 | { |
1424 | 0 | if (m_bNoDataSetAsUInt64) |
1425 | 0 | { |
1426 | 0 | if (pbSuccess) |
1427 | 0 | *pbSuccess = TRUE; |
1428 | |
|
1429 | 0 | return m_nNodataValueUInt64; |
1430 | 0 | } |
1431 | | |
1432 | 0 | return GDALPamRasterBand::GetNoDataValueAsUInt64(pbSuccess); |
1433 | 0 | } |
1434 | | |
1435 | | /************************************************************************/ |
1436 | | /* SetNoDataValue() */ |
1437 | | /************************************************************************/ |
1438 | | |
1439 | | CPLErr netCDFRasterBand::SetNoDataValue(double dfNoData) |
1440 | | |
1441 | 0 | { |
1442 | 0 | CPLMutexHolderD(&hNCMutex); |
1443 | | |
1444 | | // If already set to new value, don't do anything. |
1445 | 0 | if (m_bNoDataSet && CPLIsEqual(dfNoData, m_dfNoDataValue)) |
1446 | 0 | return CE_None; |
1447 | | |
1448 | | // Write value if in update mode. |
1449 | 0 | if (poDS->GetAccess() == GA_Update) |
1450 | 0 | { |
1451 | | // netcdf-4 does not allow to set _FillValue after leaving define mode, |
1452 | | // but it is ok if variable has not been written to, so only print |
1453 | | // debug. See bug #4484. |
1454 | 0 | if (m_bNoDataSet && |
1455 | 0 | !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode()) |
1456 | 0 | { |
1457 | 0 | CPLDebug("GDAL_netCDF", |
1458 | 0 | "Setting NoDataValue to %.17g (previously set to %.17g) " |
1459 | 0 | "but file is no longer in define mode (id #%d, band #%d)", |
1460 | 0 | dfNoData, m_dfNoDataValue, cdfid, nBand); |
1461 | 0 | } |
1462 | | #ifdef NCDF_DEBUG |
1463 | | else |
1464 | | { |
1465 | | CPLDebug("GDAL_netCDF", |
1466 | | "Setting NoDataValue to %.17g (id #%d, band #%d)", |
1467 | | dfNoData, cdfid, nBand); |
1468 | | } |
1469 | | #endif |
1470 | | // Make sure we are in define mode. |
1471 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1472 | |
|
1473 | 0 | int status; |
1474 | 0 | if (eDataType == GDT_Byte) |
1475 | 0 | { |
1476 | 0 | if (bSignedData) |
1477 | 0 | { |
1478 | 0 | signed char cNoDataValue = static_cast<signed char>(dfNoData); |
1479 | 0 | status = nc_put_att_schar(cdfid, nZId, NCDF_FillValue, |
1480 | 0 | nc_datatype, 1, &cNoDataValue); |
1481 | 0 | } |
1482 | 0 | else |
1483 | 0 | { |
1484 | 0 | const unsigned char ucNoDataValue = |
1485 | 0 | static_cast<unsigned char>(dfNoData); |
1486 | 0 | status = nc_put_att_uchar(cdfid, nZId, NCDF_FillValue, |
1487 | 0 | nc_datatype, 1, &ucNoDataValue); |
1488 | 0 | } |
1489 | 0 | } |
1490 | 0 | else if (eDataType == GDT_Int16) |
1491 | 0 | { |
1492 | 0 | short nsNoDataValue = static_cast<short>(dfNoData); |
1493 | 0 | status = nc_put_att_short(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1494 | 0 | 1, &nsNoDataValue); |
1495 | 0 | } |
1496 | 0 | else if (eDataType == GDT_Int32) |
1497 | 0 | { |
1498 | 0 | int nNoDataValue = static_cast<int>(dfNoData); |
1499 | 0 | status = nc_put_att_int(cdfid, nZId, NCDF_FillValue, nc_datatype, 1, |
1500 | 0 | &nNoDataValue); |
1501 | 0 | } |
1502 | 0 | else if (eDataType == GDT_Float32) |
1503 | 0 | { |
1504 | 0 | float fNoDataValue = static_cast<float>(dfNoData); |
1505 | 0 | status = nc_put_att_float(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1506 | 0 | 1, &fNoDataValue); |
1507 | 0 | } |
1508 | 0 | else if (eDataType == GDT_UInt16 && |
1509 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->eFormat == |
1510 | 0 | NCDF_FORMAT_NC4) |
1511 | 0 | { |
1512 | 0 | unsigned short usNoDataValue = |
1513 | 0 | static_cast<unsigned short>(dfNoData); |
1514 | 0 | status = nc_put_att_ushort(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1515 | 0 | 1, &usNoDataValue); |
1516 | 0 | } |
1517 | 0 | else if (eDataType == GDT_UInt32 && |
1518 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->eFormat == |
1519 | 0 | NCDF_FORMAT_NC4) |
1520 | 0 | { |
1521 | 0 | unsigned int unNoDataValue = static_cast<unsigned int>(dfNoData); |
1522 | 0 | status = nc_put_att_uint(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1523 | 0 | 1, &unNoDataValue); |
1524 | 0 | } |
1525 | 0 | else |
1526 | 0 | { |
1527 | 0 | status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1528 | 0 | 1, &dfNoData); |
1529 | 0 | } |
1530 | |
|
1531 | 0 | NCDF_ERR(status); |
1532 | | |
1533 | | // Update status if write worked. |
1534 | 0 | if (status == NC_NOERR) |
1535 | 0 | { |
1536 | 0 | SetNoDataValueNoUpdate(dfNoData); |
1537 | 0 | return CE_None; |
1538 | 0 | } |
1539 | | |
1540 | 0 | return CE_Failure; |
1541 | 0 | } |
1542 | | |
1543 | 0 | SetNoDataValueNoUpdate(dfNoData); |
1544 | 0 | return CE_None; |
1545 | 0 | } |
1546 | | |
1547 | | /************************************************************************/ |
1548 | | /* SetNoDataValueNoUpdate() */ |
1549 | | /************************************************************************/ |
1550 | | |
1551 | | void netCDFRasterBand::SetNoDataValueNoUpdate(double dfNoData) |
1552 | 0 | { |
1553 | 0 | m_dfNoDataValue = dfNoData; |
1554 | 0 | m_bNoDataSet = true; |
1555 | 0 | m_bNoDataSetAsInt64 = false; |
1556 | 0 | m_bNoDataSetAsUInt64 = false; |
1557 | 0 | } |
1558 | | |
1559 | | /************************************************************************/ |
1560 | | /* SetNoDataValueAsInt64() */ |
1561 | | /************************************************************************/ |
1562 | | |
1563 | | CPLErr netCDFRasterBand::SetNoDataValueAsInt64(int64_t nNoData) |
1564 | | |
1565 | 0 | { |
1566 | 0 | CPLMutexHolderD(&hNCMutex); |
1567 | | |
1568 | | // If already set to new value, don't do anything. |
1569 | 0 | if (m_bNoDataSetAsInt64 && nNoData == m_nNodataValueInt64) |
1570 | 0 | return CE_None; |
1571 | | |
1572 | | // Write value if in update mode. |
1573 | 0 | if (poDS->GetAccess() == GA_Update) |
1574 | 0 | { |
1575 | | // netcdf-4 does not allow to set NCDF_FillValue after leaving define mode, |
1576 | | // but it is ok if variable has not been written to, so only print |
1577 | | // debug. See bug #4484. |
1578 | 0 | if (m_bNoDataSetAsInt64 && |
1579 | 0 | !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode()) |
1580 | 0 | { |
1581 | 0 | CPLDebug("GDAL_netCDF", |
1582 | 0 | "Setting NoDataValue to " CPL_FRMT_GIB |
1583 | 0 | " (previously set to " CPL_FRMT_GIB ") " |
1584 | 0 | "but file is no longer in define mode (id #%d, band #%d)", |
1585 | 0 | static_cast<GIntBig>(nNoData), |
1586 | 0 | static_cast<GIntBig>(m_nNodataValueInt64), cdfid, nBand); |
1587 | 0 | } |
1588 | | #ifdef NCDF_DEBUG |
1589 | | else |
1590 | | { |
1591 | | CPLDebug("GDAL_netCDF", |
1592 | | "Setting NoDataValue to " CPL_FRMT_GIB |
1593 | | " (id #%d, band #%d)", |
1594 | | static_cast<GIntBig>(nNoData), cdfid, nBand); |
1595 | | } |
1596 | | #endif |
1597 | | // Make sure we are in define mode. |
1598 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1599 | |
|
1600 | 0 | int status; |
1601 | 0 | if (eDataType == GDT_Int64 && |
1602 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4) |
1603 | 0 | { |
1604 | 0 | long long tmp = static_cast<long long>(nNoData); |
1605 | 0 | status = nc_put_att_longlong(cdfid, nZId, NCDF_FillValue, |
1606 | 0 | nc_datatype, 1, &tmp); |
1607 | 0 | } |
1608 | 0 | else |
1609 | 0 | { |
1610 | 0 | double dfNoData = static_cast<double>(nNoData); |
1611 | 0 | status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1612 | 0 | 1, &dfNoData); |
1613 | 0 | } |
1614 | |
|
1615 | 0 | NCDF_ERR(status); |
1616 | | |
1617 | | // Update status if write worked. |
1618 | 0 | if (status == NC_NOERR) |
1619 | 0 | { |
1620 | 0 | SetNoDataValueNoUpdate(nNoData); |
1621 | 0 | return CE_None; |
1622 | 0 | } |
1623 | | |
1624 | 0 | return CE_Failure; |
1625 | 0 | } |
1626 | | |
1627 | 0 | SetNoDataValueNoUpdate(nNoData); |
1628 | 0 | return CE_None; |
1629 | 0 | } |
1630 | | |
1631 | | /************************************************************************/ |
1632 | | /* SetNoDataValueNoUpdate() */ |
1633 | | /************************************************************************/ |
1634 | | |
1635 | | void netCDFRasterBand::SetNoDataValueNoUpdate(int64_t nNoData) |
1636 | 0 | { |
1637 | 0 | m_nNodataValueInt64 = nNoData; |
1638 | 0 | m_bNoDataSet = false; |
1639 | 0 | m_bNoDataSetAsInt64 = true; |
1640 | 0 | m_bNoDataSetAsUInt64 = false; |
1641 | 0 | } |
1642 | | |
1643 | | /************************************************************************/ |
1644 | | /* SetNoDataValueAsUInt64() */ |
1645 | | /************************************************************************/ |
1646 | | |
1647 | | CPLErr netCDFRasterBand::SetNoDataValueAsUInt64(uint64_t nNoData) |
1648 | | |
1649 | 0 | { |
1650 | 0 | CPLMutexHolderD(&hNCMutex); |
1651 | | |
1652 | | // If already set to new value, don't do anything. |
1653 | 0 | if (m_bNoDataSetAsUInt64 && nNoData == m_nNodataValueUInt64) |
1654 | 0 | return CE_None; |
1655 | | |
1656 | | // Write value if in update mode. |
1657 | 0 | if (poDS->GetAccess() == GA_Update) |
1658 | 0 | { |
1659 | | // netcdf-4 does not allow to set _FillValue after leaving define mode, |
1660 | | // but it is ok if variable has not been written to, so only print |
1661 | | // debug. See bug #4484. |
1662 | 0 | if (m_bNoDataSetAsUInt64 && |
1663 | 0 | !reinterpret_cast<netCDFDataset *>(poDS)->GetDefineMode()) |
1664 | 0 | { |
1665 | 0 | CPLDebug("GDAL_netCDF", |
1666 | 0 | "Setting NoDataValue to " CPL_FRMT_GUIB |
1667 | 0 | " (previously set to " CPL_FRMT_GUIB ") " |
1668 | 0 | "but file is no longer in define mode (id #%d, band #%d)", |
1669 | 0 | static_cast<GUIntBig>(nNoData), |
1670 | 0 | static_cast<GUIntBig>(m_nNodataValueUInt64), cdfid, nBand); |
1671 | 0 | } |
1672 | | #ifdef NCDF_DEBUG |
1673 | | else |
1674 | | { |
1675 | | CPLDebug("GDAL_netCDF", |
1676 | | "Setting NoDataValue to " CPL_FRMT_GUIB |
1677 | | " (id #%d, band #%d)", |
1678 | | static_cast<GUIntBig>(nNoData), cdfid, nBand); |
1679 | | } |
1680 | | #endif |
1681 | | // Make sure we are in define mode. |
1682 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1683 | |
|
1684 | 0 | int status; |
1685 | 0 | if (eDataType == GDT_UInt64 && |
1686 | 0 | reinterpret_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4) |
1687 | 0 | { |
1688 | 0 | unsigned long long tmp = static_cast<long long>(nNoData); |
1689 | 0 | status = nc_put_att_ulonglong(cdfid, nZId, NCDF_FillValue, |
1690 | 0 | nc_datatype, 1, &tmp); |
1691 | 0 | } |
1692 | 0 | else |
1693 | 0 | { |
1694 | 0 | double dfNoData = static_cast<double>(nNoData); |
1695 | 0 | status = nc_put_att_double(cdfid, nZId, NCDF_FillValue, nc_datatype, |
1696 | 0 | 1, &dfNoData); |
1697 | 0 | } |
1698 | |
|
1699 | 0 | NCDF_ERR(status); |
1700 | | |
1701 | | // Update status if write worked. |
1702 | 0 | if (status == NC_NOERR) |
1703 | 0 | { |
1704 | 0 | SetNoDataValueNoUpdate(nNoData); |
1705 | 0 | return CE_None; |
1706 | 0 | } |
1707 | | |
1708 | 0 | return CE_Failure; |
1709 | 0 | } |
1710 | | |
1711 | 0 | SetNoDataValueNoUpdate(nNoData); |
1712 | 0 | return CE_None; |
1713 | 0 | } |
1714 | | |
1715 | | /************************************************************************/ |
1716 | | /* SetNoDataValueNoUpdate() */ |
1717 | | /************************************************************************/ |
1718 | | |
1719 | | void netCDFRasterBand::SetNoDataValueNoUpdate(uint64_t nNoData) |
1720 | 0 | { |
1721 | 0 | m_nNodataValueUInt64 = nNoData; |
1722 | 0 | m_bNoDataSet = false; |
1723 | 0 | m_bNoDataSetAsInt64 = false; |
1724 | 0 | m_bNoDataSetAsUInt64 = true; |
1725 | 0 | } |
1726 | | |
1727 | | /************************************************************************/ |
1728 | | /* DeleteNoDataValue() */ |
1729 | | /************************************************************************/ |
1730 | | |
1731 | | #ifdef notdef |
1732 | | CPLErr netCDFRasterBand::DeleteNoDataValue() |
1733 | | |
1734 | | { |
1735 | | CPLMutexHolderD(&hNCMutex); |
1736 | | |
1737 | | if (!bNoDataSet) |
1738 | | return CE_None; |
1739 | | |
1740 | | // Write value if in update mode. |
1741 | | if (poDS->GetAccess() == GA_Update) |
1742 | | { |
1743 | | // Make sure we are in define mode. |
1744 | | static_cast<netCDFDataset *>(poDS)->SetDefineMode(true); |
1745 | | |
1746 | | status = nc_del_att(cdfid, nZId, NCDF_FillValue); |
1747 | | |
1748 | | NCDF_ERR(status); |
1749 | | |
1750 | | // Update status if write worked. |
1751 | | if (status == NC_NOERR) |
1752 | | { |
1753 | | dfNoDataValue = 0.0; |
1754 | | bNoDataSet = false; |
1755 | | return CE_None; |
1756 | | } |
1757 | | |
1758 | | return CE_Failure; |
1759 | | } |
1760 | | |
1761 | | dfNoDataValue = 0.0; |
1762 | | bNoDataSet = false; |
1763 | | return CE_None; |
1764 | | } |
1765 | | #endif |
1766 | | |
1767 | | /************************************************************************/ |
1768 | | /* SerializeToXML() */ |
1769 | | /************************************************************************/ |
1770 | | |
1771 | | CPLXMLNode *netCDFRasterBand::SerializeToXML(const char * /* pszUnused */) |
1772 | 0 | { |
1773 | | // Overridden from GDALPamDataset to add only band histogram |
1774 | | // and statistics. See bug #4244. |
1775 | 0 | if (psPam == nullptr) |
1776 | 0 | return nullptr; |
1777 | | |
1778 | | // Setup root node and attributes. |
1779 | 0 | CPLXMLNode *psTree = |
1780 | 0 | CPLCreateXMLNode(nullptr, CXT_Element, "PAMRasterBand"); |
1781 | |
|
1782 | 0 | if (GetBand() > 0) |
1783 | 0 | { |
1784 | 0 | CPLString oFmt; |
1785 | 0 | CPLSetXMLValue(psTree, "#band", oFmt.Printf("%d", GetBand())); |
1786 | 0 | } |
1787 | | |
1788 | | // Histograms. |
1789 | 0 | if (psPam->psSavedHistograms != nullptr) |
1790 | 0 | CPLAddXMLChild(psTree, CPLCloneXMLTree(psPam->psSavedHistograms)); |
1791 | | |
1792 | | // Metadata (statistics only). |
1793 | 0 | GDALMultiDomainMetadata oMDMDStats; |
1794 | 0 | const char *papszMDStats[] = {"STATISTICS_MINIMUM", "STATISTICS_MAXIMUM", |
1795 | 0 | "STATISTICS_MEAN", "STATISTICS_STDDEV", |
1796 | 0 | nullptr}; |
1797 | 0 | for (int i = 0; i < CSLCount(papszMDStats); i++) |
1798 | 0 | { |
1799 | 0 | const char *pszMDI = GetMetadataItem(papszMDStats[i]); |
1800 | 0 | if (pszMDI) |
1801 | 0 | oMDMDStats.SetMetadataItem(papszMDStats[i], pszMDI); |
1802 | 0 | } |
1803 | 0 | CPLXMLNode *psMD = oMDMDStats.Serialize(); |
1804 | |
|
1805 | 0 | if (psMD != nullptr) |
1806 | 0 | { |
1807 | 0 | if (psMD->psChild == nullptr) |
1808 | 0 | CPLDestroyXMLNode(psMD); |
1809 | 0 | else |
1810 | 0 | CPLAddXMLChild(psTree, psMD); |
1811 | 0 | } |
1812 | | |
1813 | | // We don't want to return anything if we had no metadata to attach. |
1814 | 0 | if (psTree->psChild == nullptr || psTree->psChild->psNext == nullptr) |
1815 | 0 | { |
1816 | 0 | CPLDestroyXMLNode(psTree); |
1817 | 0 | psTree = nullptr; |
1818 | 0 | } |
1819 | |
|
1820 | 0 | return psTree; |
1821 | 0 | } |
1822 | | |
1823 | | /************************************************************************/ |
1824 | | /* Get1DVariableIndexedByDimension() */ |
1825 | | /************************************************************************/ |
1826 | | |
1827 | | static int Get1DVariableIndexedByDimension(int cdfid, int nDimId, |
1828 | | const char *pszDimName, |
1829 | | bool bVerboseError, int *pnGroupID) |
1830 | 0 | { |
1831 | 0 | *pnGroupID = -1; |
1832 | 0 | int nVarID = -1; |
1833 | | // First try to find a variable whose name is identical to the dimension |
1834 | | // name, and check that it is indeed indexed by this dimension |
1835 | 0 | if (NCDFResolveVar(cdfid, pszDimName, pnGroupID, &nVarID) == CE_None) |
1836 | 0 | { |
1837 | 0 | int nDimCountOfVariable = 0; |
1838 | 0 | nc_inq_varndims(*pnGroupID, nVarID, &nDimCountOfVariable); |
1839 | 0 | if (nDimCountOfVariable == 1) |
1840 | 0 | { |
1841 | 0 | int nDimIdOfVariable = -1; |
1842 | 0 | nc_inq_vardimid(*pnGroupID, nVarID, &nDimIdOfVariable); |
1843 | 0 | if (nDimIdOfVariable == nDimId) |
1844 | 0 | { |
1845 | 0 | return nVarID; |
1846 | 0 | } |
1847 | 0 | } |
1848 | 0 | } |
1849 | | |
1850 | | // Otherwise iterate over the variables to find potential candidates |
1851 | | // TODO: should be modified to search also in other groups using the same |
1852 | | // logic than in NCDFResolveVar(), but maybe not needed if it's a |
1853 | | // very rare case? and I think this is not CF compliant. |
1854 | 0 | int nvars = 0; |
1855 | 0 | CPL_IGNORE_RET_VAL(nc_inq(cdfid, nullptr, &nvars, nullptr, nullptr)); |
1856 | |
|
1857 | 0 | int nCountCandidateVars = 0; |
1858 | 0 | int nCandidateVarID = -1; |
1859 | 0 | for (int k = 0; k < nvars; k++) |
1860 | 0 | { |
1861 | 0 | int nDimCountOfVariable = 0; |
1862 | 0 | nc_inq_varndims(cdfid, k, &nDimCountOfVariable); |
1863 | 0 | if (nDimCountOfVariable == 1) |
1864 | 0 | { |
1865 | 0 | int nDimIdOfVariable = -1; |
1866 | 0 | nc_inq_vardimid(cdfid, k, &nDimIdOfVariable); |
1867 | 0 | if (nDimIdOfVariable == nDimId) |
1868 | 0 | { |
1869 | 0 | nCountCandidateVars++; |
1870 | 0 | nCandidateVarID = k; |
1871 | 0 | } |
1872 | 0 | } |
1873 | 0 | } |
1874 | 0 | if (nCountCandidateVars > 1) |
1875 | 0 | { |
1876 | 0 | if (bVerboseError) |
1877 | 0 | { |
1878 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1879 | 0 | "Several 1D variables are indexed by dimension %s", |
1880 | 0 | pszDimName); |
1881 | 0 | } |
1882 | 0 | *pnGroupID = -1; |
1883 | 0 | return -1; |
1884 | 0 | } |
1885 | 0 | else if (nCandidateVarID < 0) |
1886 | 0 | { |
1887 | 0 | if (bVerboseError) |
1888 | 0 | { |
1889 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1890 | 0 | "No 1D variable is indexed by dimension %s", pszDimName); |
1891 | 0 | } |
1892 | 0 | } |
1893 | 0 | *pnGroupID = cdfid; |
1894 | 0 | return nCandidateVarID; |
1895 | 0 | } |
1896 | | |
1897 | | /************************************************************************/ |
1898 | | /* CreateMetadataFromAttributes() */ |
1899 | | /************************************************************************/ |
1900 | | |
1901 | | void netCDFRasterBand::CreateMetadataFromAttributes() |
1902 | 0 | { |
1903 | 0 | char szVarName[NC_MAX_NAME + 1] = {}; |
1904 | 0 | int status = nc_inq_varname(cdfid, nZId, szVarName); |
1905 | 0 | NCDF_ERR(status); |
1906 | |
|
1907 | 0 | GDALPamRasterBand::SetMetadataItem("NETCDF_VARNAME", szVarName); |
1908 | | |
1909 | | // Get attribute metadata. |
1910 | 0 | int nAtt = 0; |
1911 | 0 | NCDF_ERR(nc_inq_varnatts(cdfid, nZId, &nAtt)); |
1912 | |
|
1913 | 0 | for (int i = 0; i < nAtt; i++) |
1914 | 0 | { |
1915 | 0 | char szMetaName[NC_MAX_NAME + 1] = {}; |
1916 | 0 | status = nc_inq_attname(cdfid, nZId, i, szMetaName); |
1917 | 0 | if (status != NC_NOERR) |
1918 | 0 | continue; |
1919 | | |
1920 | 0 | if (GDALPamRasterBand::GetMetadataItem(szMetaName) != nullptr) |
1921 | 0 | { |
1922 | 0 | continue; |
1923 | 0 | } |
1924 | | |
1925 | 0 | char *pszMetaValue = nullptr; |
1926 | 0 | if (NCDFGetAttr(cdfid, nZId, szMetaName, &pszMetaValue) == CE_None) |
1927 | 0 | { |
1928 | 0 | GDALPamRasterBand::SetMetadataItem(szMetaName, pszMetaValue); |
1929 | 0 | } |
1930 | 0 | else |
1931 | 0 | { |
1932 | 0 | CPLDebug("GDAL_netCDF", "invalid Band metadata %s", szMetaName); |
1933 | 0 | } |
1934 | |
|
1935 | 0 | if (pszMetaValue) |
1936 | 0 | { |
1937 | 0 | CPLFree(pszMetaValue); |
1938 | 0 | pszMetaValue = nullptr; |
1939 | 0 | } |
1940 | 0 | } |
1941 | 0 | } |
1942 | | |
1943 | | /************************************************************************/ |
1944 | | /* CreateMetadataFromOtherVars() */ |
1945 | | /************************************************************************/ |
1946 | | |
1947 | | void netCDFRasterBand::CreateMetadataFromOtherVars() |
1948 | | |
1949 | 0 | { |
1950 | 0 | CPLAssert(!m_bCreateMetadataFromOtherVarsDone); |
1951 | 0 | m_bCreateMetadataFromOtherVarsDone = true; |
1952 | |
|
1953 | 0 | netCDFDataset *l_poDS = reinterpret_cast<netCDFDataset *>(poDS); |
1954 | 0 | const int nPamFlagsBackup = l_poDS->nPamFlags; |
1955 | | |
1956 | | // Compute all dimensions from Band number and save in Metadata. |
1957 | 0 | int nd = 0; |
1958 | 0 | nc_inq_varndims(cdfid, nZId, &nd); |
1959 | | // Compute multidimention band position. |
1960 | | // |
1961 | | // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels) |
1962 | | // if Data[2,3,4,x,y] |
1963 | | // |
1964 | | // BandPos0 = (nBand) / (3*4) |
1965 | | // BandPos1 = (nBand - BandPos0*(3*4)) / (4) |
1966 | | // BandPos2 = (nBand - BandPos0*(3*4)) % (4) |
1967 | |
|
1968 | 0 | int Sum = 1; |
1969 | 0 | if (nd == 3) |
1970 | 0 | { |
1971 | 0 | Sum *= panBandZLev[0]; |
1972 | 0 | } |
1973 | | |
1974 | | // Loop over non-spatial dimensions. |
1975 | 0 | int Taken = 0; |
1976 | |
|
1977 | 0 | for (int i = 0; i < nd - 2; i++) |
1978 | 0 | { |
1979 | 0 | int result; |
1980 | 0 | if (i != nd - 2 - 1) |
1981 | 0 | { |
1982 | 0 | Sum = 1; |
1983 | 0 | for (int j = i + 1; j < nd - 2; j++) |
1984 | 0 | { |
1985 | 0 | Sum *= panBandZLev[j]; |
1986 | 0 | } |
1987 | 0 | result = static_cast<int>((nLevel - Taken) / Sum); |
1988 | 0 | } |
1989 | 0 | else |
1990 | 0 | { |
1991 | 0 | result = static_cast<int>((nLevel - Taken) % Sum); |
1992 | 0 | } |
1993 | |
|
1994 | 0 | char szName[NC_MAX_NAME + 1] = {}; |
1995 | 0 | snprintf(szName, sizeof(szName), "%s", |
1996 | 0 | l_poDS->papszDimName[l_poDS->m_anDimIds[panBandZPos[i]]]); |
1997 | |
|
1998 | 0 | char szMetaName[NC_MAX_NAME + 1 + 32]; |
1999 | 0 | snprintf(szMetaName, sizeof(szMetaName), "NETCDF_DIM_%s", szName); |
2000 | |
|
2001 | 0 | const int nGroupID = l_poDS->m_anExtraDimGroupIds[i]; |
2002 | 0 | const int nVarID = l_poDS->m_anExtraDimVarIds[i]; |
2003 | 0 | if (nVarID < 0) |
2004 | 0 | { |
2005 | 0 | GDALPamRasterBand::SetMetadataItem(szMetaName, |
2006 | 0 | CPLSPrintf("%d", result + 1)); |
2007 | 0 | } |
2008 | 0 | else |
2009 | 0 | { |
2010 | | // TODO: Make sure all the status checks make sense. |
2011 | |
|
2012 | 0 | nc_type nVarType = NC_NAT; |
2013 | 0 | /* status = */ nc_inq_vartype(nGroupID, nVarID, &nVarType); |
2014 | |
|
2015 | 0 | int nDims = 0; |
2016 | 0 | /* status = */ nc_inq_varndims(nGroupID, nVarID, &nDims); |
2017 | |
|
2018 | 0 | char szMetaTemp[256] = {}; |
2019 | 0 | if (nDims == 1) |
2020 | 0 | { |
2021 | 0 | size_t count[1] = {1}; |
2022 | 0 | size_t start[1] = {static_cast<size_t>(result)}; |
2023 | |
|
2024 | 0 | switch (nVarType) |
2025 | 0 | { |
2026 | 0 | case NC_BYTE: |
2027 | | // TODO: Check for signed/unsigned byte. |
2028 | 0 | signed char cData; |
2029 | 0 | /* status = */ nc_get_vara_schar(nGroupID, nVarID, |
2030 | 0 | start, count, &cData); |
2031 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", cData); |
2032 | 0 | break; |
2033 | 0 | case NC_SHORT: |
2034 | 0 | short sData; |
2035 | 0 | /* status = */ nc_get_vara_short(nGroupID, nVarID, |
2036 | 0 | start, count, &sData); |
2037 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", sData); |
2038 | 0 | break; |
2039 | 0 | case NC_INT: |
2040 | 0 | { |
2041 | 0 | int nData; |
2042 | 0 | /* status = */ nc_get_vara_int(nGroupID, nVarID, start, |
2043 | 0 | count, &nData); |
2044 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", nData); |
2045 | 0 | break; |
2046 | 0 | } |
2047 | 0 | case NC_FLOAT: |
2048 | 0 | float fData; |
2049 | 0 | /* status = */ nc_get_vara_float(nGroupID, nVarID, |
2050 | 0 | start, count, &fData); |
2051 | 0 | CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.8g", |
2052 | 0 | fData); |
2053 | 0 | break; |
2054 | 0 | case NC_DOUBLE: |
2055 | 0 | double dfData; |
2056 | 0 | /* status = */ nc_get_vara_double( |
2057 | 0 | nGroupID, nVarID, start, count, &dfData); |
2058 | 0 | CPLsnprintf(szMetaTemp, sizeof(szMetaTemp), "%.16g", |
2059 | 0 | dfData); |
2060 | 0 | break; |
2061 | 0 | case NC_UBYTE: |
2062 | 0 | unsigned char ucData; |
2063 | 0 | /* status = */ nc_get_vara_uchar(nGroupID, nVarID, |
2064 | 0 | start, count, &ucData); |
2065 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", ucData); |
2066 | 0 | break; |
2067 | 0 | case NC_USHORT: |
2068 | 0 | unsigned short usData; |
2069 | 0 | /* status = */ nc_get_vara_ushort( |
2070 | 0 | nGroupID, nVarID, start, count, &usData); |
2071 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", usData); |
2072 | 0 | break; |
2073 | 0 | case NC_UINT: |
2074 | 0 | { |
2075 | 0 | unsigned int unData; |
2076 | 0 | /* status = */ nc_get_vara_uint(nGroupID, nVarID, start, |
2077 | 0 | count, &unData); |
2078 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%u", unData); |
2079 | 0 | break; |
2080 | 0 | } |
2081 | 0 | case NC_INT64: |
2082 | 0 | { |
2083 | 0 | long long nData; |
2084 | 0 | /* status = */ nc_get_vara_longlong( |
2085 | 0 | nGroupID, nVarID, start, count, &nData); |
2086 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GIB, |
2087 | 0 | nData); |
2088 | 0 | break; |
2089 | 0 | } |
2090 | 0 | case NC_UINT64: |
2091 | 0 | { |
2092 | 0 | unsigned long long unData; |
2093 | 0 | /* status = */ nc_get_vara_ulonglong( |
2094 | 0 | nGroupID, nVarID, start, count, &unData); |
2095 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), CPL_FRMT_GUIB, |
2096 | 0 | unData); |
2097 | 0 | break; |
2098 | 0 | } |
2099 | 0 | default: |
2100 | 0 | CPLDebug("GDAL_netCDF", "invalid dim %s, type=%d", |
2101 | 0 | szMetaTemp, nVarType); |
2102 | 0 | break; |
2103 | 0 | } |
2104 | 0 | } |
2105 | 0 | else |
2106 | 0 | { |
2107 | 0 | snprintf(szMetaTemp, sizeof(szMetaTemp), "%d", result + 1); |
2108 | 0 | } |
2109 | | |
2110 | | // Save dimension value. |
2111 | | // NOTE: removed #original_units as not part of CF-1. |
2112 | | |
2113 | 0 | GDALPamRasterBand::SetMetadataItem(szMetaName, szMetaTemp); |
2114 | 0 | } |
2115 | | |
2116 | | // Avoid int32 overflow. Perhaps something more sensible to do here ? |
2117 | 0 | if (result > 0 && Sum > INT_MAX / result) |
2118 | 0 | break; |
2119 | 0 | if (Taken > INT_MAX - result * Sum) |
2120 | 0 | break; |
2121 | | |
2122 | 0 | Taken += result * Sum; |
2123 | 0 | } // End loop non-spatial dimensions. |
2124 | | |
2125 | 0 | l_poDS->nPamFlags = nPamFlagsBackup; |
2126 | 0 | } |
2127 | | |
2128 | | /************************************************************************/ |
2129 | | /* CheckData() */ |
2130 | | /************************************************************************/ |
2131 | | template <class T> |
2132 | | void netCDFRasterBand::CheckData(void *pImage, void *pImageNC, |
2133 | | size_t nTmpBlockXSize, size_t nTmpBlockYSize, |
2134 | | bool bCheckIsNan) |
2135 | 0 | { |
2136 | 0 | CPLAssert(pImage != nullptr && pImageNC != nullptr); |
2137 | | |
2138 | | // If this block is not a full block (in the x axis), we need to re-arrange |
2139 | | // the data this is because partial blocks are not arranged the same way in |
2140 | | // netcdf and gdal. |
2141 | 0 | if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize)) |
2142 | 0 | { |
2143 | 0 | T *ptrWrite = static_cast<T *>(pImage); |
2144 | 0 | T *ptrRead = static_cast<T *>(pImageNC); |
2145 | 0 | for (size_t j = 0; j < nTmpBlockYSize; |
2146 | 0 | j++, ptrWrite += nBlockXSize, ptrRead += nTmpBlockXSize) |
2147 | 0 | { |
2148 | 0 | memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T)); |
2149 | 0 | } |
2150 | 0 | } |
2151 | | |
2152 | | // Is valid data checking needed or requested? |
2153 | 0 | if (bValidRangeValid || bCheckIsNan) |
2154 | 0 | { |
2155 | 0 | T *ptrImage = static_cast<T *>(pImage); |
2156 | 0 | for (size_t j = 0; j < nTmpBlockYSize; j++) |
2157 | 0 | { |
2158 | | // k moves along the gdal block, skipping the out-of-range pixels. |
2159 | 0 | size_t k = j * nBlockXSize; |
2160 | 0 | for (size_t i = 0; i < nTmpBlockXSize; i++, k++) |
2161 | 0 | { |
2162 | | // Check for nodata and nan. |
2163 | 0 | if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue)) |
2164 | 0 | continue; |
2165 | 0 | if (bCheckIsNan && std::isnan((double)ptrImage[k])) |
2166 | 0 | { |
2167 | 0 | ptrImage[k] = (T)m_dfNoDataValue; |
2168 | 0 | continue; |
2169 | 0 | } |
2170 | | // Check for valid_range. |
2171 | 0 | if (bValidRangeValid) |
2172 | 0 | { |
2173 | 0 | if (((adfValidRange[0] != m_dfNoDataValue) && |
2174 | 0 | (ptrImage[k] < (T)adfValidRange[0])) || |
2175 | 0 | ((adfValidRange[1] != m_dfNoDataValue) && |
2176 | 0 | (ptrImage[k] > (T)adfValidRange[1]))) |
2177 | 0 | { |
2178 | 0 | ptrImage[k] = (T)m_dfNoDataValue; |
2179 | 0 | } |
2180 | 0 | } |
2181 | 0 | } |
2182 | 0 | } |
2183 | 0 | } |
2184 | | |
2185 | | // If minimum longitude is > 180, subtract 360 from all. |
2186 | | // If not, disable checking for further calls (check just once). |
2187 | | // Only check first and last block elements since lon must be monotonic. |
2188 | 0 | const bool bIsSigned = std::numeric_limits<T>::is_signed; |
2189 | 0 | if (bCheckLongitude && bIsSigned && |
2190 | 0 | !CPLIsEqual((double)((T *)pImage)[0], m_dfNoDataValue) && |
2191 | 0 | !CPLIsEqual((double)((T *)pImage)[nTmpBlockXSize - 1], |
2192 | 0 | m_dfNoDataValue) && |
2193 | 0 | std::min(((T *)pImage)[0], ((T *)pImage)[nTmpBlockXSize - 1]) > 180.0) |
2194 | 0 | { |
2195 | 0 | T *ptrImage = static_cast<T *>(pImage); |
2196 | 0 | for (size_t j = 0; j < nTmpBlockYSize; j++) |
2197 | 0 | { |
2198 | 0 | size_t k = j * nBlockXSize; |
2199 | 0 | for (size_t i = 0; i < nTmpBlockXSize; i++, k++) |
2200 | 0 | { |
2201 | 0 | if (!CPLIsEqual((double)ptrImage[k], m_dfNoDataValue)) |
2202 | 0 | ptrImage[k] = static_cast<T>(ptrImage[k] - 360); |
2203 | 0 | } |
2204 | 0 | } |
2205 | 0 | } |
2206 | 0 | else |
2207 | 0 | { |
2208 | 0 | bCheckLongitude = false; |
2209 | 0 | } |
2210 | 0 | } Unexecuted instantiation: void netCDFRasterBand::CheckData<signed char>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned char>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<short>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned short>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<int>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<float>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<double>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned int>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<long>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckData<unsigned long>(void*, void*, unsigned long, unsigned long, bool) |
2211 | | |
2212 | | /************************************************************************/ |
2213 | | /* CheckDataCpx() */ |
2214 | | /************************************************************************/ |
2215 | | template <class T> |
2216 | | void netCDFRasterBand::CheckDataCpx(void *pImage, void *pImageNC, |
2217 | | size_t nTmpBlockXSize, |
2218 | | size_t nTmpBlockYSize, bool bCheckIsNan) |
2219 | 0 | { |
2220 | 0 | CPLAssert(pImage != nullptr && pImageNC != nullptr); |
2221 | | |
2222 | | // If this block is not a full block (in the x axis), we need to re-arrange |
2223 | | // the data this is because partial blocks are not arranged the same way in |
2224 | | // netcdf and gdal. |
2225 | 0 | if (nTmpBlockXSize != static_cast<size_t>(nBlockXSize)) |
2226 | 0 | { |
2227 | 0 | T *ptrWrite = static_cast<T *>(pImage); |
2228 | 0 | T *ptrRead = static_cast<T *>(pImageNC); |
2229 | 0 | for (size_t j = 0; j < nTmpBlockYSize; j++, |
2230 | 0 | ptrWrite += (2 * nBlockXSize), |
2231 | 0 | ptrRead += (2 * nTmpBlockXSize)) |
2232 | 0 | { |
2233 | 0 | memmove(ptrWrite, ptrRead, nTmpBlockXSize * sizeof(T) * 2); |
2234 | 0 | } |
2235 | 0 | } |
2236 | | |
2237 | | // Is valid data checking needed or requested? |
2238 | 0 | if (bValidRangeValid || bCheckIsNan) |
2239 | 0 | { |
2240 | 0 | T *ptrImage = static_cast<T *>(pImage); |
2241 | 0 | for (size_t j = 0; j < nTmpBlockYSize; j++) |
2242 | 0 | { |
2243 | | // k moves along the gdal block, skipping the out-of-range pixels. |
2244 | 0 | size_t k = 2 * j * nBlockXSize; |
2245 | 0 | for (size_t i = 0; i < (2 * nTmpBlockXSize); i++, k++) |
2246 | 0 | { |
2247 | | // Check for nodata and nan. |
2248 | 0 | if (CPLIsEqual((double)ptrImage[k], m_dfNoDataValue)) |
2249 | 0 | continue; |
2250 | 0 | if (bCheckIsNan && std::isnan((double)ptrImage[k])) |
2251 | 0 | { |
2252 | 0 | ptrImage[k] = (T)m_dfNoDataValue; |
2253 | 0 | continue; |
2254 | 0 | } |
2255 | | // Check for valid_range. |
2256 | 0 | if (bValidRangeValid) |
2257 | 0 | { |
2258 | 0 | if (((adfValidRange[0] != m_dfNoDataValue) && |
2259 | 0 | (ptrImage[k] < (T)adfValidRange[0])) || |
2260 | 0 | ((adfValidRange[1] != m_dfNoDataValue) && |
2261 | 0 | (ptrImage[k] > (T)adfValidRange[1]))) |
2262 | 0 | { |
2263 | 0 | ptrImage[k] = (T)m_dfNoDataValue; |
2264 | 0 | } |
2265 | 0 | } |
2266 | 0 | } |
2267 | 0 | } |
2268 | 0 | } |
2269 | 0 | } Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<short>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<int>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<float>(void*, void*, unsigned long, unsigned long, bool) Unexecuted instantiation: void netCDFRasterBand::CheckDataCpx<double>(void*, void*, unsigned long, unsigned long, bool) |
2270 | | |
2271 | | /************************************************************************/ |
2272 | | /* FetchNetcdfChunk() */ |
2273 | | /************************************************************************/ |
2274 | | |
2275 | | bool netCDFRasterBand::FetchNetcdfChunk(size_t xstart, size_t ystart, |
2276 | | void *pImage) |
2277 | 0 | { |
2278 | 0 | size_t start[MAX_NC_DIMS] = {}; |
2279 | 0 | size_t edge[MAX_NC_DIMS] = {}; |
2280 | |
|
2281 | 0 | start[nBandXPos] = xstart; |
2282 | 0 | edge[nBandXPos] = nBlockXSize; |
2283 | 0 | if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize) |
2284 | 0 | edge[nBandXPos] = nRasterXSize - start[nBandXPos]; |
2285 | 0 | if (nBandYPos >= 0) |
2286 | 0 | { |
2287 | 0 | start[nBandYPos] = ystart; |
2288 | 0 | edge[nBandYPos] = nBlockYSize; |
2289 | 0 | if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize) |
2290 | 0 | edge[nBandYPos] = nRasterYSize - start[nBandYPos]; |
2291 | 0 | } |
2292 | 0 | const size_t nYChunkSize = nBandYPos < 0 ? 1 : edge[nBandYPos]; |
2293 | |
|
2294 | | #ifdef NCDF_DEBUG |
2295 | | CPLDebug("GDAL_netCDF", "start={%ld,%ld} edge={%ld,%ld} bBottomUp=%d", |
2296 | | start[nBandXPos], nBandYPos < 0 ? 0 : start[nBandYPos], |
2297 | | edge[nBandXPos], nYChunkSize, ((netCDFDataset *)poDS)->bBottomUp); |
2298 | | #endif |
2299 | |
|
2300 | 0 | int nd = 0; |
2301 | 0 | nc_inq_varndims(cdfid, nZId, &nd); |
2302 | 0 | if (nd == 3) |
2303 | 0 | { |
2304 | 0 | start[panBandZPos[0]] = nLevel; // z |
2305 | 0 | edge[panBandZPos[0]] = 1; |
2306 | 0 | } |
2307 | | |
2308 | | // Compute multidimention band position. |
2309 | | // |
2310 | | // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels) |
2311 | | // if Data[2,3,4,x,y] |
2312 | | // |
2313 | | // BandPos0 = (nBand) / (3*4) |
2314 | | // BandPos1 = (nBand - (3*4)) / (4) |
2315 | | // BandPos2 = (nBand - (3*4)) % (4) |
2316 | 0 | if (nd > 3) |
2317 | 0 | { |
2318 | 0 | int Sum = -1; |
2319 | 0 | int Taken = 0; |
2320 | 0 | for (int i = 0; i < nd - 2; i++) |
2321 | 0 | { |
2322 | 0 | if (i != nd - 2 - 1) |
2323 | 0 | { |
2324 | 0 | Sum = 1; |
2325 | 0 | for (int j = i + 1; j < nd - 2; j++) |
2326 | 0 | { |
2327 | 0 | Sum *= panBandZLev[j]; |
2328 | 0 | } |
2329 | 0 | start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum); |
2330 | 0 | edge[panBandZPos[i]] = 1; |
2331 | 0 | } |
2332 | 0 | else |
2333 | 0 | { |
2334 | 0 | start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum); |
2335 | 0 | edge[panBandZPos[i]] = 1; |
2336 | 0 | } |
2337 | 0 | Taken += static_cast<int>(start[panBandZPos[i]]) * Sum; |
2338 | 0 | } |
2339 | 0 | } |
2340 | | |
2341 | | // Make sure we are in data mode. |
2342 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(false); |
2343 | | |
2344 | | // If this block is not a full block in the x axis, we need to |
2345 | | // re-arrange the data because partial blocks are not arranged the |
2346 | | // same way in netcdf and gdal, so we first we read the netcdf data at |
2347 | | // the end of the gdal block buffer then re-arrange rows in CheckData(). |
2348 | 0 | void *pImageNC = pImage; |
2349 | 0 | if (edge[nBandXPos] != static_cast<size_t>(nBlockXSize)) |
2350 | 0 | { |
2351 | 0 | pImageNC = static_cast<GByte *>(pImage) + |
2352 | 0 | ((static_cast<size_t>(nBlockXSize) * nBlockYSize - |
2353 | 0 | edge[nBandXPos] * nYChunkSize) * |
2354 | 0 | (GDALGetDataTypeSize(eDataType) / 8)); |
2355 | 0 | } |
2356 | | |
2357 | | // Read data according to type. |
2358 | 0 | int status; |
2359 | 0 | if (eDataType == GDT_Byte) |
2360 | 0 | { |
2361 | 0 | if (bSignedData) |
2362 | 0 | { |
2363 | 0 | status = nc_get_vara_schar(cdfid, nZId, start, edge, |
2364 | 0 | static_cast<signed char *>(pImageNC)); |
2365 | 0 | if (status == NC_NOERR) |
2366 | 0 | CheckData<signed char>(pImage, pImageNC, edge[nBandXPos], |
2367 | 0 | nYChunkSize, false); |
2368 | 0 | } |
2369 | 0 | else |
2370 | 0 | { |
2371 | 0 | status = nc_get_vara_uchar(cdfid, nZId, start, edge, |
2372 | 0 | static_cast<unsigned char *>(pImageNC)); |
2373 | 0 | if (status == NC_NOERR) |
2374 | 0 | CheckData<unsigned char>(pImage, pImageNC, edge[nBandXPos], |
2375 | 0 | nYChunkSize, false); |
2376 | 0 | } |
2377 | 0 | } |
2378 | 0 | else if (eDataType == GDT_Int8) |
2379 | 0 | { |
2380 | 0 | status = nc_get_vara_schar(cdfid, nZId, start, edge, |
2381 | 0 | static_cast<signed char *>(pImageNC)); |
2382 | 0 | if (status == NC_NOERR) |
2383 | 0 | CheckData<signed char>(pImage, pImageNC, edge[nBandXPos], |
2384 | 0 | nYChunkSize, false); |
2385 | 0 | } |
2386 | 0 | else if (nc_datatype == NC_SHORT) |
2387 | 0 | { |
2388 | 0 | status = nc_get_vara_short(cdfid, nZId, start, edge, |
2389 | 0 | static_cast<short *>(pImageNC)); |
2390 | 0 | if (status == NC_NOERR) |
2391 | 0 | { |
2392 | 0 | if (eDataType == GDT_Int16) |
2393 | 0 | { |
2394 | 0 | CheckData<GInt16>(pImage, pImageNC, edge[nBandXPos], |
2395 | 0 | nYChunkSize, false); |
2396 | 0 | } |
2397 | 0 | else |
2398 | 0 | { |
2399 | 0 | CheckData<GUInt16>(pImage, pImageNC, edge[nBandXPos], |
2400 | 0 | nYChunkSize, false); |
2401 | 0 | } |
2402 | 0 | } |
2403 | 0 | } |
2404 | 0 | else if (eDataType == GDT_Int32) |
2405 | 0 | { |
2406 | | #if SIZEOF_UNSIGNED_LONG == 4 |
2407 | | status = nc_get_vara_long(cdfid, nZId, start, edge, |
2408 | | static_cast<long *>(pImageNC)); |
2409 | | if (status == NC_NOERR) |
2410 | | CheckData<long>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2411 | | false); |
2412 | | #else |
2413 | 0 | status = nc_get_vara_int(cdfid, nZId, start, edge, |
2414 | 0 | static_cast<int *>(pImageNC)); |
2415 | 0 | if (status == NC_NOERR) |
2416 | 0 | CheckData<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2417 | 0 | false); |
2418 | 0 | #endif |
2419 | 0 | } |
2420 | 0 | else if (eDataType == GDT_Float32) |
2421 | 0 | { |
2422 | 0 | status = nc_get_vara_float(cdfid, nZId, start, edge, |
2423 | 0 | static_cast<float *>(pImageNC)); |
2424 | 0 | if (status == NC_NOERR) |
2425 | 0 | CheckData<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2426 | 0 | true); |
2427 | 0 | } |
2428 | 0 | else if (eDataType == GDT_Float64) |
2429 | 0 | { |
2430 | 0 | status = nc_get_vara_double(cdfid, nZId, start, edge, |
2431 | 0 | static_cast<double *>(pImageNC)); |
2432 | 0 | if (status == NC_NOERR) |
2433 | 0 | CheckData<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2434 | 0 | true); |
2435 | 0 | } |
2436 | 0 | else if (eDataType == GDT_UInt16) |
2437 | 0 | { |
2438 | 0 | status = nc_get_vara_ushort(cdfid, nZId, start, edge, |
2439 | 0 | static_cast<unsigned short *>(pImageNC)); |
2440 | 0 | if (status == NC_NOERR) |
2441 | 0 | CheckData<unsigned short>(pImage, pImageNC, edge[nBandXPos], |
2442 | 0 | nYChunkSize, false); |
2443 | 0 | } |
2444 | 0 | else if (eDataType == GDT_UInt32) |
2445 | 0 | { |
2446 | 0 | status = nc_get_vara_uint(cdfid, nZId, start, edge, |
2447 | 0 | static_cast<unsigned int *>(pImageNC)); |
2448 | 0 | if (status == NC_NOERR) |
2449 | 0 | CheckData<unsigned int>(pImage, pImageNC, edge[nBandXPos], |
2450 | 0 | nYChunkSize, false); |
2451 | 0 | } |
2452 | 0 | else if (eDataType == GDT_Int64) |
2453 | 0 | { |
2454 | 0 | status = nc_get_vara_longlong(cdfid, nZId, start, edge, |
2455 | 0 | static_cast<long long *>(pImageNC)); |
2456 | 0 | if (status == NC_NOERR) |
2457 | 0 | CheckData<std::int64_t>(pImage, pImageNC, edge[nBandXPos], |
2458 | 0 | nYChunkSize, false); |
2459 | 0 | } |
2460 | 0 | else if (eDataType == GDT_UInt64) |
2461 | 0 | { |
2462 | 0 | status = |
2463 | 0 | nc_get_vara_ulonglong(cdfid, nZId, start, edge, |
2464 | 0 | static_cast<unsigned long long *>(pImageNC)); |
2465 | 0 | if (status == NC_NOERR) |
2466 | 0 | CheckData<std::uint64_t>(pImage, pImageNC, edge[nBandXPos], |
2467 | 0 | nYChunkSize, false); |
2468 | 0 | } |
2469 | 0 | else if (eDataType == GDT_CInt16) |
2470 | 0 | { |
2471 | 0 | status = nc_get_vara(cdfid, nZId, start, edge, pImageNC); |
2472 | 0 | if (status == NC_NOERR) |
2473 | 0 | CheckDataCpx<short>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2474 | 0 | false); |
2475 | 0 | } |
2476 | 0 | else if (eDataType == GDT_CInt32) |
2477 | 0 | { |
2478 | 0 | status = nc_get_vara(cdfid, nZId, start, edge, pImageNC); |
2479 | 0 | if (status == NC_NOERR) |
2480 | 0 | CheckDataCpx<int>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2481 | 0 | false); |
2482 | 0 | } |
2483 | 0 | else if (eDataType == GDT_CFloat32) |
2484 | 0 | { |
2485 | 0 | status = nc_get_vara(cdfid, nZId, start, edge, pImageNC); |
2486 | 0 | if (status == NC_NOERR) |
2487 | 0 | CheckDataCpx<float>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2488 | 0 | false); |
2489 | 0 | } |
2490 | 0 | else if (eDataType == GDT_CFloat64) |
2491 | 0 | { |
2492 | 0 | status = nc_get_vara(cdfid, nZId, start, edge, pImageNC); |
2493 | 0 | if (status == NC_NOERR) |
2494 | 0 | CheckDataCpx<double>(pImage, pImageNC, edge[nBandXPos], nYChunkSize, |
2495 | 0 | false); |
2496 | 0 | } |
2497 | | |
2498 | 0 | else |
2499 | 0 | status = NC_EBADTYPE; |
2500 | |
|
2501 | 0 | if (status != NC_NOERR) |
2502 | 0 | { |
2503 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2504 | 0 | "netCDF chunk fetch failed: #%d (%s)", status, |
2505 | 0 | nc_strerror(status)); |
2506 | 0 | return false; |
2507 | 0 | } |
2508 | 0 | return true; |
2509 | 0 | } |
2510 | | |
2511 | | /************************************************************************/ |
2512 | | /* IReadBlock() */ |
2513 | | /************************************************************************/ |
2514 | | |
2515 | | CPLErr netCDFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, |
2516 | | void *pImage) |
2517 | | |
2518 | 0 | { |
2519 | 0 | CPLMutexHolderD(&hNCMutex); |
2520 | | |
2521 | | // Locate X, Y and Z position in the array. |
2522 | |
|
2523 | 0 | size_t xstart = static_cast<size_t>(nBlockXOff) * nBlockXSize; |
2524 | 0 | size_t ystart = 0; |
2525 | | |
2526 | | // Check y order. |
2527 | 0 | if (nBandYPos >= 0) |
2528 | 0 | { |
2529 | 0 | auto poGDS = static_cast<netCDFDataset *>(poDS); |
2530 | 0 | if (poGDS->bBottomUp) |
2531 | 0 | { |
2532 | 0 | if (nBlockYSize == 1) |
2533 | 0 | { |
2534 | 0 | ystart = nRasterYSize - 1 - nBlockYOff; |
2535 | 0 | } |
2536 | 0 | else |
2537 | 0 | { |
2538 | | // in GDAL space |
2539 | 0 | ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize; |
2540 | 0 | const size_t yend = |
2541 | 0 | std::min(ystart + nBlockYSize - 1, |
2542 | 0 | static_cast<size_t>(nRasterYSize - 1)); |
2543 | | // in netCDF space |
2544 | 0 | const size_t nFirstChunkLine = nRasterYSize - 1 - yend; |
2545 | 0 | const size_t nLastChunkLine = nRasterYSize - 1 - ystart; |
2546 | 0 | const size_t nFirstChunkBlock = nFirstChunkLine / nBlockYSize; |
2547 | 0 | const size_t nLastChunkBlock = nLastChunkLine / nBlockYSize; |
2548 | |
|
2549 | 0 | const auto firstKey = netCDFDataset::ChunkKey( |
2550 | 0 | nBlockXOff, nFirstChunkBlock, nBand); |
2551 | 0 | const auto secondKey = |
2552 | 0 | netCDFDataset::ChunkKey(nBlockXOff, nLastChunkBlock, nBand); |
2553 | | |
2554 | | // Retrieve data from the one or 2 needed netCDF chunks |
2555 | 0 | std::shared_ptr<std::vector<GByte>> firstChunk; |
2556 | 0 | std::shared_ptr<std::vector<GByte>> secondChunk; |
2557 | 0 | if (poGDS->poChunkCache) |
2558 | 0 | { |
2559 | 0 | poGDS->poChunkCache->tryGet(firstKey, firstChunk); |
2560 | 0 | if (firstKey != secondKey) |
2561 | 0 | poGDS->poChunkCache->tryGet(secondKey, secondChunk); |
2562 | 0 | } |
2563 | 0 | const size_t nChunkLineSize = |
2564 | 0 | static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) * |
2565 | 0 | nBlockXSize; |
2566 | 0 | const size_t nChunkSize = nChunkLineSize * nBlockYSize; |
2567 | 0 | if (!firstChunk) |
2568 | 0 | { |
2569 | 0 | firstChunk.reset(new std::vector<GByte>(nChunkSize)); |
2570 | 0 | if (!FetchNetcdfChunk(xstart, |
2571 | 0 | nFirstChunkBlock * nBlockYSize, |
2572 | 0 | firstChunk.get()->data())) |
2573 | 0 | return CE_Failure; |
2574 | 0 | if (poGDS->poChunkCache) |
2575 | 0 | poGDS->poChunkCache->insert(firstKey, firstChunk); |
2576 | 0 | } |
2577 | 0 | if (!secondChunk && firstKey != secondKey) |
2578 | 0 | { |
2579 | 0 | secondChunk.reset(new std::vector<GByte>(nChunkSize)); |
2580 | 0 | if (!FetchNetcdfChunk(xstart, nLastChunkBlock * nBlockYSize, |
2581 | 0 | secondChunk.get()->data())) |
2582 | 0 | return CE_Failure; |
2583 | 0 | if (poGDS->poChunkCache) |
2584 | 0 | poGDS->poChunkCache->insert(secondKey, secondChunk); |
2585 | 0 | } |
2586 | | |
2587 | | // Assemble netCDF chunks into GDAL block |
2588 | 0 | GByte *pabyImage = static_cast<GByte *>(pImage); |
2589 | 0 | const size_t nFirstChunkBlockLine = |
2590 | 0 | nFirstChunkBlock * nBlockYSize; |
2591 | 0 | const size_t nLastChunkBlockLine = |
2592 | 0 | nLastChunkBlock * nBlockYSize; |
2593 | 0 | for (size_t iLine = ystart; iLine <= yend; iLine++) |
2594 | 0 | { |
2595 | 0 | const size_t nLineFromBottom = nRasterYSize - 1 - iLine; |
2596 | 0 | const size_t nChunkY = nLineFromBottom / nBlockYSize; |
2597 | 0 | if (nChunkY == nFirstChunkBlock) |
2598 | 0 | { |
2599 | 0 | memcpy(pabyImage + nChunkLineSize * (iLine - ystart), |
2600 | 0 | firstChunk.get()->data() + |
2601 | 0 | (nLineFromBottom - nFirstChunkBlockLine) * |
2602 | 0 | nChunkLineSize, |
2603 | 0 | nChunkLineSize); |
2604 | 0 | } |
2605 | 0 | else |
2606 | 0 | { |
2607 | 0 | CPLAssert(nChunkY == nLastChunkBlock); |
2608 | 0 | assert(secondChunk); |
2609 | 0 | memcpy(pabyImage + nChunkLineSize * (iLine - ystart), |
2610 | 0 | secondChunk.get()->data() + |
2611 | 0 | (nLineFromBottom - nLastChunkBlockLine) * |
2612 | 0 | nChunkLineSize, |
2613 | 0 | nChunkLineSize); |
2614 | 0 | } |
2615 | 0 | } |
2616 | 0 | return CE_None; |
2617 | 0 | } |
2618 | 0 | } |
2619 | 0 | else |
2620 | 0 | { |
2621 | 0 | ystart = static_cast<size_t>(nBlockYOff) * nBlockYSize; |
2622 | 0 | } |
2623 | 0 | } |
2624 | | |
2625 | 0 | return FetchNetcdfChunk(xstart, ystart, pImage) ? CE_None : CE_Failure; |
2626 | 0 | } |
2627 | | |
2628 | | /************************************************************************/ |
2629 | | /* IWriteBlock() */ |
2630 | | /************************************************************************/ |
2631 | | |
2632 | | CPLErr netCDFRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff, |
2633 | | void *pImage) |
2634 | 0 | { |
2635 | 0 | CPLMutexHolderD(&hNCMutex); |
2636 | |
|
2637 | | #ifdef NCDF_DEBUG |
2638 | | if (nBlockYOff == 0 || (nBlockYOff == nRasterYSize - 1)) |
2639 | | CPLDebug("GDAL_netCDF", |
2640 | | "netCDFRasterBand::IWriteBlock( %d, %d, ...) nBand=%d", |
2641 | | nBlockXOff, nBlockYOff, nBand); |
2642 | | #endif |
2643 | |
|
2644 | 0 | int nd = 0; |
2645 | 0 | nc_inq_varndims(cdfid, nZId, &nd); |
2646 | | |
2647 | | // Locate X, Y and Z position in the array. |
2648 | |
|
2649 | 0 | size_t start[MAX_NC_DIMS]; |
2650 | 0 | memset(start, 0, sizeof(start)); |
2651 | 0 | start[nBandXPos] = static_cast<size_t>(nBlockXOff) * nBlockXSize; |
2652 | | |
2653 | | // check y order. |
2654 | 0 | if (static_cast<netCDFDataset *>(poDS)->bBottomUp) |
2655 | 0 | { |
2656 | 0 | if (nBlockYSize == 1) |
2657 | 0 | { |
2658 | 0 | start[nBandYPos] = nRasterYSize - 1 - nBlockYOff; |
2659 | 0 | } |
2660 | 0 | else |
2661 | 0 | { |
2662 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2663 | 0 | "nBlockYSize = %d, only 1 supported when " |
2664 | 0 | "writing bottom-up dataset", |
2665 | 0 | nBlockYSize); |
2666 | 0 | return CE_Failure; |
2667 | 0 | } |
2668 | 0 | } |
2669 | 0 | else |
2670 | 0 | { |
2671 | 0 | start[nBandYPos] = static_cast<size_t>(nBlockYOff) * nBlockYSize; // y |
2672 | 0 | } |
2673 | | |
2674 | 0 | size_t edge[MAX_NC_DIMS] = {}; |
2675 | |
|
2676 | 0 | edge[nBandXPos] = nBlockXSize; |
2677 | 0 | if ((start[nBandXPos] + edge[nBandXPos]) > (size_t)nRasterXSize) |
2678 | 0 | edge[nBandXPos] = nRasterXSize - start[nBandXPos]; |
2679 | 0 | edge[nBandYPos] = nBlockYSize; |
2680 | 0 | if ((start[nBandYPos] + edge[nBandYPos]) > (size_t)nRasterYSize) |
2681 | 0 | edge[nBandYPos] = nRasterYSize - start[nBandYPos]; |
2682 | |
|
2683 | 0 | if (nd == 3) |
2684 | 0 | { |
2685 | 0 | start[panBandZPos[0]] = nLevel; // z |
2686 | 0 | edge[panBandZPos[0]] = 1; |
2687 | 0 | } |
2688 | | |
2689 | | // Compute multidimention band position. |
2690 | | // |
2691 | | // BandPosition = (Total - sum(PastBandLevels) - 1)/sum(remainingLevels) |
2692 | | // if Data[2,3,4,x,y] |
2693 | | // |
2694 | | // BandPos0 = (nBand) / (3*4) |
2695 | | // BandPos1 = (nBand - (3*4)) / (4) |
2696 | | // BandPos2 = (nBand - (3*4)) % (4) |
2697 | 0 | if (nd > 3) |
2698 | 0 | { |
2699 | 0 | int Sum = -1; |
2700 | 0 | int Taken = 0; |
2701 | 0 | for (int i = 0; i < nd - 2; i++) |
2702 | 0 | { |
2703 | 0 | if (i != nd - 2 - 1) |
2704 | 0 | { |
2705 | 0 | Sum = 1; |
2706 | 0 | for (int j = i + 1; j < nd - 2; j++) |
2707 | 0 | { |
2708 | 0 | Sum *= panBandZLev[j]; |
2709 | 0 | } |
2710 | 0 | start[panBandZPos[i]] = (int)((nLevel - Taken) / Sum); |
2711 | 0 | edge[panBandZPos[i]] = 1; |
2712 | 0 | } |
2713 | 0 | else |
2714 | 0 | { |
2715 | 0 | start[panBandZPos[i]] = (int)((nLevel - Taken) % Sum); |
2716 | 0 | edge[panBandZPos[i]] = 1; |
2717 | 0 | } |
2718 | 0 | Taken += static_cast<int>(start[panBandZPos[i]]) * Sum; |
2719 | 0 | } |
2720 | 0 | } |
2721 | | |
2722 | | // Make sure we are in data mode. |
2723 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(false); |
2724 | | |
2725 | | // Copy data according to type. |
2726 | 0 | int status = 0; |
2727 | 0 | if (eDataType == GDT_Byte) |
2728 | 0 | { |
2729 | 0 | if (bSignedData) |
2730 | 0 | status = nc_put_vara_schar(cdfid, nZId, start, edge, |
2731 | 0 | static_cast<signed char *>(pImage)); |
2732 | 0 | else |
2733 | 0 | status = nc_put_vara_uchar(cdfid, nZId, start, edge, |
2734 | 0 | static_cast<unsigned char *>(pImage)); |
2735 | 0 | } |
2736 | 0 | else if (eDataType == GDT_Int8) |
2737 | 0 | { |
2738 | 0 | status = nc_put_vara_schar(cdfid, nZId, start, edge, |
2739 | 0 | static_cast<signed char *>(pImage)); |
2740 | 0 | } |
2741 | 0 | else if (nc_datatype == NC_SHORT) |
2742 | 0 | { |
2743 | 0 | status = nc_put_vara_short(cdfid, nZId, start, edge, |
2744 | 0 | static_cast<short *>(pImage)); |
2745 | 0 | } |
2746 | 0 | else if (eDataType == GDT_Int32) |
2747 | 0 | { |
2748 | 0 | status = nc_put_vara_int(cdfid, nZId, start, edge, |
2749 | 0 | static_cast<int *>(pImage)); |
2750 | 0 | } |
2751 | 0 | else if (eDataType == GDT_Float32) |
2752 | 0 | { |
2753 | 0 | status = nc_put_vara_float(cdfid, nZId, start, edge, |
2754 | 0 | static_cast<float *>(pImage)); |
2755 | 0 | } |
2756 | 0 | else if (eDataType == GDT_Float64) |
2757 | 0 | { |
2758 | 0 | status = nc_put_vara_double(cdfid, nZId, start, edge, |
2759 | 0 | static_cast<double *>(pImage)); |
2760 | 0 | } |
2761 | 0 | else if (eDataType == GDT_UInt16 && |
2762 | 0 | static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4) |
2763 | 0 | { |
2764 | 0 | status = nc_put_vara_ushort(cdfid, nZId, start, edge, |
2765 | 0 | static_cast<unsigned short *>(pImage)); |
2766 | 0 | } |
2767 | 0 | else if (eDataType == GDT_UInt32 && |
2768 | 0 | static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4) |
2769 | 0 | { |
2770 | 0 | status = nc_put_vara_uint(cdfid, nZId, start, edge, |
2771 | 0 | static_cast<unsigned int *>(pImage)); |
2772 | 0 | } |
2773 | 0 | else if (eDataType == GDT_UInt64 && |
2774 | 0 | static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4) |
2775 | 0 | { |
2776 | 0 | status = |
2777 | 0 | nc_put_vara_ulonglong(cdfid, nZId, start, edge, |
2778 | 0 | static_cast<unsigned long long *>(pImage)); |
2779 | 0 | } |
2780 | 0 | else if (eDataType == GDT_Int64 && |
2781 | 0 | static_cast<netCDFDataset *>(poDS)->eFormat == NCDF_FORMAT_NC4) |
2782 | 0 | { |
2783 | 0 | status = nc_put_vara_longlong(cdfid, nZId, start, edge, |
2784 | 0 | static_cast<long long *>(pImage)); |
2785 | 0 | } |
2786 | 0 | else |
2787 | 0 | { |
2788 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2789 | 0 | "The NetCDF driver does not support GDAL data type %d", |
2790 | 0 | eDataType); |
2791 | 0 | status = NC_EBADTYPE; |
2792 | 0 | } |
2793 | 0 | NCDF_ERR(status); |
2794 | |
|
2795 | 0 | if (status != NC_NOERR) |
2796 | 0 | { |
2797 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2798 | 0 | "netCDF scanline write failed: %s", nc_strerror(status)); |
2799 | 0 | return CE_Failure; |
2800 | 0 | } |
2801 | | |
2802 | 0 | return CE_None; |
2803 | 0 | } |
2804 | | |
2805 | | /************************************************************************/ |
2806 | | /* ==================================================================== */ |
2807 | | /* netCDFDataset */ |
2808 | | /* ==================================================================== */ |
2809 | | /************************************************************************/ |
2810 | | |
2811 | | /************************************************************************/ |
2812 | | /* netCDFDataset() */ |
2813 | | /************************************************************************/ |
2814 | | |
2815 | | netCDFDataset::netCDFDataset() |
2816 | | : |
2817 | | // Basic dataset vars. |
2818 | | #ifdef ENABLE_NCDUMP |
2819 | 399 | bFileToDestroyAtClosing(false), |
2820 | | #endif |
2821 | 399 | cdfid(-1), nSubDatasets(0), papszSubDatasets(nullptr), |
2822 | 399 | papszMetadata(nullptr), bBottomUp(true), eFormat(NCDF_FORMAT_NONE), |
2823 | 399 | bIsGdalFile(false), bIsGdalCfFile(false), pszCFProjection(nullptr), |
2824 | 399 | pszCFCoordinates(nullptr), nCFVersion(1.6), bSGSupport(false), |
2825 | 399 | eMultipleLayerBehavior(SINGLE_LAYER), logCount(0), vcdf(this, cdfid), |
2826 | 399 | GeometryScribe(vcdf, this->generateLogName()), |
2827 | 399 | FieldScribe(vcdf, this->generateLogName()), |
2828 | 399 | bufManager(CPLGetUsablePhysicalRAM() / 5), |
2829 | | |
2830 | | // projection/GT. |
2831 | 399 | nXDimID(-1), nYDimID(-1), bIsProjected(false), |
2832 | 399 | bIsGeographic(false), // Can be not projected, and also not geographic |
2833 | | // State vars. |
2834 | 399 | bDefineMode(true), bAddedGridMappingRef(false), |
2835 | | |
2836 | | // Create vars. |
2837 | 399 | papszCreationOptions(nullptr), eCompress(NCDF_COMPRESS_NONE), |
2838 | 399 | nZLevel(NCDF_DEFLATE_LEVEL), bChunking(false), nCreateMode(NC_CLOBBER), |
2839 | 399 | bSignedData(true) |
2840 | 399 | { |
2841 | 399 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
2842 | | |
2843 | | // Projection/GT. |
2844 | 399 | m_adfGeoTransform[0] = 0.0; |
2845 | 399 | m_adfGeoTransform[1] = 1.0; |
2846 | 399 | m_adfGeoTransform[2] = 0.0; |
2847 | 399 | m_adfGeoTransform[3] = 0.0; |
2848 | 399 | m_adfGeoTransform[4] = 0.0; |
2849 | 399 | m_adfGeoTransform[5] = 1.0; |
2850 | | |
2851 | | // Set buffers |
2852 | 399 | bufManager.addBuffer(&(GeometryScribe.getMemBuffer())); |
2853 | 399 | bufManager.addBuffer(&(FieldScribe.getMemBuffer())); |
2854 | 399 | } |
2855 | | |
2856 | | /************************************************************************/ |
2857 | | /* ~netCDFDataset() */ |
2858 | | /************************************************************************/ |
2859 | | |
2860 | | netCDFDataset::~netCDFDataset() |
2861 | | |
2862 | 399 | { |
2863 | 399 | netCDFDataset::Close(); |
2864 | 399 | } |
2865 | | |
2866 | | /************************************************************************/ |
2867 | | /* Close() */ |
2868 | | /************************************************************************/ |
2869 | | |
2870 | | CPLErr netCDFDataset::Close() |
2871 | 422 | { |
2872 | 422 | CPLErr eErr = CE_None; |
2873 | 422 | if (nOpenFlags != OPEN_FLAGS_CLOSED) |
2874 | 399 | { |
2875 | 399 | CPLMutexHolderD(&hNCMutex); |
2876 | | |
2877 | | #ifdef NCDF_DEBUG |
2878 | | CPLDebug("GDAL_netCDF", |
2879 | | "netCDFDataset::~netCDFDataset(), cdfid=%d filename=%s", cdfid, |
2880 | | osFilename.c_str()); |
2881 | | #endif |
2882 | | |
2883 | | // Write data related to geotransform |
2884 | 399 | if (GetAccess() == GA_Update && !m_bAddedProjectionVarsData && |
2885 | 399 | (m_bHasProjection || m_bHasGeoTransform)) |
2886 | 0 | { |
2887 | | // Ensure projection is written if GeoTransform OR Projection are |
2888 | | // missing. |
2889 | 0 | if (!m_bAddedProjectionVarsDefs) |
2890 | 0 | { |
2891 | 0 | AddProjectionVars(true, nullptr, nullptr); |
2892 | 0 | } |
2893 | 0 | AddProjectionVars(false, nullptr, nullptr); |
2894 | 0 | } |
2895 | | |
2896 | 399 | if (netCDFDataset::FlushCache(true) != CE_None) |
2897 | 0 | eErr = CE_Failure; |
2898 | | |
2899 | 399 | if (GetAccess() == GA_Update && !SGCommitPendingTransaction()) |
2900 | 0 | eErr = CE_Failure; |
2901 | | |
2902 | 399 | for (size_t i = 0; i < apoVectorDatasets.size(); i++) |
2903 | 0 | delete apoVectorDatasets[i]; |
2904 | | |
2905 | | // Make sure projection variable is written to band variable. |
2906 | 399 | if (GetAccess() == GA_Update && !bAddedGridMappingRef) |
2907 | 0 | { |
2908 | 0 | if (!AddGridMappingRef()) |
2909 | 0 | eErr = CE_Failure; |
2910 | 0 | } |
2911 | | |
2912 | 399 | CSLDestroy(papszMetadata); |
2913 | 399 | CSLDestroy(papszSubDatasets); |
2914 | 399 | CSLDestroy(papszCreationOptions); |
2915 | | |
2916 | 399 | CPLFree(pszCFProjection); |
2917 | | |
2918 | 399 | if (cdfid > 0) |
2919 | 399 | { |
2920 | | #ifdef NCDF_DEBUG |
2921 | | CPLDebug("GDAL_netCDF", "calling nc_close( %d)", cdfid); |
2922 | | #endif |
2923 | 399 | int status = GDAL_nc_close(cdfid); |
2924 | 399 | #ifdef ENABLE_UFFD |
2925 | 399 | NETCDF_UFFD_UNMAP(pCtx); |
2926 | 399 | #endif |
2927 | 399 | NCDF_ERR(status); |
2928 | 399 | if (status != NC_NOERR) |
2929 | 0 | eErr = CE_Failure; |
2930 | 399 | } |
2931 | | |
2932 | 399 | if (fpVSIMEM) |
2933 | 0 | VSIFCloseL(fpVSIMEM); |
2934 | | |
2935 | 399 | #ifdef ENABLE_NCDUMP |
2936 | 399 | if (bFileToDestroyAtClosing) |
2937 | 0 | VSIUnlink(osFilename); |
2938 | 399 | #endif |
2939 | | |
2940 | 399 | if (GDALPamDataset::Close() != CE_None) |
2941 | 0 | eErr = CE_Failure; |
2942 | 399 | } |
2943 | 422 | return eErr; |
2944 | 422 | } |
2945 | | |
2946 | | /************************************************************************/ |
2947 | | /* SetDefineMode() */ |
2948 | | /************************************************************************/ |
2949 | | bool netCDFDataset::SetDefineMode(bool bNewDefineMode) |
2950 | 5.16k | { |
2951 | | // Do nothing if already in new define mode |
2952 | | // or if dataset is in read-only mode or if dataset is true NC4 dataset. |
2953 | 5.16k | if (bDefineMode == bNewDefineMode || GetAccess() == GA_ReadOnly || |
2954 | 5.16k | eFormat == NCDF_FORMAT_NC4) |
2955 | 5.16k | return true; |
2956 | | |
2957 | 0 | CPLDebug("GDAL_netCDF", "SetDefineMode(%d) old=%d", |
2958 | 0 | static_cast<int>(bNewDefineMode), static_cast<int>(bDefineMode)); |
2959 | |
|
2960 | 0 | bDefineMode = bNewDefineMode; |
2961 | |
|
2962 | 0 | int status; |
2963 | 0 | if (bDefineMode) |
2964 | 0 | status = nc_redef(cdfid); |
2965 | 0 | else |
2966 | 0 | status = nc_enddef(cdfid); |
2967 | |
|
2968 | 0 | NCDF_ERR(status); |
2969 | 0 | return status == NC_NOERR; |
2970 | 5.16k | } |
2971 | | |
2972 | | /************************************************************************/ |
2973 | | /* GetMetadataDomainList() */ |
2974 | | /************************************************************************/ |
2975 | | |
2976 | | char **netCDFDataset::GetMetadataDomainList() |
2977 | 0 | { |
2978 | 0 | char **papszDomains = BuildMetadataDomainList( |
2979 | 0 | GDALDataset::GetMetadataDomainList(), TRUE, "SUBDATASETS", nullptr); |
2980 | 0 | for (const auto &kv : m_oMapDomainToJSon) |
2981 | 0 | papszDomains = CSLAddString(papszDomains, ("json:" + kv.first).c_str()); |
2982 | 0 | return papszDomains; |
2983 | 0 | } |
2984 | | |
2985 | | /************************************************************************/ |
2986 | | /* GetMetadata() */ |
2987 | | /************************************************************************/ |
2988 | | char **netCDFDataset::GetMetadata(const char *pszDomain) |
2989 | 0 | { |
2990 | 0 | if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS")) |
2991 | 0 | return papszSubDatasets; |
2992 | | |
2993 | 0 | if (pszDomain != nullptr && STARTS_WITH(pszDomain, "json:")) |
2994 | 0 | { |
2995 | 0 | auto iter = m_oMapDomainToJSon.find(pszDomain + strlen("json:")); |
2996 | 0 | if (iter != m_oMapDomainToJSon.end()) |
2997 | 0 | return iter->second.List(); |
2998 | 0 | } |
2999 | | |
3000 | 0 | return GDALDataset::GetMetadata(pszDomain); |
3001 | 0 | } |
3002 | | |
3003 | | /************************************************************************/ |
3004 | | /* SetMetadataItem() */ |
3005 | | /************************************************************************/ |
3006 | | |
3007 | | CPLErr netCDFDataset::SetMetadataItem(const char *pszName, const char *pszValue, |
3008 | | const char *pszDomain) |
3009 | 0 | { |
3010 | 0 | if (GetAccess() == GA_Update && |
3011 | 0 | (pszDomain == nullptr || pszDomain[0] == '\0') && pszValue != nullptr) |
3012 | 0 | { |
3013 | 0 | std::string osName(pszName); |
3014 | | |
3015 | | // Same logic as in CopyMetadata() |
3016 | 0 | if (STARTS_WITH(osName.c_str(), "NC_GLOBAL#")) |
3017 | 0 | osName = osName.substr(strlen("NC_GLOBAL#")); |
3018 | 0 | else if (strchr(osName.c_str(), '#') == nullptr) |
3019 | 0 | osName = "GDAL_" + osName; |
3020 | |
|
3021 | 0 | if (STARTS_WITH(osName.c_str(), "NETCDF_DIM_") || |
3022 | 0 | strchr(osName.c_str(), '#') != nullptr) |
3023 | 0 | { |
3024 | | // do nothing |
3025 | 0 | return CE_None; |
3026 | 0 | } |
3027 | 0 | else |
3028 | 0 | { |
3029 | 0 | SetDefineMode(true); |
3030 | |
|
3031 | 0 | if (!NCDFPutAttr(cdfid, NC_GLOBAL, osName.c_str(), pszValue)) |
3032 | 0 | return CE_Failure; |
3033 | 0 | } |
3034 | 0 | } |
3035 | | |
3036 | 0 | return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain); |
3037 | 0 | } |
3038 | | |
3039 | | /************************************************************************/ |
3040 | | /* SetMetadata() */ |
3041 | | /************************************************************************/ |
3042 | | |
3043 | | CPLErr netCDFDataset::SetMetadata(char **papszMD, const char *pszDomain) |
3044 | 0 | { |
3045 | 0 | if (GetAccess() == GA_Update && |
3046 | 0 | (pszDomain == nullptr || pszDomain[0] == '\0')) |
3047 | 0 | { |
3048 | | // We don't handle metadata item removal for now |
3049 | 0 | for (const char *const *papszIter = papszMD; papszIter && *papszIter; |
3050 | 0 | ++papszIter) |
3051 | 0 | { |
3052 | 0 | char *pszName = nullptr; |
3053 | 0 | const char *pszValue = CPLParseNameValue(*papszIter, &pszName); |
3054 | 0 | if (pszName && pszValue) |
3055 | 0 | SetMetadataItem(pszName, pszValue); |
3056 | 0 | CPLFree(pszName); |
3057 | 0 | } |
3058 | 0 | return CE_None; |
3059 | 0 | } |
3060 | 0 | return GDALPamDataset::SetMetadata(papszMD, pszDomain); |
3061 | 0 | } |
3062 | | |
3063 | | /************************************************************************/ |
3064 | | /* GetSpatialRef() */ |
3065 | | /************************************************************************/ |
3066 | | |
3067 | | const OGRSpatialReference *netCDFDataset::GetSpatialRef() const |
3068 | 0 | { |
3069 | 0 | if (m_bHasProjection) |
3070 | 0 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
3071 | | |
3072 | 0 | return GDALPamDataset::GetSpatialRef(); |
3073 | 0 | } |
3074 | | |
3075 | | /************************************************************************/ |
3076 | | /* FetchCopyParam() */ |
3077 | | /************************************************************************/ |
3078 | | |
3079 | | double netCDFDataset::FetchCopyParam(const char *pszGridMappingValue, |
3080 | | const char *pszParam, double dfDefault, |
3081 | | bool *pbFound) |
3082 | | |
3083 | 0 | { |
3084 | 0 | char *pszTemp = |
3085 | 0 | CPLStrdup(CPLSPrintf("%s#%s", pszGridMappingValue, pszParam)); |
3086 | 0 | const char *pszValue = CSLFetchNameValue(papszMetadata, pszTemp); |
3087 | 0 | CPLFree(pszTemp); |
3088 | |
|
3089 | 0 | if (pbFound) |
3090 | 0 | { |
3091 | 0 | *pbFound = (pszValue != nullptr); |
3092 | 0 | } |
3093 | |
|
3094 | 0 | if (pszValue) |
3095 | 0 | { |
3096 | 0 | return CPLAtofM(pszValue); |
3097 | 0 | } |
3098 | | |
3099 | 0 | return dfDefault; |
3100 | 0 | } |
3101 | | |
3102 | | /************************************************************************/ |
3103 | | /* FetchStandardParallels() */ |
3104 | | /************************************************************************/ |
3105 | | |
3106 | | std::vector<std::string> |
3107 | | netCDFDataset::FetchStandardParallels(const char *pszGridMappingValue) |
3108 | 0 | { |
3109 | | // cf-1.0 tags |
3110 | 0 | const char *pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL); |
3111 | |
|
3112 | 0 | std::vector<std::string> ret; |
3113 | 0 | if (pszValue != nullptr) |
3114 | 0 | { |
3115 | 0 | CPLStringList aosValues; |
3116 | 0 | if (pszValue[0] != '{' && |
3117 | 0 | CPLString(pszValue).Trim().find(' ') != std::string::npos) |
3118 | 0 | { |
3119 | | // Some files like |
3120 | | // ftp://data.knmi.nl/download/KNW-NetCDF-3D/1.0/noversion/2013/11/14/KNW-1.0_H37-ERA_NL_20131114.nc |
3121 | | // do not use standard formatting for arrays, but just space |
3122 | | // separated syntax |
3123 | 0 | aosValues = CSLTokenizeString2(pszValue, " ", 0); |
3124 | 0 | } |
3125 | 0 | else |
3126 | 0 | { |
3127 | 0 | aosValues = NCDFTokenizeArray(pszValue); |
3128 | 0 | } |
3129 | 0 | for (int i = 0; i < aosValues.size(); i++) |
3130 | 0 | { |
3131 | 0 | ret.push_back(aosValues[i]); |
3132 | 0 | } |
3133 | 0 | } |
3134 | | // Try gdal tags. |
3135 | 0 | else |
3136 | 0 | { |
3137 | 0 | pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_1); |
3138 | |
|
3139 | 0 | if (pszValue != nullptr) |
3140 | 0 | ret.push_back(pszValue); |
3141 | |
|
3142 | 0 | pszValue = FetchAttr(pszGridMappingValue, CF_PP_STD_PARALLEL_2); |
3143 | |
|
3144 | 0 | if (pszValue != nullptr) |
3145 | 0 | ret.push_back(pszValue); |
3146 | 0 | } |
3147 | |
|
3148 | 0 | return ret; |
3149 | 0 | } |
3150 | | |
3151 | | /************************************************************************/ |
3152 | | /* FetchAttr() */ |
3153 | | /************************************************************************/ |
3154 | | |
3155 | | const char *netCDFDataset::FetchAttr(const char *pszVarFullName, |
3156 | | const char *pszAttr) |
3157 | | |
3158 | 115 | { |
3159 | 115 | char *pszKey = CPLStrdup(CPLSPrintf("%s#%s", pszVarFullName, pszAttr)); |
3160 | 115 | const char *pszValue = CSLFetchNameValue(papszMetadata, pszKey); |
3161 | 115 | CPLFree(pszKey); |
3162 | 115 | return pszValue; |
3163 | 115 | } |
3164 | | |
3165 | | const char *netCDFDataset::FetchAttr(int nGroupId, int nVarId, |
3166 | | const char *pszAttr) |
3167 | | |
3168 | 69 | { |
3169 | 69 | char *pszVarFullName = nullptr; |
3170 | 69 | NCDFGetVarFullName(nGroupId, nVarId, &pszVarFullName); |
3171 | 69 | const char *pszValue = FetchAttr(pszVarFullName, pszAttr); |
3172 | 69 | CPLFree(pszVarFullName); |
3173 | 69 | return pszValue; |
3174 | 69 | } |
3175 | | |
3176 | | /************************************************************************/ |
3177 | | /* IsDifferenceBelow() */ |
3178 | | /************************************************************************/ |
3179 | | |
3180 | | static bool IsDifferenceBelow(double dfA, double dfB, double dfError) |
3181 | 0 | { |
3182 | 0 | const double dfAbsDiff = fabs(dfA - dfB); |
3183 | 0 | return dfAbsDiff <= dfError; |
3184 | 0 | } |
3185 | | |
3186 | | /************************************************************************/ |
3187 | | /* SetProjectionFromVar() */ |
3188 | | /************************************************************************/ |
3189 | | void netCDFDataset::SetProjectionFromVar( |
3190 | | int nGroupId, int nVarId, bool bReadSRSOnly, const char *pszGivenGM, |
3191 | | std::string *returnProjStr, nccfdriver::SGeometry_Reader *sg, |
3192 | | std::vector<std::string> *paosRemovedMDItems) |
3193 | 23 | { |
3194 | 23 | bool bGotGeogCS = false; |
3195 | 23 | bool bGotCfSRS = false; |
3196 | 23 | bool bGotCfWktSRS = false; |
3197 | 23 | bool bGotGdalSRS = false; |
3198 | 23 | bool bGotCfGT = false; |
3199 | 23 | bool bGotGdalGT = false; |
3200 | | |
3201 | | // These values from CF metadata. |
3202 | 23 | OGRSpatialReference oSRS; |
3203 | 23 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
3204 | 23 | size_t xdim = nRasterXSize; |
3205 | 23 | size_t ydim = nRasterYSize; |
3206 | | |
3207 | | // These values from GDAL metadata. |
3208 | 23 | const char *pszWKT = nullptr; |
3209 | 23 | const char *pszGeoTransform = nullptr; |
3210 | | |
3211 | 23 | netCDFDataset *poDS = this; // Perhaps this should be removed for clarity. |
3212 | | |
3213 | 23 | CPLDebug("GDAL_netCDF", "\n=====\nSetProjectionFromVar( %d, %d)", nGroupId, |
3214 | 23 | nVarId); |
3215 | | |
3216 | | // Get x/y range information. |
3217 | | |
3218 | | // Temp variables to use in SetGeoTransform() and SetProjection(). |
3219 | 23 | double adfTempGeoTransform[6] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; |
3220 | | |
3221 | | // Look for grid_mapping metadata. |
3222 | 23 | const char *pszValue = pszGivenGM; |
3223 | 23 | CPLString osTmpGridMapping; // let is in this outer scope as pszValue may |
3224 | | // point to it |
3225 | 23 | if (pszValue == nullptr) |
3226 | 23 | { |
3227 | 23 | pszValue = FetchAttr(nGroupId, nVarId, CF_GRD_MAPPING); |
3228 | 23 | if (pszValue && strchr(pszValue, ':') && strchr(pszValue, ' ')) |
3229 | 0 | { |
3230 | | // Expanded form of grid_mapping |
3231 | | // e.g. "crsOSGB: x y crsWGS84: lat lon" |
3232 | | // Pickup the grid_mapping whose coordinates are dimensions of the |
3233 | | // variable |
3234 | 0 | CPLStringList aosTokens(CSLTokenizeString2(pszValue, " ", 0)); |
3235 | 0 | if ((aosTokens.size() % 3) == 0) |
3236 | 0 | { |
3237 | 0 | for (int i = 0; i < aosTokens.size() / 3; i++) |
3238 | 0 | { |
3239 | 0 | if (CSLFindString(poDS->papszDimName, |
3240 | 0 | aosTokens[3 * i + 1]) >= 0 && |
3241 | 0 | CSLFindString(poDS->papszDimName, |
3242 | 0 | aosTokens[3 * i + 2]) >= 0) |
3243 | 0 | { |
3244 | 0 | osTmpGridMapping = aosTokens[3 * i]; |
3245 | 0 | if (!osTmpGridMapping.empty() && |
3246 | 0 | osTmpGridMapping.back() == ':') |
3247 | 0 | { |
3248 | 0 | osTmpGridMapping.resize(osTmpGridMapping.size() - |
3249 | 0 | 1); |
3250 | 0 | } |
3251 | 0 | pszValue = osTmpGridMapping.c_str(); |
3252 | 0 | break; |
3253 | 0 | } |
3254 | 0 | } |
3255 | 0 | } |
3256 | 0 | } |
3257 | 23 | } |
3258 | 23 | char *pszGridMappingValue = CPLStrdup(pszValue ? pszValue : ""); |
3259 | | |
3260 | 23 | if (!EQUAL(pszGridMappingValue, "")) |
3261 | 0 | { |
3262 | | // Read grid_mapping metadata. |
3263 | 0 | int nProjGroupID = -1; |
3264 | 0 | int nProjVarID = -1; |
3265 | 0 | if (NCDFResolveVar(nGroupId, pszGridMappingValue, &nProjGroupID, |
3266 | 0 | &nProjVarID) == CE_None) |
3267 | 0 | { |
3268 | 0 | poDS->ReadAttributes(nProjGroupID, nProjVarID); |
3269 | | |
3270 | | // Look for GDAL spatial_ref and GeoTransform within grid_mapping. |
3271 | 0 | CPLFree(pszGridMappingValue); |
3272 | 0 | pszGridMappingValue = nullptr; |
3273 | 0 | NCDFGetVarFullName(nProjGroupID, nProjVarID, &pszGridMappingValue); |
3274 | 0 | if (pszGridMappingValue) |
3275 | 0 | { |
3276 | 0 | CPLDebug("GDAL_netCDF", "got grid_mapping %s", |
3277 | 0 | pszGridMappingValue); |
3278 | 0 | pszWKT = FetchAttr(pszGridMappingValue, NCDF_SPATIAL_REF); |
3279 | 0 | if (!pszWKT) |
3280 | 0 | { |
3281 | 0 | pszWKT = FetchAttr(pszGridMappingValue, NCDF_CRS_WKT); |
3282 | 0 | } |
3283 | 0 | else |
3284 | 0 | { |
3285 | 0 | bGotGdalSRS = true; |
3286 | 0 | CPLDebug("GDAL_netCDF", "setting WKT from GDAL"); |
3287 | 0 | } |
3288 | 0 | if (pszWKT) |
3289 | 0 | { |
3290 | 0 | if (!bGotGdalSRS) |
3291 | 0 | { |
3292 | 0 | bGotCfWktSRS = true; |
3293 | 0 | CPLDebug("GDAL_netCDF", "setting WKT from CF"); |
3294 | 0 | } |
3295 | 0 | if (returnProjStr != nullptr) |
3296 | 0 | { |
3297 | 0 | (*returnProjStr) = std::string(pszWKT); |
3298 | 0 | } |
3299 | 0 | else |
3300 | 0 | { |
3301 | 0 | m_bAddedProjectionVarsDefs = true; |
3302 | 0 | m_bAddedProjectionVarsData = true; |
3303 | 0 | OGRSpatialReference oSRSTmp; |
3304 | 0 | oSRSTmp.SetAxisMappingStrategy( |
3305 | 0 | OAMS_TRADITIONAL_GIS_ORDER); |
3306 | 0 | oSRSTmp.importFromWkt(pszWKT); |
3307 | 0 | SetSpatialRefNoUpdate(&oSRSTmp); |
3308 | 0 | } |
3309 | 0 | pszGeoTransform = |
3310 | 0 | FetchAttr(pszGridMappingValue, NCDF_GEOTRANSFORM); |
3311 | 0 | } |
3312 | 0 | } |
3313 | 0 | else |
3314 | 0 | { |
3315 | 0 | pszGridMappingValue = CPLStrdup(""); |
3316 | 0 | } |
3317 | 0 | } |
3318 | 0 | } |
3319 | | |
3320 | | // Get information about the file. |
3321 | | // |
3322 | | // Was this file created by the GDAL netcdf driver? |
3323 | | // Was this file created by the newer (CF-conformant) driver? |
3324 | | // |
3325 | | // 1) If GDAL netcdf metadata is set, and version >= 1.9, |
3326 | | // it was created with the new driver |
3327 | | // 2) Else, if spatial_ref and GeoTransform are present in the |
3328 | | // grid_mapping variable, it was created by the old driver |
3329 | 23 | pszValue = FetchAttr("NC_GLOBAL", "GDAL"); |
3330 | | |
3331 | 23 | if (pszValue && NCDFIsGDALVersionGTE(pszValue, 1900)) |
3332 | 0 | { |
3333 | 0 | bIsGdalFile = true; |
3334 | 0 | bIsGdalCfFile = true; |
3335 | 0 | } |
3336 | 23 | else if (pszWKT != nullptr && pszGeoTransform != nullptr) |
3337 | 0 | { |
3338 | 0 | bIsGdalFile = true; |
3339 | 0 | bIsGdalCfFile = false; |
3340 | 0 | } |
3341 | | |
3342 | | // Set default bottom-up default value. |
3343 | | // Y axis dimension and absence of GT can modify this value. |
3344 | | // Override with Config option GDAL_NETCDF_BOTTOMUP. |
3345 | | |
3346 | | // New driver is bottom-up by default. |
3347 | 23 | if ((bIsGdalFile && !bIsGdalCfFile) || bSwitchedXY) |
3348 | 0 | poDS->bBottomUp = false; |
3349 | 23 | else |
3350 | 23 | poDS->bBottomUp = true; |
3351 | | |
3352 | 23 | CPLDebug("GDAL_netCDF", |
3353 | 23 | "bIsGdalFile=%d bIsGdalCfFile=%d bSwitchedXY=%d bBottomUp=%d", |
3354 | 23 | static_cast<int>(bIsGdalFile), static_cast<int>(bIsGdalCfFile), |
3355 | 23 | static_cast<int>(bSwitchedXY), static_cast<int>(bBottomUp)); |
3356 | | |
3357 | | // Read projection coordinates. |
3358 | | |
3359 | 23 | int nGroupDimXID = -1; |
3360 | 23 | int nVarDimXID = -1; |
3361 | 23 | int nGroupDimYID = -1; |
3362 | 23 | int nVarDimYID = -1; |
3363 | 23 | if (sg != nullptr) |
3364 | 0 | { |
3365 | 0 | nGroupDimXID = sg->get_ncID(); |
3366 | 0 | nGroupDimYID = sg->get_ncID(); |
3367 | 0 | nVarDimXID = sg->getNodeCoordVars()[0]; |
3368 | 0 | nVarDimYID = sg->getNodeCoordVars()[1]; |
3369 | 0 | } |
3370 | | |
3371 | 23 | if (!bReadSRSOnly) |
3372 | 0 | { |
3373 | 0 | NCDFResolveVar(nGroupId, poDS->papszDimName[nXDimID], &nGroupDimXID, |
3374 | 0 | &nVarDimXID); |
3375 | 0 | NCDFResolveVar(nGroupId, poDS->papszDimName[nYDimID], &nGroupDimYID, |
3376 | 0 | &nVarDimYID); |
3377 | | // TODO: if above resolving fails we should also search for coordinate |
3378 | | // variables without same name than dimension using the same resolving |
3379 | | // logic. This should handle for example NASA Ocean Color L2 products. |
3380 | |
|
3381 | 0 | const bool bIgnoreXYAxisNameChecks = |
3382 | 0 | CPLTestBool(CSLFetchNameValueDef( |
3383 | 0 | papszOpenOptions, "IGNORE_XY_AXIS_NAME_CHECKS", |
3384 | 0 | CPLGetConfigOption("GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS", |
3385 | 0 | "NO"))) || |
3386 | | // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a res |
3387 | | // and transform attributes |
3388 | 0 | (FetchAttr(nGroupId, nVarId, "res") != nullptr && |
3389 | 0 | FetchAttr(nGroupId, nVarId, "transform") != nullptr) || |
3390 | 0 | FetchAttr(nGroupId, NC_GLOBAL, "GMT_version") != nullptr; |
3391 | | |
3392 | | // Check that they are 1D or 2D variables |
3393 | 0 | if (nVarDimXID >= 0) |
3394 | 0 | { |
3395 | 0 | int ndims = -1; |
3396 | 0 | nc_inq_varndims(nGroupId, nVarDimXID, &ndims); |
3397 | 0 | if (ndims == 0 || ndims > 2) |
3398 | 0 | nVarDimXID = -1; |
3399 | 0 | else if (!bIgnoreXYAxisNameChecks) |
3400 | 0 | { |
3401 | 0 | if (!NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) && |
3402 | 0 | !NCDFIsVarProjectionX(nGroupId, nVarDimXID, nullptr) && |
3403 | | // In case of inversion of X/Y |
3404 | 0 | !NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr) && |
3405 | 0 | !NCDFIsVarProjectionY(nGroupId, nVarDimXID, nullptr)) |
3406 | 0 | { |
3407 | 0 | char szVarNameX[NC_MAX_NAME + 1]; |
3408 | 0 | CPL_IGNORE_RET_VAL( |
3409 | 0 | nc_inq_varname(nGroupId, nVarDimXID, szVarNameX)); |
3410 | 0 | if (!(ndims == 1 && |
3411 | 0 | (EQUAL(szVarNameX, CF_LONGITUDE_STD_NAME) || |
3412 | 0 | EQUAL(szVarNameX, CF_LONGITUDE_VAR_NAME)))) |
3413 | 0 | { |
3414 | 0 | CPLDebug( |
3415 | 0 | "netCDF", |
3416 | 0 | "Georeferencing ignored due to non-specific " |
3417 | 0 | "enough X axis name. " |
3418 | 0 | "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES " |
3419 | 0 | "as configuration option to bypass this check"); |
3420 | 0 | nVarDimXID = -1; |
3421 | 0 | } |
3422 | 0 | } |
3423 | 0 | } |
3424 | 0 | } |
3425 | |
|
3426 | 0 | if (nVarDimYID >= 0) |
3427 | 0 | { |
3428 | 0 | int ndims = -1; |
3429 | 0 | nc_inq_varndims(nGroupId, nVarDimYID, &ndims); |
3430 | 0 | if (ndims == 0 || ndims > 2) |
3431 | 0 | nVarDimYID = -1; |
3432 | 0 | else if (!bIgnoreXYAxisNameChecks) |
3433 | 0 | { |
3434 | 0 | if (!NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr) && |
3435 | 0 | !NCDFIsVarProjectionY(nGroupId, nVarDimYID, nullptr) && |
3436 | | // In case of inversion of X/Y |
3437 | 0 | !NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) && |
3438 | 0 | !NCDFIsVarProjectionX(nGroupId, nVarDimYID, nullptr)) |
3439 | 0 | { |
3440 | 0 | char szVarNameY[NC_MAX_NAME + 1]; |
3441 | 0 | CPL_IGNORE_RET_VAL( |
3442 | 0 | nc_inq_varname(nGroupId, nVarDimYID, szVarNameY)); |
3443 | 0 | if (!(ndims == 1 && |
3444 | 0 | (EQUAL(szVarNameY, CF_LATITUDE_STD_NAME) || |
3445 | 0 | EQUAL(szVarNameY, CF_LATITUDE_VAR_NAME)))) |
3446 | 0 | { |
3447 | 0 | CPLDebug( |
3448 | 0 | "netCDF", |
3449 | 0 | "Georeferencing ignored due to non-specific " |
3450 | 0 | "enough Y axis name. " |
3451 | 0 | "Set GDAL_NETCDF_IGNORE_XY_AXIS_NAME_CHECKS=YES " |
3452 | 0 | "as configuration option to bypass this check"); |
3453 | 0 | nVarDimYID = -1; |
3454 | 0 | } |
3455 | 0 | } |
3456 | 0 | } |
3457 | 0 | } |
3458 | |
|
3459 | 0 | if ((nVarDimXID >= 0 && xdim == 1) || (nVarDimXID >= 0 && ydim == 1)) |
3460 | 0 | { |
3461 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
3462 | 0 | "1-pixel width/height files not supported, " |
3463 | 0 | "xdim: %ld ydim: %ld", |
3464 | 0 | static_cast<long>(xdim), static_cast<long>(ydim)); |
3465 | 0 | nVarDimXID = -1; |
3466 | 0 | nVarDimYID = -1; |
3467 | 0 | } |
3468 | 0 | } |
3469 | | |
3470 | 23 | const char *pszUnits = nullptr; |
3471 | 23 | if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0) |
3472 | 0 | { |
3473 | 0 | const char *pszUnitsX = FetchAttr(nGroupDimXID, nVarDimXID, "units"); |
3474 | 0 | const char *pszUnitsY = FetchAttr(nGroupDimYID, nVarDimYID, "units"); |
3475 | | // Normalize degrees_east/degrees_north to degrees |
3476 | | // Cf https://github.com/OSGeo/gdal/issues/11009 |
3477 | 0 | if (pszUnitsX && EQUAL(pszUnitsX, "degrees_east")) |
3478 | 0 | pszUnitsX = "degrees"; |
3479 | 0 | if (pszUnitsY && EQUAL(pszUnitsY, "degrees_north")) |
3480 | 0 | pszUnitsY = "degrees"; |
3481 | |
|
3482 | 0 | if (pszUnitsX && pszUnitsY) |
3483 | 0 | { |
3484 | 0 | if (EQUAL(pszUnitsX, pszUnitsY)) |
3485 | 0 | pszUnits = pszUnitsX; |
3486 | 0 | else if (!pszWKT && !EQUAL(pszGridMappingValue, "")) |
3487 | 0 | { |
3488 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3489 | 0 | "X axis unit (%s) is different from Y axis " |
3490 | 0 | "unit (%s). SRS will ignore axis unit and be " |
3491 | 0 | "likely wrong.", |
3492 | 0 | pszUnitsX, pszUnitsY); |
3493 | 0 | } |
3494 | 0 | } |
3495 | 0 | else if (pszUnitsX && !pszWKT && !EQUAL(pszGridMappingValue, "")) |
3496 | 0 | { |
3497 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3498 | 0 | "X axis unit is defined, but not Y one ." |
3499 | 0 | "SRS will ignore axis unit and be likely wrong."); |
3500 | 0 | } |
3501 | 0 | else if (pszUnitsY && !pszWKT && !EQUAL(pszGridMappingValue, "")) |
3502 | 0 | { |
3503 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3504 | 0 | "Y axis unit is defined, but not X one ." |
3505 | 0 | "SRS will ignore axis unit and be likely wrong."); |
3506 | 0 | } |
3507 | 0 | } |
3508 | | |
3509 | 23 | if (!pszWKT && !EQUAL(pszGridMappingValue, "")) |
3510 | 0 | { |
3511 | 0 | CPLStringList aosGridMappingKeyValues; |
3512 | 0 | const size_t nLenGridMappingValue = strlen(pszGridMappingValue); |
3513 | 0 | for (const char *const *papszIter = papszMetadata; |
3514 | 0 | papszIter && *papszIter; ++papszIter) |
3515 | 0 | { |
3516 | 0 | if (STARTS_WITH(*papszIter, pszGridMappingValue) && |
3517 | 0 | (*papszIter)[nLenGridMappingValue] == '#') |
3518 | 0 | { |
3519 | 0 | char *pszKey = nullptr; |
3520 | 0 | pszValue = CPLParseNameValue( |
3521 | 0 | *papszIter + nLenGridMappingValue + 1, &pszKey); |
3522 | 0 | if (pszKey && pszValue) |
3523 | 0 | aosGridMappingKeyValues.SetNameValue(pszKey, pszValue); |
3524 | 0 | CPLFree(pszKey); |
3525 | 0 | } |
3526 | 0 | } |
3527 | |
|
3528 | 0 | bGotGeogCS = aosGridMappingKeyValues.FetchNameValue( |
3529 | 0 | CF_PP_SEMI_MAJOR_AXIS) != nullptr; |
3530 | |
|
3531 | 0 | oSRS.importFromCF1(aosGridMappingKeyValues.List(), pszUnits); |
3532 | 0 | bGotCfSRS = oSRS.IsGeographic() || oSRS.IsProjected(); |
3533 | 0 | } |
3534 | 23 | else |
3535 | 23 | { |
3536 | | // Dataset from https://github.com/OSGeo/gdal/issues/4075 has a "crs" |
3537 | | // attribute hold on the variable of interest that contains a PROJ.4 |
3538 | | // string |
3539 | 23 | pszValue = FetchAttr(nGroupId, nVarId, "crs"); |
3540 | 23 | if (pszValue && |
3541 | 23 | (strstr(pszValue, "+proj=") != nullptr || |
3542 | 0 | strstr(pszValue, "GEOGCS") != nullptr || |
3543 | 0 | strstr(pszValue, "PROJCS") != nullptr || |
3544 | 0 | strstr(pszValue, "EPSG:") != nullptr) && |
3545 | 23 | oSRS.SetFromUserInput(pszValue) == OGRERR_NONE) |
3546 | 0 | { |
3547 | 0 | bGotCfSRS = true; |
3548 | 0 | } |
3549 | 23 | } |
3550 | | |
3551 | | // Set Projection from CF. |
3552 | 23 | double dfLinearUnitsConvFactor = 1.0; |
3553 | 23 | if ((bGotGeogCS || bGotCfSRS)) |
3554 | 0 | { |
3555 | 0 | if ((nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && ydim > 0) |
3556 | 0 | { |
3557 | | // Set SRS Units. |
3558 | | |
3559 | | // Check units for x and y. |
3560 | 0 | if (oSRS.IsProjected()) |
3561 | 0 | { |
3562 | 0 | dfLinearUnitsConvFactor = oSRS.GetLinearUnits(nullptr); |
3563 | | |
3564 | | // If the user doesn't ask to preserve the axis unit, |
3565 | | // then normalize to metre |
3566 | 0 | if (dfLinearUnitsConvFactor != 1.0 && |
3567 | 0 | !CPLFetchBool(GetOpenOptions(), "PRESERVE_AXIS_UNIT_IN_CRS", |
3568 | 0 | false)) |
3569 | 0 | { |
3570 | 0 | oSRS.SetLinearUnits("metre", 1.0); |
3571 | 0 | oSRS.SetAuthority("PROJCS|UNIT", "EPSG", 9001); |
3572 | 0 | } |
3573 | 0 | else |
3574 | 0 | { |
3575 | 0 | dfLinearUnitsConvFactor = 1.0; |
3576 | 0 | } |
3577 | 0 | } |
3578 | 0 | } |
3579 | | |
3580 | | // Set projection. |
3581 | 0 | char *pszTempProjection = nullptr; |
3582 | 0 | oSRS.exportToWkt(&pszTempProjection); |
3583 | 0 | if (pszTempProjection) |
3584 | 0 | { |
3585 | 0 | CPLDebug("GDAL_netCDF", "setting WKT from CF"); |
3586 | 0 | if (returnProjStr != nullptr) |
3587 | 0 | { |
3588 | 0 | (*returnProjStr) = std::string(pszTempProjection); |
3589 | 0 | } |
3590 | 0 | else |
3591 | 0 | { |
3592 | 0 | m_bAddedProjectionVarsDefs = true; |
3593 | 0 | m_bAddedProjectionVarsData = true; |
3594 | 0 | SetSpatialRefNoUpdate(&oSRS); |
3595 | 0 | } |
3596 | 0 | } |
3597 | 0 | CPLFree(pszTempProjection); |
3598 | 0 | } |
3599 | | |
3600 | 23 | if (!bReadSRSOnly && (nVarDimXID != -1) && (nVarDimYID != -1) && xdim > 0 && |
3601 | 23 | ydim > 0) |
3602 | 0 | { |
3603 | 0 | double *pdfXCoord = |
3604 | 0 | static_cast<double *>(CPLCalloc(xdim, sizeof(double))); |
3605 | 0 | double *pdfYCoord = |
3606 | 0 | static_cast<double *>(CPLCalloc(ydim, sizeof(double))); |
3607 | |
|
3608 | 0 | size_t start[2] = {0, 0}; |
3609 | 0 | size_t edge[2] = {xdim, 0}; |
3610 | 0 | int status = nc_get_vara_double(nGroupDimXID, nVarDimXID, start, edge, |
3611 | 0 | pdfXCoord); |
3612 | 0 | NCDF_ERR(status); |
3613 | |
|
3614 | 0 | edge[0] = ydim; |
3615 | 0 | status = nc_get_vara_double(nGroupDimYID, nVarDimYID, start, edge, |
3616 | 0 | pdfYCoord); |
3617 | 0 | NCDF_ERR(status); |
3618 | |
|
3619 | 0 | nc_type nc_var_dimx_datatype = NC_NAT; |
3620 | 0 | status = |
3621 | 0 | nc_inq_vartype(nGroupDimXID, nVarDimXID, &nc_var_dimx_datatype); |
3622 | 0 | NCDF_ERR(status); |
3623 | |
|
3624 | 0 | nc_type nc_var_dimy_datatype = NC_NAT; |
3625 | 0 | status = |
3626 | 0 | nc_inq_vartype(nGroupDimYID, nVarDimYID, &nc_var_dimy_datatype); |
3627 | 0 | NCDF_ERR(status); |
3628 | |
|
3629 | 0 | if (!poDS->bSwitchedXY) |
3630 | 0 | { |
3631 | | // Convert ]180,540] longitude values to ]-180,0]. |
3632 | 0 | if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) && |
3633 | 0 | CPLTestBool( |
3634 | 0 | CPLGetConfigOption("GDAL_NETCDF_CENTERLONG_180", "YES"))) |
3635 | 0 | { |
3636 | | // If minimum longitude is > 180, subtract 360 from all. |
3637 | | // Add a check on the maximum X value too, since |
3638 | | // NCDFIsVarLongitude() is not very specific by default (see |
3639 | | // https://github.com/OSGeo/gdal/issues/1440) |
3640 | 0 | if (std::min(pdfXCoord[0], pdfXCoord[xdim - 1]) > 180.0 && |
3641 | 0 | std::max(pdfXCoord[0], pdfXCoord[xdim - 1]) <= 540) |
3642 | 0 | { |
3643 | 0 | CPLDebug( |
3644 | 0 | "GDAL_netCDF", |
3645 | 0 | "Offsetting longitudes from ]180,540] to ]-180,180]. " |
3646 | 0 | "Can be disabled with GDAL_NETCDF_CENTERLONG_180=NO"); |
3647 | 0 | for (size_t i = 0; i < xdim; i++) |
3648 | 0 | pdfXCoord[i] -= 360; |
3649 | 0 | } |
3650 | 0 | } |
3651 | 0 | } |
3652 | | |
3653 | | // Is pixel spacing uniform across the map? |
3654 | | |
3655 | | // Check Longitude. |
3656 | |
|
3657 | 0 | bool bLonSpacingOK = false; |
3658 | 0 | if (xdim == 2) |
3659 | 0 | { |
3660 | 0 | bLonSpacingOK = true; |
3661 | 0 | } |
3662 | 0 | else |
3663 | 0 | { |
3664 | 0 | bool bWestIsLeft = (pdfXCoord[0] < pdfXCoord[xdim - 1]); |
3665 | | |
3666 | | // fix longitudes if longitudes should increase from |
3667 | | // west to east, but west > east |
3668 | 0 | if (NCDFIsVarLongitude(nGroupDimXID, nVarDimXID, nullptr) && |
3669 | 0 | !bWestIsLeft) |
3670 | 0 | { |
3671 | 0 | size_t ndecreases = 0; |
3672 | | |
3673 | | // there is lon wrap if longitudes increase |
3674 | | // with one single decrease |
3675 | 0 | for (size_t i = 1; i < xdim; i++) |
3676 | 0 | { |
3677 | 0 | if (pdfXCoord[i] < pdfXCoord[i - 1]) |
3678 | 0 | ndecreases++; |
3679 | 0 | } |
3680 | |
|
3681 | 0 | if (ndecreases == 1) |
3682 | 0 | { |
3683 | 0 | CPLDebug("GDAL_netCDF", "longitude wrap detected"); |
3684 | 0 | for (size_t i = 0; i < xdim; i++) |
3685 | 0 | { |
3686 | 0 | if (pdfXCoord[i] > pdfXCoord[xdim - 1]) |
3687 | 0 | pdfXCoord[i] -= 360; |
3688 | 0 | } |
3689 | 0 | } |
3690 | 0 | } |
3691 | |
|
3692 | 0 | const double dfSpacingBegin = pdfXCoord[1] - pdfXCoord[0]; |
3693 | 0 | const double dfSpacingMiddle = |
3694 | 0 | pdfXCoord[xdim / 2 + 1] - pdfXCoord[xdim / 2]; |
3695 | 0 | const double dfSpacingLast = |
3696 | 0 | pdfXCoord[xdim - 1] - pdfXCoord[xdim - 2]; |
3697 | |
|
3698 | 0 | CPLDebug("GDAL_netCDF", |
3699 | 0 | "xdim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f " |
3700 | 0 | "dfSpacingLast: %f", |
3701 | 0 | static_cast<long>(xdim), dfSpacingBegin, dfSpacingMiddle, |
3702 | 0 | dfSpacingLast); |
3703 | | #ifdef NCDF_DEBUG |
3704 | | CPLDebug("GDAL_netCDF", "xcoords: %f %f %f %f %f %f", pdfXCoord[0], |
3705 | | pdfXCoord[1], pdfXCoord[xdim / 2], |
3706 | | pdfXCoord[(xdim / 2) + 1], pdfXCoord[xdim - 2], |
3707 | | pdfXCoord[xdim - 1]); |
3708 | | #endif |
3709 | | |
3710 | | // ftp://ftp.cdc.noaa.gov/Datasets/NARR/Dailies/monolevel/vwnd.10m.2015.nc |
3711 | | // requires a 0.02% tolerance, so let's settle for 0.05% |
3712 | | |
3713 | | // For float variables, increase to 0.2% (as seen in |
3714 | | // https://github.com/OSGeo/gdal/issues/3663) |
3715 | 0 | const double dfEpsRel = |
3716 | 0 | nc_var_dimx_datatype == NC_FLOAT ? 0.002 : 0.0005; |
3717 | |
|
3718 | 0 | const double dfEps = |
3719 | 0 | dfEpsRel * |
3720 | 0 | std::max(fabs(dfSpacingBegin), |
3721 | 0 | std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast))); |
3722 | 0 | if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) && |
3723 | 0 | IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) && |
3724 | 0 | IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps)) |
3725 | 0 | { |
3726 | 0 | bLonSpacingOK = true; |
3727 | 0 | } |
3728 | 0 | else if (CPLTestBool(CPLGetConfigOption( |
3729 | 0 | "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO"))) |
3730 | 0 | { |
3731 | 0 | bLonSpacingOK = true; |
3732 | 0 | CPLDebug( |
3733 | 0 | "GDAL_netCDF", |
3734 | 0 | "Longitude/X is not equally spaced, but will be considered " |
3735 | 0 | "as such because of " |
3736 | 0 | "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK"); |
3737 | 0 | } |
3738 | 0 | } |
3739 | |
|
3740 | 0 | if (bLonSpacingOK == false) |
3741 | 0 | { |
3742 | 0 | CPLDebug( |
3743 | 0 | "GDAL_netCDF", "%s", |
3744 | 0 | "Longitude/X is not equally spaced (with a 0.05% tolerance). " |
3745 | 0 | "You may set the " |
3746 | 0 | "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration " |
3747 | 0 | "option to YES to ignore this check"); |
3748 | 0 | } |
3749 | | |
3750 | | // Check Latitude. |
3751 | 0 | bool bLatSpacingOK = false; |
3752 | |
|
3753 | 0 | if (ydim == 2) |
3754 | 0 | { |
3755 | 0 | bLatSpacingOK = true; |
3756 | 0 | } |
3757 | 0 | else |
3758 | 0 | { |
3759 | 0 | const double dfSpacingBegin = pdfYCoord[1] - pdfYCoord[0]; |
3760 | 0 | const double dfSpacingMiddle = |
3761 | 0 | pdfYCoord[ydim / 2 + 1] - pdfYCoord[ydim / 2]; |
3762 | |
|
3763 | 0 | const double dfSpacingLast = |
3764 | 0 | pdfYCoord[ydim - 1] - pdfYCoord[ydim - 2]; |
3765 | |
|
3766 | 0 | CPLDebug("GDAL_netCDF", |
3767 | 0 | "ydim: %ld dfSpacingBegin: %f dfSpacingMiddle: %f " |
3768 | 0 | "dfSpacingLast: %f", |
3769 | 0 | (long)ydim, dfSpacingBegin, dfSpacingMiddle, |
3770 | 0 | dfSpacingLast); |
3771 | | #ifdef NCDF_DEBUG |
3772 | | CPLDebug("GDAL_netCDF", "ycoords: %f %f %f %f %f %f", pdfYCoord[0], |
3773 | | pdfYCoord[1], pdfYCoord[ydim / 2], |
3774 | | pdfYCoord[(ydim / 2) + 1], pdfYCoord[ydim - 2], |
3775 | | pdfYCoord[ydim - 1]); |
3776 | | #endif |
3777 | |
|
3778 | 0 | const double dfEpsRel = |
3779 | 0 | nc_var_dimy_datatype == NC_FLOAT ? 0.002 : 0.0005; |
3780 | |
|
3781 | 0 | const double dfEps = |
3782 | 0 | dfEpsRel * |
3783 | 0 | std::max(fabs(dfSpacingBegin), |
3784 | 0 | std::max(fabs(dfSpacingMiddle), fabs(dfSpacingLast))); |
3785 | 0 | if (IsDifferenceBelow(dfSpacingBegin, dfSpacingLast, dfEps) && |
3786 | 0 | IsDifferenceBelow(dfSpacingBegin, dfSpacingMiddle, dfEps) && |
3787 | 0 | IsDifferenceBelow(dfSpacingMiddle, dfSpacingLast, dfEps)) |
3788 | 0 | { |
3789 | 0 | bLatSpacingOK = true; |
3790 | 0 | } |
3791 | 0 | else if (CPLTestBool(CPLGetConfigOption( |
3792 | 0 | "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK", "NO"))) |
3793 | 0 | { |
3794 | 0 | bLatSpacingOK = true; |
3795 | 0 | CPLDebug( |
3796 | 0 | "GDAL_netCDF", |
3797 | 0 | "Latitude/Y is not equally spaced, but will be considered " |
3798 | 0 | "as such because of " |
3799 | 0 | "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK"); |
3800 | 0 | } |
3801 | 0 | else if (!oSRS.IsProjected() && |
3802 | 0 | fabs(dfSpacingBegin - dfSpacingLast) <= 0.1 && |
3803 | 0 | fabs(dfSpacingBegin - dfSpacingMiddle) <= 0.1 && |
3804 | 0 | fabs(dfSpacingMiddle - dfSpacingLast) <= 0.1) |
3805 | 0 | { |
3806 | 0 | bLatSpacingOK = true; |
3807 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
3808 | 0 | "Latitude grid not spaced evenly. " |
3809 | 0 | "Setting projection for grid spacing is " |
3810 | 0 | "within 0.1 degrees threshold."); |
3811 | |
|
3812 | 0 | CPLDebug("GDAL_netCDF", |
3813 | 0 | "Latitude grid not spaced evenly, but within 0.1 " |
3814 | 0 | "degree threshold (probably a Gaussian grid). " |
3815 | 0 | "Saving original latitude values in Y_VALUES " |
3816 | 0 | "geolocation metadata"); |
3817 | 0 | Set1DGeolocation(nGroupDimYID, nVarDimYID, "Y"); |
3818 | 0 | } |
3819 | |
|
3820 | 0 | if (bLatSpacingOK == false) |
3821 | 0 | { |
3822 | 0 | CPLDebug( |
3823 | 0 | "GDAL_netCDF", "%s", |
3824 | 0 | "Latitude/Y is not equally spaced (with a 0.05% " |
3825 | 0 | "tolerance). " |
3826 | 0 | "You may set the " |
3827 | 0 | "GDAL_NETCDF_IGNORE_EQUALLY_SPACED_XY_CHECK configuration " |
3828 | 0 | "option to YES to ignore this check"); |
3829 | 0 | } |
3830 | 0 | } |
3831 | |
|
3832 | 0 | if (bLonSpacingOK && bLatSpacingOK) |
3833 | 0 | { |
3834 | | // We have gridded data so we can set the Georeferencing info. |
3835 | | |
3836 | | // Enable GeoTransform. |
3837 | | |
3838 | | // In the following "actual_range" and "node_offset" |
3839 | | // are attributes used by netCDF files created by GMT. |
3840 | | // If we find them we know how to proceed. Else, use |
3841 | | // the original algorithm. |
3842 | 0 | bGotCfGT = true; |
3843 | |
|
3844 | 0 | int node_offset = 0; |
3845 | 0 | NCDFResolveAttInt(nGroupId, NC_GLOBAL, "node_offset", &node_offset); |
3846 | |
|
3847 | 0 | double adfActualRange[2] = {0.0, 0.0}; |
3848 | 0 | double xMinMax[2] = {0.0, 0.0}; |
3849 | 0 | double yMinMax[2] = {0.0, 0.0}; |
3850 | |
|
3851 | 0 | const auto RoundMinMaxForFloatVals = |
3852 | 0 | [](double &dfMin, double &dfMax, int nIntervals) |
3853 | 0 | { |
3854 | | // Helps for a case where longitudes range from |
3855 | | // -179.99 to 180.0 with a 0.01 degree spacing. |
3856 | | // However as this is encoded in a float array, |
3857 | | // -179.99 is actually read as -179.99000549316406 as |
3858 | | // a double. Try to detect that and correct the rounding |
3859 | |
|
3860 | 0 | const auto IsAlmostInteger = [](double dfVal) |
3861 | 0 | { |
3862 | 0 | constexpr double THRESHOLD_INTEGER = 1e-3; |
3863 | 0 | return std::fabs(dfVal - std::round(dfVal)) <= |
3864 | 0 | THRESHOLD_INTEGER; |
3865 | 0 | }; |
3866 | |
|
3867 | 0 | const double dfSpacing = (dfMax - dfMin) / nIntervals; |
3868 | 0 | if (dfSpacing > 0) |
3869 | 0 | { |
3870 | 0 | const double dfInvSpacing = 1.0 / dfSpacing; |
3871 | 0 | if (IsAlmostInteger(dfInvSpacing)) |
3872 | 0 | { |
3873 | 0 | const double dfRoundedSpacing = |
3874 | 0 | 1.0 / std::round(dfInvSpacing); |
3875 | 0 | const double dfMinDivRoundedSpacing = |
3876 | 0 | dfMin / dfRoundedSpacing; |
3877 | 0 | const double dfMaxDivRoundedSpacing = |
3878 | 0 | dfMax / dfRoundedSpacing; |
3879 | 0 | if (IsAlmostInteger(dfMinDivRoundedSpacing) && |
3880 | 0 | IsAlmostInteger(dfMaxDivRoundedSpacing)) |
3881 | 0 | { |
3882 | 0 | const double dfRoundedMin = |
3883 | 0 | std::round(dfMinDivRoundedSpacing) * |
3884 | 0 | dfRoundedSpacing; |
3885 | 0 | const double dfRoundedMax = |
3886 | 0 | std::round(dfMaxDivRoundedSpacing) * |
3887 | 0 | dfRoundedSpacing; |
3888 | 0 | if (static_cast<float>(dfMin) == |
3889 | 0 | static_cast<float>(dfRoundedMin) && |
3890 | 0 | static_cast<float>(dfMax) == |
3891 | 0 | static_cast<float>(dfRoundedMax)) |
3892 | 0 | { |
3893 | 0 | dfMin = dfRoundedMin; |
3894 | 0 | dfMax = dfRoundedMax; |
3895 | 0 | } |
3896 | 0 | } |
3897 | 0 | } |
3898 | 0 | } |
3899 | 0 | }; |
3900 | |
|
3901 | 0 | if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range", |
3902 | 0 | adfActualRange)) |
3903 | 0 | { |
3904 | 0 | xMinMax[0] = adfActualRange[0]; |
3905 | 0 | xMinMax[1] = adfActualRange[1]; |
3906 | | |
3907 | | // Present xMinMax[] in the same order as padfXCoord |
3908 | 0 | if ((xMinMax[0] - xMinMax[1]) * |
3909 | 0 | (pdfXCoord[0] - pdfXCoord[xdim - 1]) < |
3910 | 0 | 0) |
3911 | 0 | { |
3912 | 0 | std::swap(xMinMax[0], xMinMax[1]); |
3913 | 0 | } |
3914 | 0 | } |
3915 | 0 | else |
3916 | 0 | { |
3917 | 0 | xMinMax[0] = pdfXCoord[0]; |
3918 | 0 | xMinMax[1] = pdfXCoord[xdim - 1]; |
3919 | 0 | node_offset = 0; |
3920 | |
|
3921 | 0 | if (nc_var_dimx_datatype == NC_FLOAT) |
3922 | 0 | { |
3923 | 0 | RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1], |
3924 | 0 | poDS->nRasterXSize - 1); |
3925 | 0 | } |
3926 | 0 | } |
3927 | |
|
3928 | 0 | if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range", |
3929 | 0 | adfActualRange)) |
3930 | 0 | { |
3931 | 0 | yMinMax[0] = adfActualRange[0]; |
3932 | 0 | yMinMax[1] = adfActualRange[1]; |
3933 | | |
3934 | | // Present yMinMax[] in the same order as pdfYCoord |
3935 | 0 | if ((yMinMax[0] - yMinMax[1]) * |
3936 | 0 | (pdfYCoord[0] - pdfYCoord[ydim - 1]) < |
3937 | 0 | 0) |
3938 | 0 | { |
3939 | 0 | std::swap(yMinMax[0], yMinMax[1]); |
3940 | 0 | } |
3941 | 0 | } |
3942 | 0 | else |
3943 | 0 | { |
3944 | 0 | yMinMax[0] = pdfYCoord[0]; |
3945 | 0 | yMinMax[1] = pdfYCoord[ydim - 1]; |
3946 | 0 | node_offset = 0; |
3947 | |
|
3948 | 0 | if (nc_var_dimy_datatype == NC_FLOAT) |
3949 | 0 | { |
3950 | 0 | RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1], |
3951 | 0 | poDS->nRasterYSize - 1); |
3952 | 0 | } |
3953 | 0 | } |
3954 | |
|
3955 | 0 | double dfCoordOffset = 0.0; |
3956 | 0 | double dfCoordScale = 1.0; |
3957 | 0 | if (!nc_get_att_double(nGroupId, nVarDimXID, CF_ADD_OFFSET, |
3958 | 0 | &dfCoordOffset) && |
3959 | 0 | !nc_get_att_double(nGroupId, nVarDimXID, CF_SCALE_FACTOR, |
3960 | 0 | &dfCoordScale)) |
3961 | 0 | { |
3962 | 0 | xMinMax[0] = dfCoordOffset + xMinMax[0] * dfCoordScale; |
3963 | 0 | xMinMax[1] = dfCoordOffset + xMinMax[1] * dfCoordScale; |
3964 | 0 | } |
3965 | |
|
3966 | 0 | if (!nc_get_att_double(nGroupId, nVarDimYID, CF_ADD_OFFSET, |
3967 | 0 | &dfCoordOffset) && |
3968 | 0 | !nc_get_att_double(nGroupId, nVarDimYID, CF_SCALE_FACTOR, |
3969 | 0 | &dfCoordScale)) |
3970 | 0 | { |
3971 | 0 | yMinMax[0] = dfCoordOffset + yMinMax[0] * dfCoordScale; |
3972 | 0 | yMinMax[1] = dfCoordOffset + yMinMax[1] * dfCoordScale; |
3973 | 0 | } |
3974 | | |
3975 | | // Check for reverse order of y-coordinate. |
3976 | 0 | if (!bSwitchedXY) |
3977 | 0 | { |
3978 | 0 | poDS->bBottomUp = (yMinMax[0] <= yMinMax[1]); |
3979 | 0 | CPLDebug("GDAL_netCDF", "set bBottomUp = %d from Y axis", |
3980 | 0 | static_cast<int>(poDS->bBottomUp)); |
3981 | 0 | if (!poDS->bBottomUp) |
3982 | 0 | { |
3983 | 0 | std::swap(yMinMax[0], yMinMax[1]); |
3984 | 0 | } |
3985 | 0 | } |
3986 | | |
3987 | | // Geostationary satellites can specify units in (micro)radians |
3988 | | // So we check if they do, and if so convert to linear units |
3989 | | // (meters) |
3990 | 0 | const char *pszProjName = oSRS.GetAttrValue("PROJECTION"); |
3991 | 0 | if (pszProjName != nullptr) |
3992 | 0 | { |
3993 | 0 | if (EQUAL(pszProjName, SRS_PT_GEOSTATIONARY_SATELLITE)) |
3994 | 0 | { |
3995 | 0 | double satelliteHeight = |
3996 | 0 | oSRS.GetProjParm(SRS_PP_SATELLITE_HEIGHT, 1.0); |
3997 | 0 | size_t nAttlen = 0; |
3998 | 0 | char szUnits[NC_MAX_NAME + 1]; |
3999 | 0 | szUnits[0] = '\0'; |
4000 | 0 | nc_type nAttype = NC_NAT; |
4001 | 0 | nc_inq_att(nGroupId, nVarDimXID, "units", &nAttype, |
4002 | 0 | &nAttlen); |
4003 | 0 | if (nAttlen < sizeof(szUnits) && |
4004 | 0 | nc_get_att_text(nGroupId, nVarDimXID, "units", |
4005 | 0 | szUnits) == NC_NOERR) |
4006 | 0 | { |
4007 | 0 | szUnits[nAttlen] = '\0'; |
4008 | 0 | if (EQUAL(szUnits, "microradian")) |
4009 | 0 | { |
4010 | 0 | xMinMax[0] = |
4011 | 0 | xMinMax[0] * satelliteHeight * 0.000001; |
4012 | 0 | xMinMax[1] = |
4013 | 0 | xMinMax[1] * satelliteHeight * 0.000001; |
4014 | 0 | } |
4015 | 0 | else if (EQUAL(szUnits, "rad") || |
4016 | 0 | EQUAL(szUnits, "radian")) |
4017 | 0 | { |
4018 | 0 | xMinMax[0] = xMinMax[0] * satelliteHeight; |
4019 | 0 | xMinMax[1] = xMinMax[1] * satelliteHeight; |
4020 | 0 | } |
4021 | 0 | } |
4022 | 0 | szUnits[0] = '\0'; |
4023 | 0 | nc_inq_att(nGroupId, nVarDimYID, "units", &nAttype, |
4024 | 0 | &nAttlen); |
4025 | 0 | if (nAttlen < sizeof(szUnits) && |
4026 | 0 | nc_get_att_text(nGroupId, nVarDimYID, "units", |
4027 | 0 | szUnits) == NC_NOERR) |
4028 | 0 | { |
4029 | 0 | szUnits[nAttlen] = '\0'; |
4030 | 0 | if (EQUAL(szUnits, "microradian")) |
4031 | 0 | { |
4032 | 0 | yMinMax[0] = |
4033 | 0 | yMinMax[0] * satelliteHeight * 0.000001; |
4034 | 0 | yMinMax[1] = |
4035 | 0 | yMinMax[1] * satelliteHeight * 0.000001; |
4036 | 0 | } |
4037 | 0 | else if (EQUAL(szUnits, "rad") || |
4038 | 0 | EQUAL(szUnits, "radian")) |
4039 | 0 | { |
4040 | 0 | yMinMax[0] = yMinMax[0] * satelliteHeight; |
4041 | 0 | yMinMax[1] = yMinMax[1] * satelliteHeight; |
4042 | 0 | } |
4043 | 0 | } |
4044 | 0 | } |
4045 | 0 | } |
4046 | |
|
4047 | 0 | adfTempGeoTransform[0] = xMinMax[0]; |
4048 | 0 | adfTempGeoTransform[1] = (xMinMax[1] - xMinMax[0]) / |
4049 | 0 | (poDS->nRasterXSize + (node_offset - 1)); |
4050 | 0 | adfTempGeoTransform[2] = 0; |
4051 | 0 | if (bSwitchedXY) |
4052 | 0 | { |
4053 | 0 | adfTempGeoTransform[3] = yMinMax[0]; |
4054 | 0 | adfTempGeoTransform[4] = 0; |
4055 | 0 | adfTempGeoTransform[5] = |
4056 | 0 | (yMinMax[1] - yMinMax[0]) / |
4057 | 0 | (poDS->nRasterYSize + (node_offset - 1)); |
4058 | 0 | } |
4059 | 0 | else |
4060 | 0 | { |
4061 | 0 | adfTempGeoTransform[3] = yMinMax[1]; |
4062 | 0 | adfTempGeoTransform[4] = 0; |
4063 | 0 | adfTempGeoTransform[5] = |
4064 | 0 | (yMinMax[0] - yMinMax[1]) / |
4065 | 0 | (poDS->nRasterYSize + (node_offset - 1)); |
4066 | 0 | } |
4067 | | |
4068 | | // Compute the center of the pixel. |
4069 | 0 | if (!node_offset) |
4070 | 0 | { |
4071 | | // Otherwise its already the pixel center. |
4072 | 0 | adfTempGeoTransform[0] -= (adfTempGeoTransform[1] / 2); |
4073 | 0 | adfTempGeoTransform[3] -= (adfTempGeoTransform[5] / 2); |
4074 | 0 | } |
4075 | 0 | } |
4076 | |
|
4077 | 0 | const auto AreSRSEqualThroughProj4String = |
4078 | 0 | [](const OGRSpatialReference &oSRS1, |
4079 | 0 | const OGRSpatialReference &oSRS2) |
4080 | 0 | { |
4081 | 0 | char *pszProj4Str1 = nullptr; |
4082 | 0 | oSRS1.exportToProj4(&pszProj4Str1); |
4083 | |
|
4084 | 0 | char *pszProj4Str2 = nullptr; |
4085 | 0 | oSRS2.exportToProj4(&pszProj4Str2); |
4086 | |
|
4087 | 0 | { |
4088 | 0 | char *pszTmp = strstr(pszProj4Str1, "+datum="); |
4089 | 0 | if (pszTmp) |
4090 | 0 | memcpy(pszTmp, "+ellps=", strlen("+ellps=")); |
4091 | 0 | } |
4092 | |
|
4093 | 0 | { |
4094 | 0 | char *pszTmp = strstr(pszProj4Str2, "+datum="); |
4095 | 0 | if (pszTmp) |
4096 | 0 | memcpy(pszTmp, "+ellps=", strlen("+ellps=")); |
4097 | 0 | } |
4098 | |
|
4099 | 0 | bool bRet = false; |
4100 | 0 | if (pszProj4Str1 && pszProj4Str2 && |
4101 | 0 | EQUAL(pszProj4Str1, pszProj4Str2)) |
4102 | 0 | { |
4103 | 0 | bRet = true; |
4104 | 0 | } |
4105 | |
|
4106 | 0 | CPLFree(pszProj4Str1); |
4107 | 0 | CPLFree(pszProj4Str2); |
4108 | 0 | return bRet; |
4109 | 0 | }; |
4110 | |
|
4111 | 0 | if (dfLinearUnitsConvFactor != 1.0) |
4112 | 0 | { |
4113 | 0 | for (int i = 0; i < 6; ++i) |
4114 | 0 | adfTempGeoTransform[i] *= dfLinearUnitsConvFactor; |
4115 | |
|
4116 | 0 | if (paosRemovedMDItems) |
4117 | 0 | { |
4118 | 0 | char szVarNameX[NC_MAX_NAME + 1]; |
4119 | 0 | CPL_IGNORE_RET_VAL( |
4120 | 0 | nc_inq_varname(nGroupId, nVarDimXID, szVarNameX)); |
4121 | |
|
4122 | 0 | char szVarNameY[NC_MAX_NAME + 1]; |
4123 | 0 | CPL_IGNORE_RET_VAL( |
4124 | 0 | nc_inq_varname(nGroupId, nVarDimYID, szVarNameY)); |
4125 | |
|
4126 | 0 | paosRemovedMDItems->push_back( |
4127 | 0 | CPLSPrintf("%s#units", szVarNameX)); |
4128 | 0 | paosRemovedMDItems->push_back( |
4129 | 0 | CPLSPrintf("%s#units", szVarNameY)); |
4130 | 0 | } |
4131 | 0 | } |
4132 | | |
4133 | | // If there is a global "geospatial_bounds_crs" attribute, check that it |
4134 | | // is consistent with the SRS, and if so, use it as the SRS |
4135 | 0 | const char *pszGBCRS = |
4136 | 0 | FetchAttr(nGroupId, NC_GLOBAL, "geospatial_bounds_crs"); |
4137 | 0 | if (pszGBCRS && STARTS_WITH(pszGBCRS, "EPSG:")) |
4138 | 0 | { |
4139 | 0 | OGRSpatialReference oSRSFromGBCRS; |
4140 | 0 | oSRSFromGBCRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
4141 | 0 | if (oSRSFromGBCRS.SetFromUserInput( |
4142 | 0 | pszGBCRS, |
4143 | 0 | OGRSpatialReference:: |
4144 | 0 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE && |
4145 | 0 | AreSRSEqualThroughProj4String(oSRS, oSRSFromGBCRS)) |
4146 | 0 | { |
4147 | 0 | oSRS = std::move(oSRSFromGBCRS); |
4148 | 0 | SetSpatialRefNoUpdate(&oSRS); |
4149 | 0 | } |
4150 | 0 | } |
4151 | |
|
4152 | 0 | CPLFree(pdfXCoord); |
4153 | 0 | CPLFree(pdfYCoord); |
4154 | 0 | } // end if(has dims) |
4155 | | |
4156 | | // Process custom GeoTransform GDAL value. |
4157 | 23 | if (!EQUAL(pszGridMappingValue, "")) |
4158 | 0 | { |
4159 | 0 | if (pszGeoTransform != nullptr) |
4160 | 0 | { |
4161 | 0 | CPLStringList aosGeoTransform( |
4162 | 0 | CSLTokenizeString2(pszGeoTransform, " ", CSLT_HONOURSTRINGS)); |
4163 | 0 | if (aosGeoTransform.size() == 6) |
4164 | 0 | { |
4165 | 0 | std::array<double, 6> adfGeoTransformFromAttribute; |
4166 | 0 | for (int i = 0; i < 6; i++) |
4167 | 0 | { |
4168 | 0 | adfGeoTransformFromAttribute[i] = |
4169 | 0 | CPLAtof(aosGeoTransform[i]); |
4170 | 0 | } |
4171 | |
|
4172 | 0 | if (bGotCfGT) |
4173 | 0 | { |
4174 | 0 | constexpr double GT_RELERROR_WARN_THRESHOLD = 1e-6; |
4175 | 0 | double dfMaxAbsoluteError = 0.0; |
4176 | 0 | for (int i = 0; i < 6; i++) |
4177 | 0 | { |
4178 | 0 | double dfAbsoluteError = |
4179 | 0 | std::abs(adfTempGeoTransform[i] - |
4180 | 0 | adfGeoTransformFromAttribute[i]); |
4181 | 0 | if (dfAbsoluteError > |
4182 | 0 | std::abs(adfGeoTransformFromAttribute[i] * |
4183 | 0 | GT_RELERROR_WARN_THRESHOLD)) |
4184 | 0 | { |
4185 | 0 | dfMaxAbsoluteError = |
4186 | 0 | std::max(dfMaxAbsoluteError, dfAbsoluteError); |
4187 | 0 | } |
4188 | 0 | } |
4189 | |
|
4190 | 0 | if (dfMaxAbsoluteError > 0) |
4191 | 0 | { |
4192 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
4193 | 0 | "GeoTransform read from attribute of %s " |
4194 | 0 | "variable differs from value calculated from " |
4195 | 0 | "dimension variables (max diff = %g). Using " |
4196 | 0 | "value from attribute.", |
4197 | 0 | pszGridMappingValue, dfMaxAbsoluteError); |
4198 | 0 | } |
4199 | 0 | } |
4200 | |
|
4201 | 0 | std::copy(adfGeoTransformFromAttribute.begin(), |
4202 | 0 | adfGeoTransformFromAttribute.end(), |
4203 | 0 | adfTempGeoTransform); |
4204 | 0 | bGotGdalGT = true; |
4205 | 0 | } |
4206 | 0 | } |
4207 | 0 | else |
4208 | 0 | { |
4209 | | // Look for corner array values. |
4210 | | // CPLDebug("GDAL_netCDF", |
4211 | | // "looking for geotransform corners"); |
4212 | 0 | bool bGotNN = false; |
4213 | 0 | double dfNN = FetchCopyParam(pszGridMappingValue, |
4214 | 0 | "Northernmost_Northing", 0, &bGotNN); |
4215 | |
|
4216 | 0 | bool bGotSN = false; |
4217 | 0 | double dfSN = FetchCopyParam(pszGridMappingValue, |
4218 | 0 | "Southernmost_Northing", 0, &bGotSN); |
4219 | |
|
4220 | 0 | bool bGotEE = false; |
4221 | 0 | double dfEE = FetchCopyParam(pszGridMappingValue, |
4222 | 0 | "Easternmost_Easting", 0, &bGotEE); |
4223 | |
|
4224 | 0 | bool bGotWE = false; |
4225 | 0 | double dfWE = FetchCopyParam(pszGridMappingValue, |
4226 | 0 | "Westernmost_Easting", 0, &bGotWE); |
4227 | | |
4228 | | // Only set the GeoTransform if we got all the values. |
4229 | 0 | if (bGotNN && bGotSN && bGotEE && bGotWE) |
4230 | 0 | { |
4231 | 0 | bGotGdalGT = true; |
4232 | |
|
4233 | 0 | adfTempGeoTransform[0] = dfWE; |
4234 | 0 | adfTempGeoTransform[1] = |
4235 | 0 | (dfEE - dfWE) / (poDS->GetRasterXSize() - 1); |
4236 | 0 | adfTempGeoTransform[2] = 0.0; |
4237 | 0 | adfTempGeoTransform[3] = dfNN; |
4238 | 0 | adfTempGeoTransform[4] = 0.0; |
4239 | 0 | adfTempGeoTransform[5] = |
4240 | 0 | (dfSN - dfNN) / (poDS->GetRasterYSize() - 1); |
4241 | | // Compute the center of the pixel. |
4242 | 0 | adfTempGeoTransform[0] = dfWE - (adfTempGeoTransform[1] / 2); |
4243 | 0 | adfTempGeoTransform[3] = dfNN - (adfTempGeoTransform[5] / 2); |
4244 | 0 | } |
4245 | 0 | } // (pszGeoTransform != NULL) |
4246 | |
|
4247 | 0 | if (bGotGdalSRS && !bGotGdalGT) |
4248 | 0 | CPLDebug("GDAL_netCDF", "Got SRS but no geotransform from GDAL!"); |
4249 | 0 | } |
4250 | | |
4251 | 23 | if (!pszWKT && !bGotCfSRS) |
4252 | 23 | { |
4253 | | // Some netCDF files have a srid attribute (#6613) like |
4254 | | // urn:ogc:def:crs:EPSG::6931 |
4255 | 23 | const char *pszSRID = FetchAttr(pszGridMappingValue, "srid"); |
4256 | 23 | if (pszSRID != nullptr) |
4257 | 0 | { |
4258 | 0 | oSRS.Clear(); |
4259 | 0 | if (oSRS.SetFromUserInput( |
4260 | 0 | pszSRID, |
4261 | 0 | OGRSpatialReference:: |
4262 | 0 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE) |
4263 | 0 | { |
4264 | 0 | char *pszWKTExport = nullptr; |
4265 | 0 | CPLDebug("GDAL_netCDF", "Got SRS from %s", pszSRID); |
4266 | 0 | oSRS.exportToWkt(&pszWKTExport); |
4267 | 0 | if (returnProjStr != nullptr) |
4268 | 0 | { |
4269 | 0 | (*returnProjStr) = std::string(pszWKTExport); |
4270 | 0 | } |
4271 | 0 | else |
4272 | 0 | { |
4273 | 0 | m_bAddedProjectionVarsDefs = true; |
4274 | 0 | m_bAddedProjectionVarsData = true; |
4275 | 0 | SetSpatialRefNoUpdate(&oSRS); |
4276 | 0 | } |
4277 | 0 | CPLFree(pszWKTExport); |
4278 | 0 | } |
4279 | 0 | } |
4280 | 23 | } |
4281 | | |
4282 | 23 | CPLFree(pszGridMappingValue); |
4283 | | |
4284 | 23 | if (bReadSRSOnly) |
4285 | 23 | return; |
4286 | | |
4287 | | // Determines the SRS to be used by the geolocation array, if any |
4288 | 0 | std::string osGeolocWKT = SRS_WKT_WGS84_LAT_LONG; |
4289 | 0 | if (!m_oSRS.IsEmpty()) |
4290 | 0 | { |
4291 | 0 | OGRSpatialReference oGeogCRS; |
4292 | 0 | oGeogCRS.CopyGeogCSFrom(&m_oSRS); |
4293 | 0 | char *pszWKTTmp = nullptr; |
4294 | 0 | const char *const apszOptions[] = {"FORMAT=WKT2_2019", nullptr}; |
4295 | 0 | if (oGeogCRS.exportToWkt(&pszWKTTmp, apszOptions) == OGRERR_NONE) |
4296 | 0 | { |
4297 | 0 | osGeolocWKT = pszWKTTmp; |
4298 | 0 | } |
4299 | 0 | CPLFree(pszWKTTmp); |
4300 | 0 | } |
4301 | | |
4302 | | // Process geolocation arrays from CF "coordinates" attribute. |
4303 | 0 | std::string osGeolocXName, osGeolocYName; |
4304 | 0 | if (ProcessCFGeolocation(nGroupId, nVarId, osGeolocWKT, osGeolocXName, |
4305 | 0 | osGeolocYName)) |
4306 | 0 | { |
4307 | 0 | bool bCanCancelGT = true; |
4308 | 0 | if ((nVarDimXID != -1) && (nVarDimYID != -1)) |
4309 | 0 | { |
4310 | 0 | char szVarNameX[NC_MAX_NAME + 1]; |
4311 | 0 | CPL_IGNORE_RET_VAL( |
4312 | 0 | nc_inq_varname(nGroupId, nVarDimXID, szVarNameX)); |
4313 | 0 | char szVarNameY[NC_MAX_NAME + 1]; |
4314 | 0 | CPL_IGNORE_RET_VAL( |
4315 | 0 | nc_inq_varname(nGroupId, nVarDimYID, szVarNameY)); |
4316 | 0 | bCanCancelGT = |
4317 | 0 | !(osGeolocXName == szVarNameX && osGeolocYName == szVarNameY); |
4318 | 0 | } |
4319 | 0 | if (bCanCancelGT && !m_oSRS.IsGeographic() && !m_oSRS.IsProjected() && |
4320 | 0 | !bSwitchedXY) |
4321 | 0 | { |
4322 | 0 | bGotCfGT = false; |
4323 | 0 | } |
4324 | 0 | } |
4325 | 0 | else if (!bGotCfGT && !bReadSRSOnly && (nVarDimXID != -1) && |
4326 | 0 | (nVarDimYID != -1) && xdim > 0 && ydim > 0 && |
4327 | 0 | ((!bSwitchedXY && |
4328 | 0 | NCDFIsVarLongitude(nGroupId, nVarDimXID, nullptr) && |
4329 | 0 | NCDFIsVarLatitude(nGroupId, nVarDimYID, nullptr)) || |
4330 | 0 | (bSwitchedXY && |
4331 | 0 | NCDFIsVarLongitude(nGroupId, nVarDimYID, nullptr) && |
4332 | 0 | NCDFIsVarLatitude(nGroupId, nVarDimXID, nullptr)))) |
4333 | 0 | { |
4334 | | // Case of autotest/gdrivers/data/netcdf/GLMELT_4X5.OCN.nc |
4335 | | // which is indexed by lat, lon variables, but lat has irregular |
4336 | | // spacing. |
4337 | 0 | const char *pszGeolocXFullName = poDS->papszDimName[poDS->nXDimID]; |
4338 | 0 | const char *pszGeolocYFullName = poDS->papszDimName[poDS->nYDimID]; |
4339 | 0 | if (bSwitchedXY) |
4340 | 0 | { |
4341 | 0 | std::swap(pszGeolocXFullName, pszGeolocYFullName); |
4342 | 0 | GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION"); |
4343 | 0 | } |
4344 | |
|
4345 | 0 | CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION", |
4346 | 0 | pszGeolocXFullName, pszGeolocYFullName); |
4347 | |
|
4348 | 0 | GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(), |
4349 | 0 | "GEOLOCATION"); |
4350 | |
|
4351 | 0 | CPLString osTMP; |
4352 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), |
4353 | 0 | pszGeolocXFullName); |
4354 | |
|
4355 | 0 | GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION"); |
4356 | 0 | GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION"); |
4357 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), |
4358 | 0 | pszGeolocYFullName); |
4359 | |
|
4360 | 0 | GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION"); |
4361 | 0 | GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION"); |
4362 | |
|
4363 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION"); |
4364 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION"); |
4365 | |
|
4366 | 0 | GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION"); |
4367 | 0 | GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION"); |
4368 | |
|
4369 | 0 | GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", |
4370 | 0 | "PIXEL_CENTER", "GEOLOCATION"); |
4371 | 0 | } |
4372 | | |
4373 | | // Set GeoTransform if we got a complete one - after projection has been set |
4374 | 0 | if (bGotCfGT || bGotGdalGT) |
4375 | 0 | { |
4376 | 0 | m_bAddedProjectionVarsDefs = true; |
4377 | 0 | m_bAddedProjectionVarsData = true; |
4378 | 0 | SetGeoTransformNoUpdate(adfTempGeoTransform); |
4379 | 0 | } |
4380 | | |
4381 | | // Debugging reports. |
4382 | 0 | CPLDebug("GDAL_netCDF", |
4383 | 0 | "bGotGeogCS=%d bGotCfSRS=%d bGotCfGT=%d bGotCfWktSRS=%d " |
4384 | 0 | "bGotGdalSRS=%d bGotGdalGT=%d", |
4385 | 0 | static_cast<int>(bGotGeogCS), static_cast<int>(bGotCfSRS), |
4386 | 0 | static_cast<int>(bGotCfGT), static_cast<int>(bGotCfWktSRS), |
4387 | 0 | static_cast<int>(bGotGdalSRS), static_cast<int>(bGotGdalGT)); |
4388 | |
|
4389 | 0 | if (!bGotCfGT && !bGotGdalGT) |
4390 | 0 | CPLDebug("GDAL_netCDF", "did not get geotransform from CF nor GDAL!"); |
4391 | |
|
4392 | 0 | if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfGT && !bGotCfWktSRS) |
4393 | 0 | CPLDebug("GDAL_netCDF", "did not get projection from CF nor GDAL!"); |
4394 | | |
4395 | | // wish of 6195 |
4396 | | // we don't have CS/SRS, but we do have GT, and we live in -180,360 -90,90 |
4397 | 0 | if (!bGotGeogCS && !bGotCfSRS && !bGotGdalSRS && !bGotCfWktSRS) |
4398 | 0 | { |
4399 | 0 | if (bGotCfGT || bGotGdalGT) |
4400 | 0 | { |
4401 | 0 | bool bAssumedLongLat = CPLTestBool(CSLFetchNameValueDef( |
4402 | 0 | papszOpenOptions, "ASSUME_LONGLAT", |
4403 | 0 | CPLGetConfigOption("GDAL_NETCDF_ASSUME_LONGLAT", "NO"))); |
4404 | |
|
4405 | 0 | if (bAssumedLongLat && adfTempGeoTransform[0] >= -180 && |
4406 | 0 | adfTempGeoTransform[0] < 360 && |
4407 | 0 | (adfTempGeoTransform[0] + |
4408 | 0 | adfTempGeoTransform[1] * poDS->GetRasterXSize()) <= 360 && |
4409 | 0 | adfTempGeoTransform[3] <= 90 && adfTempGeoTransform[3] > -90 && |
4410 | 0 | (adfTempGeoTransform[3] + |
4411 | 0 | adfTempGeoTransform[5] * poDS->GetRasterYSize()) >= -90) |
4412 | 0 | { |
4413 | |
|
4414 | 0 | poDS->bIsGeographic = true; |
4415 | 0 | char *pszTempProjection = nullptr; |
4416 | | // seems odd to use 4326 so OGC:CRS84 |
4417 | 0 | oSRS.SetFromUserInput("OGC:CRS84"); |
4418 | 0 | oSRS.exportToWkt(&pszTempProjection); |
4419 | 0 | if (returnProjStr != nullptr) |
4420 | 0 | { |
4421 | 0 | (*returnProjStr) = std::string(pszTempProjection); |
4422 | 0 | } |
4423 | 0 | else |
4424 | 0 | { |
4425 | 0 | m_bAddedProjectionVarsDefs = true; |
4426 | 0 | m_bAddedProjectionVarsData = true; |
4427 | 0 | SetSpatialRefNoUpdate(&oSRS); |
4428 | 0 | } |
4429 | 0 | CPLFree(pszTempProjection); |
4430 | |
|
4431 | 0 | CPLDebug("netCDF", |
4432 | 0 | "Assumed Longitude Latitude CRS 'OGC:CRS84' because " |
4433 | 0 | "none otherwise available and geotransform within " |
4434 | 0 | "suitable bounds. " |
4435 | 0 | "Set GDAL_NETCDF_ASSUME_LONGLAT=NO as configuration " |
4436 | 0 | "option or " |
4437 | 0 | " ASSUME_LONGLAT=NO as open option to bypass this " |
4438 | 0 | "assumption."); |
4439 | 0 | } |
4440 | 0 | } |
4441 | 0 | } |
4442 | | |
4443 | | // Search for Well-known GeogCS if got only CF WKT |
4444 | | // Disabled for now, as a named datum also include control points |
4445 | | // (see mailing list and bug#4281 |
4446 | | // For example, WGS84 vs. GDA94 (EPSG:3577) - AEA in netcdf_cf.py |
4447 | | |
4448 | | // Disabled for now, but could be set in a config option. |
4449 | | #if 0 |
4450 | | bool bLookForWellKnownGCS = false; // This could be a Config Option. |
4451 | | |
4452 | | if( bLookForWellKnownGCS && bGotCfSRS && !bGotGdalSRS ) |
4453 | | { |
4454 | | // ET - Could use a more exhaustive method by scanning all EPSG codes in |
4455 | | // data/gcs.csv as proposed by Even in the gdal-dev mailing list "help |
4456 | | // for comparing two WKT". |
4457 | | // This code could be contributed to a new function. |
4458 | | // OGRSpatialReference * OGRSpatialReference::FindMatchingGeogCS( |
4459 | | // const OGRSpatialReference *poOther) */ |
4460 | | CPLDebug("GDAL_netCDF", "Searching for Well-known GeogCS"); |
4461 | | const char *pszWKGCSList[] = { "WGS84", "WGS72", "NAD27", "NAD83" }; |
4462 | | char *pszWKGCS = NULL; |
4463 | | oSRS.exportToPrettyWkt(&pszWKGCS); |
4464 | | for( size_t i = 0; i < sizeof(pszWKGCSList) / 8; i++ ) |
4465 | | { |
4466 | | pszWKGCS = CPLStrdup(pszWKGCSList[i]); |
4467 | | OGRSpatialReference oSRSTmp; |
4468 | | oSRSTmp.SetWellKnownGeogCS(pszWKGCSList[i]); |
4469 | | // Set datum to unknown, bug #4281. |
4470 | | if( oSRSTmp.GetAttrNode("DATUM" ) ) |
4471 | | oSRSTmp.GetAttrNode("DATUM")->GetChild(0)->SetValue("unknown"); |
4472 | | // Could use OGRSpatialReference::StripCTParms(), but let's keep |
4473 | | // TOWGS84. |
4474 | | oSRSTmp.GetRoot()->StripNodes("AXIS"); |
4475 | | oSRSTmp.GetRoot()->StripNodes("AUTHORITY"); |
4476 | | oSRSTmp.GetRoot()->StripNodes("EXTENSION"); |
4477 | | |
4478 | | oSRSTmp.exportToPrettyWkt(&pszWKGCS); |
4479 | | if( oSRS.IsSameGeogCS(&oSRSTmp) ) |
4480 | | { |
4481 | | oSRS.SetWellKnownGeogCS(pszWKGCSList[i]); |
4482 | | oSRS.exportToWkt(&(pszTempProjection)); |
4483 | | SetProjection(pszTempProjection); |
4484 | | CPLFree(pszTempProjection); |
4485 | | } |
4486 | | } |
4487 | | } |
4488 | | #endif |
4489 | 0 | } |
4490 | | |
4491 | | void netCDFDataset::SetProjectionFromVar(int nGroupId, int nVarId, |
4492 | | bool bReadSRSOnly) |
4493 | 23 | { |
4494 | 23 | SetProjectionFromVar(nGroupId, nVarId, bReadSRSOnly, nullptr, nullptr, |
4495 | 23 | nullptr, nullptr); |
4496 | 23 | } |
4497 | | |
4498 | | bool netCDFDataset::ProcessNASAL2OceanGeoLocation(int nGroupId, int nVarId) |
4499 | 0 | { |
4500 | | // Cf https://oceancolor.gsfc.nasa.gov/docs/format/l2nc/ |
4501 | | // and https://github.com/OSGeo/gdal/issues/7605 |
4502 | | |
4503 | | // Check for a structure like: |
4504 | | /* netcdf SNPP_VIIRS.20230406T024200.L2.OC.NRT { |
4505 | | dimensions: |
4506 | | number_of_lines = 3248 ; |
4507 | | pixels_per_line = 3200 ; |
4508 | | [...] |
4509 | | pixel_control_points = 3200 ; |
4510 | | [...] |
4511 | | group: geophysical_data { |
4512 | | variables: |
4513 | | short aot_862(number_of_lines, pixels_per_line) ; <-- nVarId |
4514 | | [...] |
4515 | | } |
4516 | | group: navigation_data { |
4517 | | variables: |
4518 | | float longitude(number_of_lines, pixel_control_points) ; |
4519 | | [...] |
4520 | | float latitude(number_of_lines, pixel_control_points) ; |
4521 | | [...] |
4522 | | } |
4523 | | } |
4524 | | */ |
4525 | | // Note that the longitude and latitude arrays are not indexed by the |
4526 | | // same dimensions. Handle only the case where |
4527 | | // pixel_control_points == pixels_per_line |
4528 | | // If there was a subsampling of the geolocation arrays, we'd need to |
4529 | | // add more logic. |
4530 | |
|
4531 | 0 | std::string osGroupName; |
4532 | 0 | osGroupName.resize(NC_MAX_NAME); |
4533 | 0 | NCDF_ERR(nc_inq_grpname(nGroupId, &osGroupName[0])); |
4534 | 0 | osGroupName.resize(strlen(osGroupName.data())); |
4535 | 0 | if (osGroupName != "geophysical_data") |
4536 | 0 | return false; |
4537 | | |
4538 | 0 | int nVarDims = 0; |
4539 | 0 | NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims)); |
4540 | 0 | if (nVarDims != 2) |
4541 | 0 | return false; |
4542 | | |
4543 | 0 | int nNavigationDataGrpId = 0; |
4544 | 0 | if (nc_inq_grp_ncid(cdfid, "navigation_data", &nNavigationDataGrpId) != |
4545 | 0 | NC_NOERR) |
4546 | 0 | return false; |
4547 | | |
4548 | 0 | std::array<int, 2> anVarDimIds; |
4549 | 0 | NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data())); |
4550 | |
|
4551 | 0 | int nLongitudeId = 0; |
4552 | 0 | int nLatitudeId = 0; |
4553 | 0 | if (nc_inq_varid(nNavigationDataGrpId, "longitude", &nLongitudeId) != |
4554 | 0 | NC_NOERR || |
4555 | 0 | nc_inq_varid(nNavigationDataGrpId, "latitude", &nLatitudeId) != |
4556 | 0 | NC_NOERR) |
4557 | 0 | { |
4558 | 0 | return false; |
4559 | 0 | } |
4560 | | |
4561 | 0 | int nDimsLongitude = 0; |
4562 | 0 | NCDF_ERR( |
4563 | 0 | nc_inq_varndims(nNavigationDataGrpId, nLongitudeId, &nDimsLongitude)); |
4564 | 0 | int nDimsLatitude = 0; |
4565 | 0 | NCDF_ERR( |
4566 | 0 | nc_inq_varndims(nNavigationDataGrpId, nLatitudeId, &nDimsLatitude)); |
4567 | 0 | if (!(nDimsLongitude == 2 && nDimsLatitude == 2)) |
4568 | 0 | { |
4569 | 0 | return false; |
4570 | 0 | } |
4571 | | |
4572 | 0 | std::array<int, 2> anDimLongitudeIds; |
4573 | 0 | NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLongitudeId, |
4574 | 0 | anDimLongitudeIds.data())); |
4575 | 0 | std::array<int, 2> anDimLatitudeIds; |
4576 | 0 | NCDF_ERR(nc_inq_vardimid(nNavigationDataGrpId, nLatitudeId, |
4577 | 0 | anDimLatitudeIds.data())); |
4578 | 0 | if (anDimLongitudeIds != anDimLatitudeIds) |
4579 | 0 | { |
4580 | 0 | return false; |
4581 | 0 | } |
4582 | | |
4583 | 0 | std::array<size_t, 2> anSizeVarDimIds; |
4584 | 0 | std::array<size_t, 2> anSizeLongLatIds; |
4585 | 0 | if (!(nc_inq_dimlen(cdfid, anVarDimIds[0], &anSizeVarDimIds[0]) == |
4586 | 0 | NC_NOERR && |
4587 | 0 | nc_inq_dimlen(cdfid, anVarDimIds[1], &anSizeVarDimIds[1]) == |
4588 | 0 | NC_NOERR && |
4589 | 0 | nc_inq_dimlen(cdfid, anDimLongitudeIds[0], &anSizeLongLatIds[0]) == |
4590 | 0 | NC_NOERR && |
4591 | 0 | nc_inq_dimlen(cdfid, anDimLongitudeIds[1], &anSizeLongLatIds[1]) == |
4592 | 0 | NC_NOERR && |
4593 | 0 | anSizeVarDimIds == anSizeLongLatIds)) |
4594 | 0 | { |
4595 | 0 | return false; |
4596 | 0 | } |
4597 | | |
4598 | 0 | const char *pszGeolocXFullName = "/navigation_data/longitude"; |
4599 | 0 | const char *pszGeolocYFullName = "/navigation_data/latitude"; |
4600 | |
|
4601 | 0 | if (bSwitchedXY) |
4602 | 0 | { |
4603 | 0 | std::swap(pszGeolocXFullName, pszGeolocYFullName); |
4604 | 0 | GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", "GEOLOCATION"); |
4605 | 0 | } |
4606 | |
|
4607 | 0 | CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION", |
4608 | 0 | pszGeolocXFullName, pszGeolocYFullName); |
4609 | |
|
4610 | 0 | GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG, |
4611 | 0 | "GEOLOCATION"); |
4612 | |
|
4613 | 0 | CPLString osTMP; |
4614 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName); |
4615 | |
|
4616 | 0 | GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION"); |
4617 | 0 | GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION"); |
4618 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName); |
4619 | |
|
4620 | 0 | GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION"); |
4621 | 0 | GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION"); |
4622 | |
|
4623 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION"); |
4624 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION"); |
4625 | |
|
4626 | 0 | GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION"); |
4627 | 0 | GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION"); |
4628 | |
|
4629 | 0 | GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER", |
4630 | 0 | "GEOLOCATION"); |
4631 | 0 | return true; |
4632 | 0 | } |
4633 | | |
4634 | | bool netCDFDataset::ProcessNASAEMITGeoLocation(int nGroupId, int nVarId) |
4635 | 0 | { |
4636 | | // Cf https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/ |
4637 | | |
4638 | | // Check for a structure like: |
4639 | | /* netcdf EMIT_L2A_RFL_001_20220903T163129_2224611_012 { |
4640 | | dimensions: |
4641 | | downtrack = 1280 ; |
4642 | | crosstrack = 1242 ; |
4643 | | bands = 285 ; |
4644 | | [...] |
4645 | | |
4646 | | variables: |
4647 | | float reflectance(downtrack, crosstrack, bands) ; |
4648 | | |
4649 | | group: location { |
4650 | | variables: |
4651 | | double lon(downtrack, crosstrack) ; |
4652 | | lon:_FillValue = -9999. ; |
4653 | | lon:long_name = "Longitude (WGS-84)" ; |
4654 | | lon:units = "degrees east" ; |
4655 | | double lat(downtrack, crosstrack) ; |
4656 | | lat:_FillValue = -9999. ; |
4657 | | lat:long_name = "Latitude (WGS-84)" ; |
4658 | | lat:units = "degrees north" ; |
4659 | | } // group location |
4660 | | |
4661 | | } |
4662 | | or |
4663 | | netcdf EMIT_L2B_MIN_001_20231024T055538_2329704_040 { |
4664 | | dimensions: |
4665 | | downtrack = 1664 ; |
4666 | | crosstrack = 1242 ; |
4667 | | [...] |
4668 | | variables: |
4669 | | float group_1_band_depth(downtrack, crosstrack) ; |
4670 | | group_1_band_depth:_FillValue = -9999.f ; |
4671 | | group_1_band_depth:long_name = "Group 1 Band Depth" ; |
4672 | | group_1_band_depth:units = "unitless" ; |
4673 | | [...] |
4674 | | group: location { |
4675 | | variables: |
4676 | | double lon(downtrack, crosstrack) ; |
4677 | | lon:_FillValue = -9999. ; |
4678 | | lon:long_name = "Longitude (WGS-84)" ; |
4679 | | lon:units = "degrees east" ; |
4680 | | double lat(downtrack, crosstrack) ; |
4681 | | lat:_FillValue = -9999. ; |
4682 | | lat:long_name = "Latitude (WGS-84)" ; |
4683 | | lat:units = "degrees north" ; |
4684 | | } |
4685 | | */ |
4686 | |
|
4687 | 0 | int nVarDims = 0; |
4688 | 0 | NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims)); |
4689 | 0 | if (nVarDims != 2 && nVarDims != 3) |
4690 | 0 | return false; |
4691 | | |
4692 | 0 | int nLocationGrpId = 0; |
4693 | 0 | if (nc_inq_grp_ncid(cdfid, "location", &nLocationGrpId) != NC_NOERR) |
4694 | 0 | return false; |
4695 | | |
4696 | 0 | std::array<int, 3> anVarDimIds; |
4697 | 0 | NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data())); |
4698 | 0 | if (nYDimID != anVarDimIds[0] || nXDimID != anVarDimIds[1]) |
4699 | 0 | return false; |
4700 | | |
4701 | 0 | int nLongitudeId = 0; |
4702 | 0 | int nLatitudeId = 0; |
4703 | 0 | if (nc_inq_varid(nLocationGrpId, "lon", &nLongitudeId) != NC_NOERR || |
4704 | 0 | nc_inq_varid(nLocationGrpId, "lat", &nLatitudeId) != NC_NOERR) |
4705 | 0 | { |
4706 | 0 | return false; |
4707 | 0 | } |
4708 | | |
4709 | 0 | int nDimsLongitude = 0; |
4710 | 0 | NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLongitudeId, &nDimsLongitude)); |
4711 | 0 | int nDimsLatitude = 0; |
4712 | 0 | NCDF_ERR(nc_inq_varndims(nLocationGrpId, nLatitudeId, &nDimsLatitude)); |
4713 | 0 | if (!(nDimsLongitude == 2 && nDimsLatitude == 2)) |
4714 | 0 | { |
4715 | 0 | return false; |
4716 | 0 | } |
4717 | | |
4718 | 0 | std::array<int, 2> anDimLongitudeIds; |
4719 | 0 | NCDF_ERR(nc_inq_vardimid(nLocationGrpId, nLongitudeId, |
4720 | 0 | anDimLongitudeIds.data())); |
4721 | 0 | std::array<int, 2> anDimLatitudeIds; |
4722 | 0 | NCDF_ERR( |
4723 | 0 | nc_inq_vardimid(nLocationGrpId, nLatitudeId, anDimLatitudeIds.data())); |
4724 | 0 | if (anDimLongitudeIds != anDimLatitudeIds) |
4725 | 0 | { |
4726 | 0 | return false; |
4727 | 0 | } |
4728 | | |
4729 | 0 | if (anDimLongitudeIds[0] != anVarDimIds[0] || |
4730 | 0 | anDimLongitudeIds[1] != anVarDimIds[1]) |
4731 | 0 | { |
4732 | 0 | return false; |
4733 | 0 | } |
4734 | | |
4735 | 0 | const char *pszGeolocXFullName = "/location/lon"; |
4736 | 0 | const char *pszGeolocYFullName = "/location/lat"; |
4737 | |
|
4738 | 0 | CPLDebug("GDAL_netCDF", "using variables %s and %s for GEOLOCATION", |
4739 | 0 | pszGeolocXFullName, pszGeolocYFullName); |
4740 | |
|
4741 | 0 | GDALPamDataset::SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG, |
4742 | 0 | "GEOLOCATION"); |
4743 | |
|
4744 | 0 | CPLString osTMP; |
4745 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocXFullName); |
4746 | |
|
4747 | 0 | GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, "GEOLOCATION"); |
4748 | 0 | GDALPamDataset::SetMetadataItem("X_BAND", "1", "GEOLOCATION"); |
4749 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), pszGeolocYFullName); |
4750 | |
|
4751 | 0 | GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, "GEOLOCATION"); |
4752 | 0 | GDALPamDataset::SetMetadataItem("Y_BAND", "1", "GEOLOCATION"); |
4753 | |
|
4754 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", "GEOLOCATION"); |
4755 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", "GEOLOCATION"); |
4756 | |
|
4757 | 0 | GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", "GEOLOCATION"); |
4758 | 0 | GDALPamDataset::SetMetadataItem("LINE_STEP", "1", "GEOLOCATION"); |
4759 | |
|
4760 | 0 | GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", "PIXEL_CENTER", |
4761 | 0 | "GEOLOCATION"); |
4762 | 0 | return true; |
4763 | 0 | } |
4764 | | |
4765 | | int netCDFDataset::ProcessCFGeolocation(int nGroupId, int nVarId, |
4766 | | const std::string &osGeolocWKT, |
4767 | | std::string &osGeolocXNameOut, |
4768 | | std::string &osGeolocYNameOut) |
4769 | 0 | { |
4770 | 0 | bool bAddGeoloc = false; |
4771 | 0 | char *pszCoordinates = nullptr; |
4772 | | |
4773 | | // If there is no explicit "coordinates" attribute, check if there are |
4774 | | // "lon" and "lat" 2D variables whose dimensions are the last |
4775 | | // 2 ones of the variable of interest. |
4776 | 0 | if (NCDFGetAttr(nGroupId, nVarId, "coordinates", &pszCoordinates) != |
4777 | 0 | CE_None) |
4778 | 0 | { |
4779 | 0 | CPLFree(pszCoordinates); |
4780 | 0 | pszCoordinates = nullptr; |
4781 | |
|
4782 | 0 | int nVarDims = 0; |
4783 | 0 | NCDF_ERR(nc_inq_varndims(nGroupId, nVarId, &nVarDims)); |
4784 | 0 | if (nVarDims >= 2) |
4785 | 0 | { |
4786 | 0 | std::vector<int> anVarDimIds(nVarDims); |
4787 | 0 | NCDF_ERR(nc_inq_vardimid(nGroupId, nVarId, anVarDimIds.data())); |
4788 | |
|
4789 | 0 | int nLongitudeId = 0; |
4790 | 0 | int nLatitudeId = 0; |
4791 | 0 | if (nc_inq_varid(nGroupId, "lon", &nLongitudeId) == NC_NOERR && |
4792 | 0 | nc_inq_varid(nGroupId, "lat", &nLatitudeId) == NC_NOERR) |
4793 | 0 | { |
4794 | 0 | int nDimsLongitude = 0; |
4795 | 0 | NCDF_ERR( |
4796 | 0 | nc_inq_varndims(nGroupId, nLongitudeId, &nDimsLongitude)); |
4797 | 0 | int nDimsLatitude = 0; |
4798 | 0 | NCDF_ERR( |
4799 | 0 | nc_inq_varndims(nGroupId, nLatitudeId, &nDimsLatitude)); |
4800 | 0 | if (nDimsLongitude == 2 && nDimsLatitude == 2) |
4801 | 0 | { |
4802 | 0 | std::vector<int> anDimLongitudeIds(2); |
4803 | 0 | NCDF_ERR(nc_inq_vardimid(nGroupId, nLongitudeId, |
4804 | 0 | anDimLongitudeIds.data())); |
4805 | 0 | std::vector<int> anDimLatitudeIds(2); |
4806 | 0 | NCDF_ERR(nc_inq_vardimid(nGroupId, nLatitudeId, |
4807 | 0 | anDimLatitudeIds.data())); |
4808 | 0 | if (anDimLongitudeIds == anDimLatitudeIds && |
4809 | 0 | anVarDimIds[anVarDimIds.size() - 2] == |
4810 | 0 | anDimLongitudeIds[0] && |
4811 | 0 | anVarDimIds[anVarDimIds.size() - 1] == |
4812 | 0 | anDimLongitudeIds[1]) |
4813 | 0 | { |
4814 | 0 | pszCoordinates = CPLStrdup("lon lat"); |
4815 | 0 | } |
4816 | 0 | } |
4817 | 0 | } |
4818 | 0 | } |
4819 | 0 | } |
4820 | |
|
4821 | 0 | if (pszCoordinates) |
4822 | 0 | { |
4823 | | // Get X and Y geolocation names from coordinates attribute. |
4824 | 0 | const CPLStringList aosCoordinates( |
4825 | 0 | NCDFTokenizeCoordinatesAttribute(pszCoordinates)); |
4826 | 0 | if (aosCoordinates.size() >= 2) |
4827 | 0 | { |
4828 | 0 | char szGeolocXName[NC_MAX_NAME + 1]; |
4829 | 0 | char szGeolocYName[NC_MAX_NAME + 1]; |
4830 | 0 | szGeolocXName[0] = '\0'; |
4831 | 0 | szGeolocYName[0] = '\0'; |
4832 | | |
4833 | | // Test that each variable is longitude/latitude. |
4834 | 0 | for (int i = 0; i < aosCoordinates.size(); i++) |
4835 | 0 | { |
4836 | 0 | if (NCDFIsVarLongitude(nGroupId, -1, aosCoordinates[i])) |
4837 | 0 | { |
4838 | 0 | int nOtherGroupId = -1; |
4839 | 0 | int nOtherVarId = -1; |
4840 | | // Check that the variable actually exists |
4841 | | // Needed on Sentinel-3 products |
4842 | 0 | if (NCDFResolveVar(nGroupId, aosCoordinates[i], |
4843 | 0 | &nOtherGroupId, &nOtherVarId) == CE_None) |
4844 | 0 | { |
4845 | 0 | snprintf(szGeolocXName, sizeof(szGeolocXName), "%s", |
4846 | 0 | aosCoordinates[i]); |
4847 | 0 | } |
4848 | 0 | } |
4849 | 0 | else if (NCDFIsVarLatitude(nGroupId, -1, aosCoordinates[i])) |
4850 | 0 | { |
4851 | 0 | int nOtherGroupId = -1; |
4852 | 0 | int nOtherVarId = -1; |
4853 | | // Check that the variable actually exists |
4854 | | // Needed on Sentinel-3 products |
4855 | 0 | if (NCDFResolveVar(nGroupId, aosCoordinates[i], |
4856 | 0 | &nOtherGroupId, &nOtherVarId) == CE_None) |
4857 | 0 | { |
4858 | 0 | snprintf(szGeolocYName, sizeof(szGeolocYName), "%s", |
4859 | 0 | aosCoordinates[i]); |
4860 | 0 | } |
4861 | 0 | } |
4862 | 0 | } |
4863 | | // Add GEOLOCATION metadata. |
4864 | 0 | if (!EQUAL(szGeolocXName, "") && !EQUAL(szGeolocYName, "")) |
4865 | 0 | { |
4866 | 0 | osGeolocXNameOut = szGeolocXName; |
4867 | 0 | osGeolocYNameOut = szGeolocYName; |
4868 | |
|
4869 | 0 | char *pszGeolocXFullName = nullptr; |
4870 | 0 | char *pszGeolocYFullName = nullptr; |
4871 | 0 | if (NCDFResolveVarFullName(nGroupId, szGeolocXName, |
4872 | 0 | &pszGeolocXFullName) == CE_None && |
4873 | 0 | NCDFResolveVarFullName(nGroupId, szGeolocYName, |
4874 | 0 | &pszGeolocYFullName) == CE_None) |
4875 | 0 | { |
4876 | 0 | if (bSwitchedXY) |
4877 | 0 | { |
4878 | 0 | std::swap(pszGeolocXFullName, pszGeolocYFullName); |
4879 | 0 | GDALPamDataset::SetMetadataItem("SWAP_XY", "YES", |
4880 | 0 | "GEOLOCATION"); |
4881 | 0 | } |
4882 | |
|
4883 | 0 | bAddGeoloc = true; |
4884 | 0 | CPLDebug("GDAL_netCDF", |
4885 | 0 | "using variables %s and %s for GEOLOCATION", |
4886 | 0 | pszGeolocXFullName, pszGeolocYFullName); |
4887 | |
|
4888 | 0 | GDALPamDataset::SetMetadataItem("SRS", osGeolocWKT.c_str(), |
4889 | 0 | "GEOLOCATION"); |
4890 | |
|
4891 | 0 | CPLString osTMP; |
4892 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), |
4893 | 0 | pszGeolocXFullName); |
4894 | |
|
4895 | 0 | GDALPamDataset::SetMetadataItem("X_DATASET", osTMP, |
4896 | 0 | "GEOLOCATION"); |
4897 | 0 | GDALPamDataset::SetMetadataItem("X_BAND", "1", |
4898 | 0 | "GEOLOCATION"); |
4899 | 0 | osTMP.Printf("NETCDF:\"%s\":%s", osFilename.c_str(), |
4900 | 0 | pszGeolocYFullName); |
4901 | |
|
4902 | 0 | GDALPamDataset::SetMetadataItem("Y_DATASET", osTMP, |
4903 | 0 | "GEOLOCATION"); |
4904 | 0 | GDALPamDataset::SetMetadataItem("Y_BAND", "1", |
4905 | 0 | "GEOLOCATION"); |
4906 | |
|
4907 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_OFFSET", "0", |
4908 | 0 | "GEOLOCATION"); |
4909 | 0 | GDALPamDataset::SetMetadataItem("PIXEL_STEP", "1", |
4910 | 0 | "GEOLOCATION"); |
4911 | |
|
4912 | 0 | GDALPamDataset::SetMetadataItem("LINE_OFFSET", "0", |
4913 | 0 | "GEOLOCATION"); |
4914 | 0 | GDALPamDataset::SetMetadataItem("LINE_STEP", "1", |
4915 | 0 | "GEOLOCATION"); |
4916 | |
|
4917 | 0 | GDALPamDataset::SetMetadataItem("GEOREFERENCING_CONVENTION", |
4918 | 0 | "PIXEL_CENTER", |
4919 | 0 | "GEOLOCATION"); |
4920 | 0 | } |
4921 | 0 | else |
4922 | 0 | { |
4923 | 0 | CPLDebug("GDAL_netCDF", |
4924 | 0 | "cannot resolve location of " |
4925 | 0 | "lat/lon variables specified by the coordinates " |
4926 | 0 | "attribute [%s]", |
4927 | 0 | pszCoordinates); |
4928 | 0 | } |
4929 | 0 | CPLFree(pszGeolocXFullName); |
4930 | 0 | CPLFree(pszGeolocYFullName); |
4931 | 0 | } |
4932 | 0 | else |
4933 | 0 | { |
4934 | 0 | CPLDebug("GDAL_netCDF", |
4935 | 0 | "coordinates attribute [%s] is unsupported", |
4936 | 0 | pszCoordinates); |
4937 | 0 | } |
4938 | 0 | } |
4939 | 0 | else |
4940 | 0 | { |
4941 | 0 | CPLDebug("GDAL_netCDF", |
4942 | 0 | "coordinates attribute [%s] with %d element(s) is " |
4943 | 0 | "unsupported", |
4944 | 0 | pszCoordinates, aosCoordinates.size()); |
4945 | 0 | } |
4946 | 0 | } |
4947 | | |
4948 | 0 | else |
4949 | 0 | { |
4950 | 0 | bAddGeoloc = ProcessNASAL2OceanGeoLocation(nGroupId, nVarId); |
4951 | |
|
4952 | 0 | if (!bAddGeoloc) |
4953 | 0 | bAddGeoloc = ProcessNASAEMITGeoLocation(nGroupId, nVarId); |
4954 | 0 | } |
4955 | |
|
4956 | 0 | CPLFree(pszCoordinates); |
4957 | |
|
4958 | 0 | return bAddGeoloc; |
4959 | 0 | } |
4960 | | |
4961 | | CPLErr netCDFDataset::Set1DGeolocation(int nGroupId, int nVarId, |
4962 | | const char *szDimName) |
4963 | 0 | { |
4964 | | // Get values. |
4965 | 0 | char *pszVarValues = nullptr; |
4966 | 0 | CPLErr eErr = NCDFGet1DVar(nGroupId, nVarId, &pszVarValues); |
4967 | 0 | if (eErr != CE_None) |
4968 | 0 | return eErr; |
4969 | | |
4970 | | // Write metadata. |
4971 | 0 | char szTemp[NC_MAX_NAME + 1 + 32] = {}; |
4972 | 0 | snprintf(szTemp, sizeof(szTemp), "%s_VALUES", szDimName); |
4973 | 0 | GDALPamDataset::SetMetadataItem(szTemp, pszVarValues, "GEOLOCATION2"); |
4974 | |
|
4975 | 0 | CPLFree(pszVarValues); |
4976 | |
|
4977 | 0 | return CE_None; |
4978 | 0 | } |
4979 | | |
4980 | | double *netCDFDataset::Get1DGeolocation(CPL_UNUSED const char *szDimName, |
4981 | | int &nVarLen) |
4982 | 0 | { |
4983 | 0 | nVarLen = 0; |
4984 | | |
4985 | | // Get Y_VALUES as tokens. |
4986 | 0 | char **papszValues = |
4987 | 0 | NCDFTokenizeArray(GetMetadataItem("Y_VALUES", "GEOLOCATION2")); |
4988 | 0 | if (papszValues == nullptr) |
4989 | 0 | return nullptr; |
4990 | | |
4991 | | // Initialize and fill array. |
4992 | 0 | nVarLen = CSLCount(papszValues); |
4993 | 0 | double *pdfVarValues = |
4994 | 0 | static_cast<double *>(CPLCalloc(nVarLen, sizeof(double))); |
4995 | |
|
4996 | 0 | for (int i = 0, j = 0; i < nVarLen; i++) |
4997 | 0 | { |
4998 | 0 | if (!bBottomUp) |
4999 | 0 | j = nVarLen - 1 - i; |
5000 | 0 | else |
5001 | 0 | j = i; // Invert latitude values. |
5002 | 0 | char *pszTemp = nullptr; |
5003 | 0 | pdfVarValues[j] = CPLStrtod(papszValues[i], &pszTemp); |
5004 | 0 | } |
5005 | 0 | CSLDestroy(papszValues); |
5006 | |
|
5007 | 0 | return pdfVarValues; |
5008 | 0 | } |
5009 | | |
5010 | | /************************************************************************/ |
5011 | | /* SetSpatialRefNoUpdate() */ |
5012 | | /************************************************************************/ |
5013 | | |
5014 | | void netCDFDataset::SetSpatialRefNoUpdate(const OGRSpatialReference *poSRS) |
5015 | 0 | { |
5016 | 0 | m_oSRS.Clear(); |
5017 | 0 | if (poSRS) |
5018 | 0 | m_oSRS = *poSRS; |
5019 | 0 | m_bHasProjection = true; |
5020 | 0 | } |
5021 | | |
5022 | | /************************************************************************/ |
5023 | | /* SetSpatialRef() */ |
5024 | | /************************************************************************/ |
5025 | | |
5026 | | CPLErr netCDFDataset::SetSpatialRef(const OGRSpatialReference *poSRS) |
5027 | 0 | { |
5028 | 0 | CPLMutexHolderD(&hNCMutex); |
5029 | |
|
5030 | 0 | if (GetAccess() != GA_Update || m_bHasProjection) |
5031 | 0 | { |
5032 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5033 | 0 | "netCDFDataset::_SetProjection() should only be called once " |
5034 | 0 | "in update mode!"); |
5035 | 0 | return CE_Failure; |
5036 | 0 | } |
5037 | | |
5038 | 0 | if (m_bHasGeoTransform) |
5039 | 0 | { |
5040 | 0 | SetSpatialRefNoUpdate(poSRS); |
5041 | | |
5042 | | // For NC4/NC4C, writing both projection variables and data, |
5043 | | // followed by redefining nodata value, cancels the projection |
5044 | | // info from the Band variable, so for now only write the |
5045 | | // variable definitions, and write data at the end. |
5046 | | // See https://trac.osgeo.org/gdal/ticket/7245 |
5047 | 0 | return AddProjectionVars(true, nullptr, nullptr); |
5048 | 0 | } |
5049 | | |
5050 | 0 | SetSpatialRefNoUpdate(poSRS); |
5051 | |
|
5052 | 0 | return CE_None; |
5053 | 0 | } |
5054 | | |
5055 | | /************************************************************************/ |
5056 | | /* SetGeoTransformNoUpdate() */ |
5057 | | /************************************************************************/ |
5058 | | |
5059 | | void netCDFDataset::SetGeoTransformNoUpdate(double *padfTransform) |
5060 | 0 | { |
5061 | 0 | memcpy(m_adfGeoTransform, padfTransform, sizeof(double) * 6); |
5062 | 0 | m_bHasGeoTransform = true; |
5063 | 0 | } |
5064 | | |
5065 | | /************************************************************************/ |
5066 | | /* SetGeoTransform() */ |
5067 | | /************************************************************************/ |
5068 | | |
5069 | | CPLErr netCDFDataset::SetGeoTransform(double *padfTransform) |
5070 | 0 | { |
5071 | 0 | CPLMutexHolderD(&hNCMutex); |
5072 | |
|
5073 | 0 | if (GetAccess() != GA_Update || m_bHasGeoTransform) |
5074 | 0 | { |
5075 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5076 | 0 | "netCDFDataset::SetGeoTransform() should only be called once " |
5077 | 0 | "in update mode!"); |
5078 | 0 | return CE_Failure; |
5079 | 0 | } |
5080 | | |
5081 | 0 | CPLDebug("GDAL_netCDF", "SetGeoTransform(%f,%f,%f,%f,%f,%f)", |
5082 | 0 | padfTransform[0], padfTransform[1], padfTransform[2], |
5083 | 0 | padfTransform[3], padfTransform[4], padfTransform[5]); |
5084 | |
|
5085 | 0 | if (m_bHasProjection) |
5086 | 0 | { |
5087 | 0 | SetGeoTransformNoUpdate(padfTransform); |
5088 | | |
5089 | | // For NC4/NC4C, writing both projection variables and data, |
5090 | | // followed by redefining nodata value, cancels the projection |
5091 | | // info from the Band variable, so for now only write the |
5092 | | // variable definitions, and write data at the end. |
5093 | | // See https://trac.osgeo.org/gdal/ticket/7245 |
5094 | 0 | return AddProjectionVars(true, nullptr, nullptr); |
5095 | 0 | } |
5096 | | |
5097 | 0 | SetGeoTransformNoUpdate(padfTransform); |
5098 | 0 | return CE_None; |
5099 | 0 | } |
5100 | | |
5101 | | /************************************************************************/ |
5102 | | /* NCDFWriteSRSVariable() */ |
5103 | | /************************************************************************/ |
5104 | | |
5105 | | int NCDFWriteSRSVariable(int cdfid, const OGRSpatialReference *poSRS, |
5106 | | char **ppszCFProjection, bool bWriteGDALTags, |
5107 | | const std::string &srsVarName) |
5108 | 0 | { |
5109 | 0 | char *pszCFProjection = nullptr; |
5110 | 0 | char **papszKeyValues = nullptr; |
5111 | 0 | poSRS->exportToCF1(&pszCFProjection, &papszKeyValues, nullptr, nullptr); |
5112 | |
|
5113 | 0 | if (bWriteGDALTags) |
5114 | 0 | { |
5115 | 0 | const char *pszWKT = CSLFetchNameValue(papszKeyValues, NCDF_CRS_WKT); |
5116 | 0 | if (pszWKT) |
5117 | 0 | { |
5118 | | // SPATIAL_REF is deprecated. Will be removed in GDAL 4. |
5119 | 0 | papszKeyValues = |
5120 | 0 | CSLSetNameValue(papszKeyValues, NCDF_SPATIAL_REF, pszWKT); |
5121 | 0 | } |
5122 | 0 | } |
5123 | |
|
5124 | 0 | const int nValues = CSLCount(papszKeyValues); |
5125 | |
|
5126 | 0 | int NCDFVarID; |
5127 | 0 | std::string varNameRadix(pszCFProjection); |
5128 | 0 | int nCounter = 2; |
5129 | 0 | while (true) |
5130 | 0 | { |
5131 | 0 | NCDFVarID = -1; |
5132 | 0 | nc_inq_varid(cdfid, pszCFProjection, &NCDFVarID); |
5133 | 0 | if (NCDFVarID < 0) |
5134 | 0 | break; |
5135 | | |
5136 | 0 | int nbAttr = 0; |
5137 | 0 | NCDF_ERR(nc_inq_varnatts(cdfid, NCDFVarID, &nbAttr)); |
5138 | 0 | bool bSame = nbAttr == nValues; |
5139 | 0 | for (int i = 0; bSame && (i < nbAttr); i++) |
5140 | 0 | { |
5141 | 0 | char szAttrName[NC_MAX_NAME + 1]; |
5142 | 0 | szAttrName[0] = 0; |
5143 | 0 | NCDF_ERR(nc_inq_attname(cdfid, NCDFVarID, i, szAttrName)); |
5144 | |
|
5145 | 0 | const char *pszValue = |
5146 | 0 | CSLFetchNameValue(papszKeyValues, szAttrName); |
5147 | 0 | if (!pszValue) |
5148 | 0 | { |
5149 | 0 | bSame = false; |
5150 | 0 | break; |
5151 | 0 | } |
5152 | | |
5153 | 0 | nc_type atttype = NC_NAT; |
5154 | 0 | size_t attlen = 0; |
5155 | 0 | NCDF_ERR( |
5156 | 0 | nc_inq_att(cdfid, NCDFVarID, szAttrName, &atttype, &attlen)); |
5157 | 0 | if (atttype != NC_CHAR && atttype != NC_DOUBLE) |
5158 | 0 | { |
5159 | 0 | bSame = false; |
5160 | 0 | break; |
5161 | 0 | } |
5162 | 0 | if (atttype == NC_CHAR) |
5163 | 0 | { |
5164 | 0 | if (CPLGetValueType(pszValue) != CPL_VALUE_STRING) |
5165 | 0 | { |
5166 | 0 | bSame = false; |
5167 | 0 | break; |
5168 | 0 | } |
5169 | 0 | std::string val; |
5170 | 0 | val.resize(attlen); |
5171 | 0 | nc_get_att_text(cdfid, NCDFVarID, szAttrName, &val[0]); |
5172 | 0 | if (val != pszValue) |
5173 | 0 | { |
5174 | 0 | bSame = false; |
5175 | 0 | break; |
5176 | 0 | } |
5177 | 0 | } |
5178 | 0 | else |
5179 | 0 | { |
5180 | 0 | const CPLStringList aosTokens( |
5181 | 0 | CSLTokenizeString2(pszValue, ",", 0)); |
5182 | 0 | if (static_cast<size_t>(aosTokens.size()) != attlen) |
5183 | 0 | { |
5184 | 0 | bSame = false; |
5185 | 0 | break; |
5186 | 0 | } |
5187 | 0 | double vals[2]; |
5188 | 0 | nc_get_att_double(cdfid, NCDFVarID, szAttrName, vals); |
5189 | 0 | if (vals[0] != CPLAtof(aosTokens[0]) || |
5190 | 0 | (attlen == 2 && vals[1] != CPLAtof(aosTokens[1]))) |
5191 | 0 | { |
5192 | 0 | bSame = false; |
5193 | 0 | break; |
5194 | 0 | } |
5195 | 0 | } |
5196 | 0 | } |
5197 | 0 | if (bSame) |
5198 | 0 | { |
5199 | 0 | *ppszCFProjection = pszCFProjection; |
5200 | 0 | CSLDestroy(papszKeyValues); |
5201 | 0 | return NCDFVarID; |
5202 | 0 | } |
5203 | 0 | CPLFree(pszCFProjection); |
5204 | 0 | pszCFProjection = |
5205 | 0 | CPLStrdup(CPLSPrintf("%s_%d", varNameRadix.c_str(), nCounter)); |
5206 | 0 | nCounter++; |
5207 | 0 | } |
5208 | | |
5209 | 0 | *ppszCFProjection = pszCFProjection; |
5210 | |
|
5211 | 0 | const char *pszVarName; |
5212 | |
|
5213 | 0 | if (srsVarName != "") |
5214 | 0 | { |
5215 | 0 | pszVarName = srsVarName.c_str(); |
5216 | 0 | } |
5217 | 0 | else |
5218 | 0 | { |
5219 | 0 | pszVarName = pszCFProjection; |
5220 | 0 | } |
5221 | |
|
5222 | 0 | int status = nc_def_var(cdfid, pszVarName, NC_CHAR, 0, nullptr, &NCDFVarID); |
5223 | 0 | NCDF_ERR(status); |
5224 | 0 | for (int i = 0; i < nValues; ++i) |
5225 | 0 | { |
5226 | 0 | char *pszKey = nullptr; |
5227 | 0 | const char *pszValue = CPLParseNameValue(papszKeyValues[i], &pszKey); |
5228 | 0 | if (pszKey && pszValue) |
5229 | 0 | { |
5230 | 0 | const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0)); |
5231 | 0 | double adfValues[2] = {0, 0}; |
5232 | 0 | const int nDoubleCount = std::min(2, aosTokens.size()); |
5233 | 0 | if (!(aosTokens.size() == 2 && |
5234 | 0 | CPLGetValueType(aosTokens[0]) != CPL_VALUE_STRING) && |
5235 | 0 | CPLGetValueType(pszValue) == CPL_VALUE_STRING) |
5236 | 0 | { |
5237 | 0 | status = nc_put_att_text(cdfid, NCDFVarID, pszKey, |
5238 | 0 | strlen(pszValue), pszValue); |
5239 | 0 | } |
5240 | 0 | else |
5241 | 0 | { |
5242 | 0 | for (int j = 0; j < nDoubleCount; ++j) |
5243 | 0 | adfValues[j] = CPLAtof(aosTokens[j]); |
5244 | 0 | status = nc_put_att_double(cdfid, NCDFVarID, pszKey, NC_DOUBLE, |
5245 | 0 | nDoubleCount, adfValues); |
5246 | 0 | } |
5247 | 0 | NCDF_ERR(status); |
5248 | 0 | } |
5249 | 0 | CPLFree(pszKey); |
5250 | 0 | } |
5251 | |
|
5252 | 0 | CSLDestroy(papszKeyValues); |
5253 | 0 | return NCDFVarID; |
5254 | 0 | } |
5255 | | |
5256 | | /************************************************************************/ |
5257 | | /* NCDFWriteLonLatVarsAttributes() */ |
5258 | | /************************************************************************/ |
5259 | | |
5260 | | void NCDFWriteLonLatVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarLonID, |
5261 | | int nVarLatID) |
5262 | 0 | { |
5263 | |
|
5264 | 0 | try |
5265 | 0 | { |
5266 | 0 | vcdf.nc_put_vatt_text(nVarLatID, CF_STD_NAME, CF_LATITUDE_STD_NAME); |
5267 | 0 | vcdf.nc_put_vatt_text(nVarLatID, CF_LNG_NAME, CF_LATITUDE_LNG_NAME); |
5268 | 0 | vcdf.nc_put_vatt_text(nVarLatID, CF_UNITS, CF_DEGREES_NORTH); |
5269 | 0 | vcdf.nc_put_vatt_text(nVarLonID, CF_STD_NAME, CF_LONGITUDE_STD_NAME); |
5270 | 0 | vcdf.nc_put_vatt_text(nVarLonID, CF_LNG_NAME, CF_LONGITUDE_LNG_NAME); |
5271 | 0 | vcdf.nc_put_vatt_text(nVarLonID, CF_UNITS, CF_DEGREES_EAST); |
5272 | 0 | } |
5273 | 0 | catch (nccfdriver::SG_Exception &e) |
5274 | 0 | { |
5275 | 0 | CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg()); |
5276 | 0 | } |
5277 | 0 | } |
5278 | | |
5279 | | /************************************************************************/ |
5280 | | /* NCDFWriteRLonRLatVarsAttributes() */ |
5281 | | /************************************************************************/ |
5282 | | |
5283 | | void NCDFWriteRLonRLatVarsAttributes(nccfdriver::netCDFVID &vcdf, |
5284 | | int nVarRLonID, int nVarRLatID) |
5285 | 0 | { |
5286 | 0 | try |
5287 | 0 | { |
5288 | 0 | vcdf.nc_put_vatt_text(nVarRLatID, CF_STD_NAME, "grid_latitude"); |
5289 | 0 | vcdf.nc_put_vatt_text(nVarRLatID, CF_LNG_NAME, |
5290 | 0 | "latitude in rotated pole grid"); |
5291 | 0 | vcdf.nc_put_vatt_text(nVarRLatID, CF_UNITS, "degrees"); |
5292 | 0 | vcdf.nc_put_vatt_text(nVarRLatID, CF_AXIS, "Y"); |
5293 | |
|
5294 | 0 | vcdf.nc_put_vatt_text(nVarRLonID, CF_STD_NAME, "grid_longitude"); |
5295 | 0 | vcdf.nc_put_vatt_text(nVarRLonID, CF_LNG_NAME, |
5296 | 0 | "longitude in rotated pole grid"); |
5297 | 0 | vcdf.nc_put_vatt_text(nVarRLonID, CF_UNITS, "degrees"); |
5298 | 0 | vcdf.nc_put_vatt_text(nVarRLonID, CF_AXIS, "X"); |
5299 | 0 | } |
5300 | 0 | catch (nccfdriver::SG_Exception &e) |
5301 | 0 | { |
5302 | 0 | CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg()); |
5303 | 0 | } |
5304 | 0 | } |
5305 | | |
5306 | | /************************************************************************/ |
5307 | | /* NCDFGetProjectedCFUnit() */ |
5308 | | /************************************************************************/ |
5309 | | |
5310 | | std::string NCDFGetProjectedCFUnit(const OGRSpatialReference *poSRS) |
5311 | 0 | { |
5312 | 0 | char *pszUnitsToWrite = nullptr; |
5313 | 0 | poSRS->exportToCF1(nullptr, nullptr, &pszUnitsToWrite, nullptr); |
5314 | 0 | std::string osRet = pszUnitsToWrite ? pszUnitsToWrite : std::string(); |
5315 | 0 | CPLFree(pszUnitsToWrite); |
5316 | 0 | return osRet; |
5317 | 0 | } |
5318 | | |
5319 | | /************************************************************************/ |
5320 | | /* NCDFWriteXYVarsAttributes() */ |
5321 | | /************************************************************************/ |
5322 | | |
5323 | | void NCDFWriteXYVarsAttributes(nccfdriver::netCDFVID &vcdf, int nVarXID, |
5324 | | int nVarYID, const OGRSpatialReference *poSRS) |
5325 | 0 | { |
5326 | 0 | const std::string osUnitsToWrite = NCDFGetProjectedCFUnit(poSRS); |
5327 | |
|
5328 | 0 | try |
5329 | 0 | { |
5330 | 0 | vcdf.nc_put_vatt_text(nVarXID, CF_STD_NAME, CF_PROJ_X_COORD); |
5331 | 0 | vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, CF_PROJ_X_COORD_LONG_NAME); |
5332 | 0 | if (!osUnitsToWrite.empty()) |
5333 | 0 | vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, osUnitsToWrite.c_str()); |
5334 | 0 | vcdf.nc_put_vatt_text(nVarYID, CF_STD_NAME, CF_PROJ_Y_COORD); |
5335 | 0 | vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, CF_PROJ_Y_COORD_LONG_NAME); |
5336 | 0 | if (!osUnitsToWrite.empty()) |
5337 | 0 | vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, osUnitsToWrite.c_str()); |
5338 | 0 | } |
5339 | 0 | catch (nccfdriver::SG_Exception &e) |
5340 | 0 | { |
5341 | 0 | CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg()); |
5342 | 0 | } |
5343 | 0 | } |
5344 | | |
5345 | | /************************************************************************/ |
5346 | | /* AddProjectionVars() */ |
5347 | | /************************************************************************/ |
5348 | | |
5349 | | CPLErr netCDFDataset::AddProjectionVars(bool bDefsOnly, |
5350 | | GDALProgressFunc pfnProgress, |
5351 | | void *pProgressData) |
5352 | 0 | { |
5353 | 0 | if (nCFVersion >= 1.8) |
5354 | 0 | return CE_None; // do nothing |
5355 | | |
5356 | 0 | bool bWriteGridMapping = false; |
5357 | 0 | bool bWriteLonLat = false; |
5358 | 0 | bool bHasGeoloc = false; |
5359 | 0 | bool bWriteGDALTags = false; |
5360 | 0 | bool bWriteGeoTransform = false; |
5361 | | |
5362 | | // For GEOLOCATION information. |
5363 | 0 | GDALDatasetH hDS_X = nullptr; |
5364 | 0 | GDALRasterBandH hBand_X = nullptr; |
5365 | 0 | GDALDatasetH hDS_Y = nullptr; |
5366 | 0 | GDALRasterBandH hBand_Y = nullptr; |
5367 | |
|
5368 | 0 | OGRSpatialReference oSRS(m_oSRS); |
5369 | 0 | if (!m_oSRS.IsEmpty()) |
5370 | 0 | { |
5371 | 0 | if (oSRS.IsProjected()) |
5372 | 0 | bIsProjected = true; |
5373 | 0 | else if (oSRS.IsGeographic()) |
5374 | 0 | bIsGeographic = true; |
5375 | 0 | } |
5376 | |
|
5377 | 0 | if (bDefsOnly) |
5378 | 0 | { |
5379 | 0 | char *pszProjection = nullptr; |
5380 | 0 | m_oSRS.exportToWkt(&pszProjection); |
5381 | 0 | CPLDebug("GDAL_netCDF", |
5382 | 0 | "SetProjection, WKT now = [%s]\nprojected: %d geographic: %d", |
5383 | 0 | pszProjection ? pszProjection : "(null)", |
5384 | 0 | static_cast<int>(bIsProjected), |
5385 | 0 | static_cast<int>(bIsGeographic)); |
5386 | 0 | CPLFree(pszProjection); |
5387 | |
|
5388 | 0 | if (!m_bHasGeoTransform) |
5389 | 0 | CPLDebug("GDAL_netCDF", |
5390 | 0 | "netCDFDataset::AddProjectionVars() called, " |
5391 | 0 | "but GeoTransform has not yet been defined!"); |
5392 | |
|
5393 | 0 | if (!m_bHasProjection) |
5394 | 0 | CPLDebug("GDAL_netCDF", |
5395 | 0 | "netCDFDataset::AddProjectionVars() called, " |
5396 | 0 | "but Projection has not yet been defined!"); |
5397 | 0 | } |
5398 | | |
5399 | | // Check GEOLOCATION information. |
5400 | 0 | char **papszGeolocationInfo = netCDFDataset::GetMetadata("GEOLOCATION"); |
5401 | 0 | if (papszGeolocationInfo != nullptr) |
5402 | 0 | { |
5403 | | // Look for geolocation datasets. |
5404 | 0 | const char *pszDSName = |
5405 | 0 | CSLFetchNameValue(papszGeolocationInfo, "X_DATASET"); |
5406 | 0 | if (pszDSName != nullptr) |
5407 | 0 | hDS_X = GDALOpenShared(pszDSName, GA_ReadOnly); |
5408 | 0 | pszDSName = CSLFetchNameValue(papszGeolocationInfo, "Y_DATASET"); |
5409 | 0 | if (pszDSName != nullptr) |
5410 | 0 | hDS_Y = GDALOpenShared(pszDSName, GA_ReadOnly); |
5411 | |
|
5412 | 0 | if (hDS_X != nullptr && hDS_Y != nullptr) |
5413 | 0 | { |
5414 | 0 | int nBand = std::max(1, atoi(CSLFetchNameValueDef( |
5415 | 0 | papszGeolocationInfo, "X_BAND", "0"))); |
5416 | 0 | hBand_X = GDALGetRasterBand(hDS_X, nBand); |
5417 | 0 | nBand = std::max(1, atoi(CSLFetchNameValueDef(papszGeolocationInfo, |
5418 | 0 | "Y_BAND", "0"))); |
5419 | 0 | hBand_Y = GDALGetRasterBand(hDS_Y, nBand); |
5420 | | |
5421 | | // If geoloc bands are found, do basic validation based on their |
5422 | | // dimensions. |
5423 | 0 | if (hBand_X != nullptr && hBand_Y != nullptr) |
5424 | 0 | { |
5425 | 0 | int nXSize_XBand = GDALGetRasterXSize(hDS_X); |
5426 | 0 | int nYSize_XBand = GDALGetRasterYSize(hDS_X); |
5427 | 0 | int nXSize_YBand = GDALGetRasterXSize(hDS_Y); |
5428 | 0 | int nYSize_YBand = GDALGetRasterYSize(hDS_Y); |
5429 | | |
5430 | | // TODO 1D geolocation arrays not implemented. |
5431 | 0 | if (nYSize_XBand == 1 && nYSize_YBand == 1) |
5432 | 0 | { |
5433 | 0 | bHasGeoloc = false; |
5434 | 0 | CPLDebug("GDAL_netCDF", |
5435 | 0 | "1D GEOLOCATION arrays not supported yet"); |
5436 | 0 | } |
5437 | | // 2D bands must have same sizes as the raster bands. |
5438 | 0 | else if (nXSize_XBand != nRasterXSize || |
5439 | 0 | nYSize_XBand != nRasterYSize || |
5440 | 0 | nXSize_YBand != nRasterXSize || |
5441 | 0 | nYSize_YBand != nRasterYSize) |
5442 | 0 | { |
5443 | 0 | bHasGeoloc = false; |
5444 | 0 | CPLDebug("GDAL_netCDF", |
5445 | 0 | "GEOLOCATION array sizes (%dx%d %dx%d) differ " |
5446 | 0 | "from raster (%dx%d), not supported", |
5447 | 0 | nXSize_XBand, nYSize_XBand, nXSize_YBand, |
5448 | 0 | nYSize_YBand, nRasterXSize, nRasterYSize); |
5449 | 0 | } |
5450 | 0 | else |
5451 | 0 | { |
5452 | 0 | bHasGeoloc = true; |
5453 | 0 | CPLDebug("GDAL_netCDF", |
5454 | 0 | "dataset has GEOLOCATION information, will try to " |
5455 | 0 | "write it"); |
5456 | 0 | } |
5457 | 0 | } |
5458 | 0 | } |
5459 | 0 | } |
5460 | | |
5461 | | // Process projection options. |
5462 | 0 | if (bIsProjected) |
5463 | 0 | { |
5464 | 0 | bool bIsCfProjection = |
5465 | 0 | oSRS.exportToCF1(nullptr, nullptr, nullptr, nullptr) == OGRERR_NONE; |
5466 | 0 | bWriteGridMapping = true; |
5467 | 0 | bWriteGDALTags = CPL_TO_BOOL( |
5468 | 0 | CSLFetchBoolean(papszCreationOptions, "WRITE_GDAL_TAGS", TRUE)); |
5469 | | // Force WRITE_GDAL_TAGS if is not a CF projection. |
5470 | 0 | if (!bWriteGDALTags && !bIsCfProjection) |
5471 | 0 | bWriteGDALTags = true; |
5472 | 0 | if (bWriteGDALTags) |
5473 | 0 | bWriteGeoTransform = true; |
5474 | | |
5475 | | // Write lon/lat: default is NO, except if has geolocation. |
5476 | | // With IF_NEEDED: write if has geoloc or is not CF projection. |
5477 | 0 | const char *pszValue = |
5478 | 0 | CSLFetchNameValue(papszCreationOptions, "WRITE_LONLAT"); |
5479 | 0 | if (pszValue) |
5480 | 0 | { |
5481 | 0 | if (EQUAL(pszValue, "IF_NEEDED")) |
5482 | 0 | { |
5483 | 0 | bWriteLonLat = bHasGeoloc || !bIsCfProjection; |
5484 | 0 | } |
5485 | 0 | else |
5486 | 0 | { |
5487 | 0 | bWriteLonLat = CPLTestBool(pszValue); |
5488 | 0 | } |
5489 | 0 | } |
5490 | 0 | else |
5491 | 0 | { |
5492 | 0 | bWriteLonLat = bHasGeoloc; |
5493 | 0 | } |
5494 | | |
5495 | | // Save value of pszCFCoordinates for later. |
5496 | 0 | if (bWriteLonLat) |
5497 | 0 | { |
5498 | 0 | pszCFCoordinates = NCDF_LONLAT; |
5499 | 0 | } |
5500 | 0 | } |
5501 | 0 | else |
5502 | 0 | { |
5503 | | // Files without a Datum will not have a grid_mapping variable and |
5504 | | // geographic information. |
5505 | 0 | bWriteGridMapping = bIsGeographic; |
5506 | |
|
5507 | 0 | if (bHasGeoloc) |
5508 | 0 | { |
5509 | 0 | bWriteLonLat = true; |
5510 | 0 | } |
5511 | 0 | else |
5512 | 0 | { |
5513 | 0 | bWriteGDALTags = CPL_TO_BOOL(CSLFetchBoolean( |
5514 | 0 | papszCreationOptions, "WRITE_GDAL_TAGS", bWriteGridMapping)); |
5515 | 0 | if (bWriteGDALTags) |
5516 | 0 | bWriteGeoTransform = true; |
5517 | |
|
5518 | 0 | const char *pszValue = CSLFetchNameValueDef(papszCreationOptions, |
5519 | 0 | "WRITE_LONLAT", "YES"); |
5520 | 0 | if (EQUAL(pszValue, "IF_NEEDED")) |
5521 | 0 | bWriteLonLat = true; |
5522 | 0 | else |
5523 | 0 | bWriteLonLat = CPLTestBool(pszValue); |
5524 | | // Don't write lon/lat if no source geotransform. |
5525 | 0 | if (!m_bHasGeoTransform) |
5526 | 0 | bWriteLonLat = false; |
5527 | | // If we don't write lon/lat, set dimnames to X/Y and write gdal |
5528 | | // tags. |
5529 | 0 | if (!bWriteLonLat) |
5530 | 0 | { |
5531 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
5532 | 0 | "creating geographic file without lon/lat values!"); |
5533 | 0 | if (m_bHasGeoTransform) |
5534 | 0 | { |
5535 | 0 | bWriteGDALTags = true; // Not desirable if no geotransform. |
5536 | 0 | bWriteGeoTransform = true; |
5537 | 0 | } |
5538 | 0 | } |
5539 | 0 | } |
5540 | 0 | } |
5541 | | |
5542 | | // Make sure we write grid_mapping if we need to write GDAL tags. |
5543 | 0 | if (bWriteGDALTags) |
5544 | 0 | bWriteGridMapping = true; |
5545 | | |
5546 | | // bottom-up value: new driver is bottom-up by default. |
5547 | | // Override with WRITE_BOTTOMUP. |
5548 | 0 | bBottomUp = CPL_TO_BOOL( |
5549 | 0 | CSLFetchBoolean(papszCreationOptions, "WRITE_BOTTOMUP", TRUE)); |
5550 | |
|
5551 | 0 | if (bDefsOnly) |
5552 | 0 | { |
5553 | 0 | CPLDebug( |
5554 | 0 | "GDAL_netCDF", |
5555 | 0 | "bIsProjected=%d bIsGeographic=%d bWriteGridMapping=%d " |
5556 | 0 | "bWriteGDALTags=%d bWriteLonLat=%d bBottomUp=%d bHasGeoloc=%d", |
5557 | 0 | static_cast<int>(bIsProjected), static_cast<int>(bIsGeographic), |
5558 | 0 | static_cast<int>(bWriteGridMapping), |
5559 | 0 | static_cast<int>(bWriteGDALTags), static_cast<int>(bWriteLonLat), |
5560 | 0 | static_cast<int>(bBottomUp), static_cast<int>(bHasGeoloc)); |
5561 | 0 | } |
5562 | | |
5563 | | // Exit if nothing to do. |
5564 | 0 | if (!bIsProjected && !bWriteLonLat) |
5565 | 0 | return CE_None; |
5566 | | |
5567 | | // Define dimension names. |
5568 | | |
5569 | 0 | constexpr const char *ROTATED_POLE_VAR_NAME = "rotated_pole"; |
5570 | |
|
5571 | 0 | if (bDefsOnly) |
5572 | 0 | { |
5573 | 0 | int nVarLonID = -1; |
5574 | 0 | int nVarLatID = -1; |
5575 | 0 | int nVarXID = -1; |
5576 | 0 | int nVarYID = -1; |
5577 | |
|
5578 | 0 | m_bAddedProjectionVarsDefs = true; |
5579 | | |
5580 | | // Make sure we are in define mode. |
5581 | 0 | SetDefineMode(true); |
5582 | | |
5583 | | // Write projection attributes. |
5584 | 0 | if (bWriteGridMapping) |
5585 | 0 | { |
5586 | 0 | const int NCDFVarID = NCDFWriteSRSVariable( |
5587 | 0 | cdfid, &oSRS, &pszCFProjection, bWriteGDALTags); |
5588 | 0 | if (NCDFVarID < 0) |
5589 | 0 | return CE_Failure; |
5590 | | |
5591 | | // Optional GDAL custom projection tags. |
5592 | 0 | if (bWriteGDALTags) |
5593 | 0 | { |
5594 | 0 | CPLString osGeoTransform; |
5595 | 0 | for (int i = 0; i < 6; i++) |
5596 | 0 | { |
5597 | 0 | osGeoTransform += |
5598 | 0 | CPLSPrintf("%.17g ", m_adfGeoTransform[i]); |
5599 | 0 | } |
5600 | 0 | CPLDebug("GDAL_netCDF", "szGeoTransform = %s", |
5601 | 0 | osGeoTransform.c_str()); |
5602 | | |
5603 | | // if( strlen(pszProj4Defn) > 0 ) { |
5604 | | // nc_put_att_text(cdfid, NCDFVarID, "proj4", |
5605 | | // strlen(pszProj4Defn), pszProj4Defn); |
5606 | | // } |
5607 | | |
5608 | | // For now, write the geotransform for back-compat or else |
5609 | | // the old (1.8.1) driver overrides the CF geotransform with |
5610 | | // empty values from dfNN, dfSN, dfEE, dfWE; |
5611 | | |
5612 | | // TODO: fix this in 1.8 branch, and then remove this here. |
5613 | 0 | if (bWriteGeoTransform && m_bHasGeoTransform) |
5614 | 0 | { |
5615 | 0 | { |
5616 | 0 | const int status = nc_put_att_text( |
5617 | 0 | cdfid, NCDFVarID, NCDF_GEOTRANSFORM, |
5618 | 0 | osGeoTransform.size(), osGeoTransform.c_str()); |
5619 | 0 | NCDF_ERR(status); |
5620 | 0 | } |
5621 | 0 | } |
5622 | 0 | } |
5623 | | |
5624 | | // Write projection variable to band variable. |
5625 | | // Need to call later if there are no bands. |
5626 | 0 | AddGridMappingRef(); |
5627 | 0 | } // end if( bWriteGridMapping ) |
5628 | | |
5629 | | // Write CF Projection vars. |
5630 | | |
5631 | 0 | const bool bIsRotatedPole = |
5632 | 0 | pszCFProjection != nullptr && |
5633 | 0 | EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME); |
5634 | 0 | if (bIsRotatedPole) |
5635 | 0 | { |
5636 | | // Rename dims to rlat/rlon. |
5637 | 0 | papszDimName |
5638 | 0 | .Clear(); // If we add other dims one day, this has to change |
5639 | 0 | papszDimName.AddString(NCDF_DIMNAME_RLAT); |
5640 | 0 | papszDimName.AddString(NCDF_DIMNAME_RLON); |
5641 | |
|
5642 | 0 | int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_RLAT); |
5643 | 0 | NCDF_ERR(status); |
5644 | 0 | status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_RLON); |
5645 | 0 | NCDF_ERR(status); |
5646 | 0 | } |
5647 | | // Rename dimensions if lon/lat. |
5648 | 0 | else if (!bIsProjected && !bHasGeoloc) |
5649 | 0 | { |
5650 | | // Rename dims to lat/lon. |
5651 | 0 | papszDimName |
5652 | 0 | .Clear(); // If we add other dims one day, this has to change |
5653 | 0 | papszDimName.AddString(NCDF_DIMNAME_LAT); |
5654 | 0 | papszDimName.AddString(NCDF_DIMNAME_LON); |
5655 | |
|
5656 | 0 | int status = nc_rename_dim(cdfid, nYDimID, NCDF_DIMNAME_LAT); |
5657 | 0 | NCDF_ERR(status); |
5658 | 0 | status = nc_rename_dim(cdfid, nXDimID, NCDF_DIMNAME_LON); |
5659 | 0 | NCDF_ERR(status); |
5660 | 0 | } |
5661 | | |
5662 | | // Write X/Y attributes. |
5663 | 0 | else /* if( bIsProjected || bHasGeoloc ) */ |
5664 | 0 | { |
5665 | | // X |
5666 | 0 | int anXDims[1]; |
5667 | 0 | anXDims[0] = nXDimID; |
5668 | 0 | CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid, |
5669 | 0 | CF_PROJ_X_VAR_NAME, NC_DOUBLE); |
5670 | 0 | int status = nc_def_var(cdfid, CF_PROJ_X_VAR_NAME, NC_DOUBLE, 1, |
5671 | 0 | anXDims, &nVarXID); |
5672 | 0 | NCDF_ERR(status); |
5673 | | |
5674 | | // Y |
5675 | 0 | int anYDims[1]; |
5676 | 0 | anYDims[0] = nYDimID; |
5677 | 0 | CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d)", cdfid, |
5678 | 0 | CF_PROJ_Y_VAR_NAME, NC_DOUBLE); |
5679 | 0 | status = nc_def_var(cdfid, CF_PROJ_Y_VAR_NAME, NC_DOUBLE, 1, |
5680 | 0 | anYDims, &nVarYID); |
5681 | 0 | NCDF_ERR(status); |
5682 | |
|
5683 | 0 | if (bIsProjected) |
5684 | 0 | { |
5685 | 0 | NCDFWriteXYVarsAttributes(this->vcdf, nVarXID, nVarYID, &oSRS); |
5686 | 0 | } |
5687 | 0 | else |
5688 | 0 | { |
5689 | 0 | CPLAssert(bHasGeoloc); |
5690 | 0 | try |
5691 | 0 | { |
5692 | 0 | vcdf.nc_put_vatt_text(nVarXID, CF_AXIS, CF_SG_X_AXIS); |
5693 | 0 | vcdf.nc_put_vatt_text(nVarXID, CF_LNG_NAME, |
5694 | 0 | "x-coordinate in Cartesian system"); |
5695 | 0 | vcdf.nc_put_vatt_text(nVarXID, CF_UNITS, "m"); |
5696 | 0 | vcdf.nc_put_vatt_text(nVarYID, CF_AXIS, CF_SG_Y_AXIS); |
5697 | 0 | vcdf.nc_put_vatt_text(nVarYID, CF_LNG_NAME, |
5698 | 0 | "y-coordinate in Cartesian system"); |
5699 | 0 | vcdf.nc_put_vatt_text(nVarYID, CF_UNITS, "m"); |
5700 | |
|
5701 | 0 | pszCFCoordinates = NCDF_LONLAT; |
5702 | 0 | } |
5703 | 0 | catch (nccfdriver::SG_Exception &e) |
5704 | 0 | { |
5705 | 0 | CPLError(CE_Failure, CPLE_FileIO, "%s", e.get_err_msg()); |
5706 | 0 | return CE_Failure; |
5707 | 0 | } |
5708 | 0 | } |
5709 | 0 | } |
5710 | | |
5711 | | // Write lat/lon attributes if needed. |
5712 | 0 | if (bWriteLonLat) |
5713 | 0 | { |
5714 | 0 | int *panLatDims = nullptr; |
5715 | 0 | int *panLonDims = nullptr; |
5716 | 0 | int nLatDims = -1; |
5717 | 0 | int nLonDims = -1; |
5718 | | |
5719 | | // Get information. |
5720 | 0 | if (bHasGeoloc) |
5721 | 0 | { |
5722 | | // Geoloc |
5723 | 0 | nLatDims = 2; |
5724 | 0 | panLatDims = |
5725 | 0 | static_cast<int *>(CPLCalloc(nLatDims, sizeof(int))); |
5726 | 0 | panLatDims[0] = nYDimID; |
5727 | 0 | panLatDims[1] = nXDimID; |
5728 | 0 | nLonDims = 2; |
5729 | 0 | panLonDims = |
5730 | 0 | static_cast<int *>(CPLCalloc(nLonDims, sizeof(int))); |
5731 | 0 | panLonDims[0] = nYDimID; |
5732 | 0 | panLonDims[1] = nXDimID; |
5733 | 0 | } |
5734 | 0 | else if (bIsProjected) |
5735 | 0 | { |
5736 | | // Projected |
5737 | 0 | nLatDims = 2; |
5738 | 0 | panLatDims = |
5739 | 0 | static_cast<int *>(CPLCalloc(nLatDims, sizeof(int))); |
5740 | 0 | panLatDims[0] = nYDimID; |
5741 | 0 | panLatDims[1] = nXDimID; |
5742 | 0 | nLonDims = 2; |
5743 | 0 | panLonDims = |
5744 | 0 | static_cast<int *>(CPLCalloc(nLonDims, sizeof(int))); |
5745 | 0 | panLonDims[0] = nYDimID; |
5746 | 0 | panLonDims[1] = nXDimID; |
5747 | 0 | } |
5748 | 0 | else |
5749 | 0 | { |
5750 | | // Geographic |
5751 | 0 | nLatDims = 1; |
5752 | 0 | panLatDims = |
5753 | 0 | static_cast<int *>(CPLCalloc(nLatDims, sizeof(int))); |
5754 | 0 | panLatDims[0] = nYDimID; |
5755 | 0 | nLonDims = 1; |
5756 | 0 | panLonDims = |
5757 | 0 | static_cast<int *>(CPLCalloc(nLonDims, sizeof(int))); |
5758 | 0 | panLonDims[0] = nXDimID; |
5759 | 0 | } |
5760 | |
|
5761 | 0 | nc_type eLonLatType = NC_NAT; |
5762 | 0 | if (bIsProjected) |
5763 | 0 | { |
5764 | 0 | eLonLatType = NC_FLOAT; |
5765 | 0 | const char *pszValue = CSLFetchNameValueDef( |
5766 | 0 | papszCreationOptions, "TYPE_LONLAT", "FLOAT"); |
5767 | 0 | if (EQUAL(pszValue, "DOUBLE")) |
5768 | 0 | eLonLatType = NC_DOUBLE; |
5769 | 0 | } |
5770 | 0 | else |
5771 | 0 | { |
5772 | 0 | eLonLatType = NC_DOUBLE; |
5773 | 0 | const char *pszValue = CSLFetchNameValueDef( |
5774 | 0 | papszCreationOptions, "TYPE_LONLAT", "DOUBLE"); |
5775 | 0 | if (EQUAL(pszValue, "FLOAT")) |
5776 | 0 | eLonLatType = NC_FLOAT; |
5777 | 0 | } |
5778 | | |
5779 | | // Def vars and attributes. |
5780 | 0 | { |
5781 | 0 | const char *pszVarName = |
5782 | 0 | bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME; |
5783 | 0 | int status = nc_def_var(cdfid, pszVarName, eLonLatType, |
5784 | 0 | nLatDims, panLatDims, &nVarLatID); |
5785 | 0 | CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d", |
5786 | 0 | cdfid, pszVarName, eLonLatType, nLatDims, nVarLatID); |
5787 | 0 | NCDF_ERR(status); |
5788 | 0 | DefVarDeflate(nVarLatID, false); // Don't set chunking. |
5789 | 0 | } |
5790 | |
|
5791 | 0 | { |
5792 | 0 | const char *pszVarName = |
5793 | 0 | bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME; |
5794 | 0 | int status = nc_def_var(cdfid, pszVarName, eLonLatType, |
5795 | 0 | nLonDims, panLonDims, &nVarLonID); |
5796 | 0 | CPLDebug("GDAL_netCDF", "nc_def_var(%d,%s,%d,%d,-,-) got id %d", |
5797 | 0 | cdfid, pszVarName, eLonLatType, nLatDims, nVarLonID); |
5798 | 0 | NCDF_ERR(status); |
5799 | 0 | DefVarDeflate(nVarLonID, false); // Don't set chunking. |
5800 | 0 | } |
5801 | |
|
5802 | 0 | if (bIsRotatedPole) |
5803 | 0 | NCDFWriteRLonRLatVarsAttributes(this->vcdf, nVarLonID, |
5804 | 0 | nVarLatID); |
5805 | 0 | else |
5806 | 0 | NCDFWriteLonLatVarsAttributes(this->vcdf, nVarLonID, nVarLatID); |
5807 | |
|
5808 | 0 | CPLFree(panLatDims); |
5809 | 0 | CPLFree(panLonDims); |
5810 | 0 | } |
5811 | 0 | } |
5812 | | |
5813 | 0 | if (!bDefsOnly) |
5814 | 0 | { |
5815 | 0 | m_bAddedProjectionVarsData = true; |
5816 | |
|
5817 | 0 | int nVarXID = -1; |
5818 | 0 | int nVarYID = -1; |
5819 | |
|
5820 | 0 | nc_inq_varid(cdfid, CF_PROJ_X_VAR_NAME, &nVarXID); |
5821 | 0 | nc_inq_varid(cdfid, CF_PROJ_Y_VAR_NAME, &nVarYID); |
5822 | |
|
5823 | 0 | int nVarLonID = -1; |
5824 | 0 | int nVarLatID = -1; |
5825 | |
|
5826 | 0 | const bool bIsRotatedPole = |
5827 | 0 | pszCFProjection != nullptr && |
5828 | 0 | EQUAL(pszCFProjection, ROTATED_POLE_VAR_NAME); |
5829 | 0 | nc_inq_varid(cdfid, |
5830 | 0 | bIsRotatedPole ? NCDF_DIMNAME_RLON : CF_LONGITUDE_VAR_NAME, |
5831 | 0 | &nVarLonID); |
5832 | 0 | nc_inq_varid(cdfid, |
5833 | 0 | bIsRotatedPole ? NCDF_DIMNAME_RLAT : CF_LATITUDE_VAR_NAME, |
5834 | 0 | &nVarLatID); |
5835 | | |
5836 | | // Get projection values. |
5837 | |
|
5838 | 0 | double *padLonVal = nullptr; |
5839 | 0 | double *padLatVal = nullptr; |
5840 | |
|
5841 | 0 | if (bIsProjected) |
5842 | 0 | { |
5843 | 0 | OGRSpatialReference *poLatLonSRS = nullptr; |
5844 | 0 | OGRCoordinateTransformation *poTransform = nullptr; |
5845 | |
|
5846 | 0 | size_t startX[1]; |
5847 | 0 | size_t countX[1]; |
5848 | 0 | size_t startY[1]; |
5849 | 0 | size_t countY[1]; |
5850 | |
|
5851 | 0 | CPLDebug("GDAL_netCDF", "Getting (X,Y) values"); |
5852 | |
|
5853 | 0 | double *padXVal = |
5854 | 0 | static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double))); |
5855 | 0 | double *padYVal = |
5856 | 0 | static_cast<double *>(CPLMalloc(nRasterYSize * sizeof(double))); |
5857 | | |
5858 | | // Get Y values. |
5859 | 0 | const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] : |
5860 | | // Invert latitude values. |
5861 | 0 | m_adfGeoTransform[3] + |
5862 | 0 | (m_adfGeoTransform[5] * nRasterYSize); |
5863 | 0 | const double dfDY = m_adfGeoTransform[5]; |
5864 | |
|
5865 | 0 | for (int j = 0; j < nRasterYSize; j++) |
5866 | 0 | { |
5867 | | // The data point is centered inside the pixel. |
5868 | 0 | if (!bBottomUp) |
5869 | 0 | padYVal[j] = dfY0 + (j + 0.5) * dfDY; |
5870 | 0 | else // Invert latitude values. |
5871 | 0 | padYVal[j] = dfY0 - (j + 0.5) * dfDY; |
5872 | 0 | } |
5873 | 0 | startX[0] = 0; |
5874 | 0 | countX[0] = nRasterXSize; |
5875 | | |
5876 | | // Get X values. |
5877 | 0 | const double dfX0 = m_adfGeoTransform[0]; |
5878 | 0 | const double dfDX = m_adfGeoTransform[1]; |
5879 | |
|
5880 | 0 | for (int i = 0; i < nRasterXSize; i++) |
5881 | 0 | { |
5882 | | // The data point is centered inside the pixel. |
5883 | 0 | padXVal[i] = dfX0 + (i + 0.5) * dfDX; |
5884 | 0 | } |
5885 | 0 | startY[0] = 0; |
5886 | 0 | countY[0] = nRasterYSize; |
5887 | | |
5888 | | // Write X/Y values. |
5889 | | |
5890 | | // Make sure we are in data mode. |
5891 | 0 | SetDefineMode(false); |
5892 | |
|
5893 | 0 | CPLDebug("GDAL_netCDF", "Writing X values"); |
5894 | 0 | int status = |
5895 | 0 | nc_put_vara_double(cdfid, nVarXID, startX, countX, padXVal); |
5896 | 0 | NCDF_ERR(status); |
5897 | |
|
5898 | 0 | CPLDebug("GDAL_netCDF", "Writing Y values"); |
5899 | 0 | status = |
5900 | 0 | nc_put_vara_double(cdfid, nVarYID, startY, countY, padYVal); |
5901 | 0 | NCDF_ERR(status); |
5902 | |
|
5903 | 0 | if (pfnProgress) |
5904 | 0 | pfnProgress(0.20, nullptr, pProgressData); |
5905 | | |
5906 | | // Write lon/lat arrays (CF coordinates) if requested. |
5907 | | |
5908 | | // Get OGR transform if GEOLOCATION is not available. |
5909 | 0 | if (bWriteLonLat && !bHasGeoloc) |
5910 | 0 | { |
5911 | 0 | poLatLonSRS = m_oSRS.CloneGeogCS(); |
5912 | 0 | if (poLatLonSRS != nullptr) |
5913 | 0 | { |
5914 | 0 | poLatLonSRS->SetAxisMappingStrategy( |
5915 | 0 | OAMS_TRADITIONAL_GIS_ORDER); |
5916 | 0 | poTransform = |
5917 | 0 | OGRCreateCoordinateTransformation(&m_oSRS, poLatLonSRS); |
5918 | 0 | } |
5919 | | // If no OGR transform, then don't write CF lon/lat. |
5920 | 0 | if (poTransform == nullptr) |
5921 | 0 | { |
5922 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5923 | 0 | "Unable to get Coordinate Transform"); |
5924 | 0 | bWriteLonLat = false; |
5925 | 0 | } |
5926 | 0 | } |
5927 | |
|
5928 | 0 | if (bWriteLonLat) |
5929 | 0 | { |
5930 | 0 | if (!bHasGeoloc) |
5931 | 0 | CPLDebug("GDAL_netCDF", "Transforming (X,Y)->(lon,lat)"); |
5932 | 0 | else |
5933 | 0 | CPLDebug("GDAL_netCDF", |
5934 | 0 | "Writing (lon,lat) from GEOLOCATION arrays"); |
5935 | |
|
5936 | 0 | bool bOK = true; |
5937 | 0 | double dfProgress = 0.2; |
5938 | |
|
5939 | 0 | size_t start[] = {0, 0}; |
5940 | 0 | size_t count[] = {1, (size_t)nRasterXSize}; |
5941 | 0 | padLatVal = static_cast<double *>( |
5942 | 0 | CPLMalloc(nRasterXSize * sizeof(double))); |
5943 | 0 | padLonVal = static_cast<double *>( |
5944 | 0 | CPLMalloc(nRasterXSize * sizeof(double))); |
5945 | |
|
5946 | 0 | for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; |
5947 | 0 | j++) |
5948 | 0 | { |
5949 | 0 | start[0] = j; |
5950 | | |
5951 | | // Get values from geotransform. |
5952 | 0 | if (!bHasGeoloc) |
5953 | 0 | { |
5954 | | // Fill values to transform. |
5955 | 0 | for (int i = 0; i < nRasterXSize; i++) |
5956 | 0 | { |
5957 | 0 | padLatVal[i] = padYVal[j]; |
5958 | 0 | padLonVal[i] = padXVal[i]; |
5959 | 0 | } |
5960 | | |
5961 | | // Do the transform. |
5962 | 0 | bOK = CPL_TO_BOOL(poTransform->Transform( |
5963 | 0 | nRasterXSize, padLonVal, padLatVal, nullptr)); |
5964 | 0 | if (!bOK) |
5965 | 0 | { |
5966 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5967 | 0 | "Unable to Transform (X,Y) to (lon,lat)."); |
5968 | 0 | } |
5969 | 0 | } |
5970 | | // Get values from geoloc arrays. |
5971 | 0 | else |
5972 | 0 | { |
5973 | 0 | CPLErr eErr = GDALRasterIO( |
5974 | 0 | hBand_Y, GF_Read, 0, j, nRasterXSize, 1, padLatVal, |
5975 | 0 | nRasterXSize, 1, GDT_Float64, 0, 0); |
5976 | 0 | if (eErr == CE_None) |
5977 | 0 | { |
5978 | 0 | eErr = GDALRasterIO( |
5979 | 0 | hBand_X, GF_Read, 0, j, nRasterXSize, 1, |
5980 | 0 | padLonVal, nRasterXSize, 1, GDT_Float64, 0, 0); |
5981 | 0 | } |
5982 | |
|
5983 | 0 | if (eErr == CE_None) |
5984 | 0 | { |
5985 | 0 | bOK = true; |
5986 | 0 | } |
5987 | 0 | else |
5988 | 0 | { |
5989 | 0 | bOK = false; |
5990 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
5991 | 0 | "Unable to get scanline %d", j); |
5992 | 0 | } |
5993 | 0 | } |
5994 | | |
5995 | | // Write data. |
5996 | 0 | if (bOK) |
5997 | 0 | { |
5998 | 0 | status = nc_put_vara_double(cdfid, nVarLatID, start, |
5999 | 0 | count, padLatVal); |
6000 | 0 | NCDF_ERR(status); |
6001 | 0 | status = nc_put_vara_double(cdfid, nVarLonID, start, |
6002 | 0 | count, padLonVal); |
6003 | 0 | NCDF_ERR(status); |
6004 | 0 | } |
6005 | |
|
6006 | 0 | if (pfnProgress && (nRasterYSize / 10) > 0 && |
6007 | 0 | (j % (nRasterYSize / 10) == 0)) |
6008 | 0 | { |
6009 | 0 | dfProgress += 0.08; |
6010 | 0 | pfnProgress(dfProgress, nullptr, pProgressData); |
6011 | 0 | } |
6012 | 0 | } |
6013 | 0 | } |
6014 | |
|
6015 | 0 | if (poLatLonSRS != nullptr) |
6016 | 0 | delete poLatLonSRS; |
6017 | 0 | if (poTransform != nullptr) |
6018 | 0 | delete poTransform; |
6019 | |
|
6020 | 0 | CPLFree(padXVal); |
6021 | 0 | CPLFree(padYVal); |
6022 | 0 | } // Projected |
6023 | | |
6024 | | // If not projected/geographic and has geoloc |
6025 | 0 | else if (!bIsGeographic && bHasGeoloc) |
6026 | 0 | { |
6027 | | // Use |
6028 | | // https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#_two_dimensional_latitude_longitude_coordinate_variables |
6029 | |
|
6030 | 0 | bool bOK = true; |
6031 | 0 | double dfProgress = 0.2; |
6032 | | |
6033 | | // Make sure we are in data mode. |
6034 | 0 | SetDefineMode(false); |
6035 | |
|
6036 | 0 | size_t startX[1]; |
6037 | 0 | size_t countX[1]; |
6038 | 0 | size_t startY[1]; |
6039 | 0 | size_t countY[1]; |
6040 | 0 | startX[0] = 0; |
6041 | 0 | countX[0] = nRasterXSize; |
6042 | |
|
6043 | 0 | startY[0] = 0; |
6044 | 0 | countY[0] = nRasterYSize; |
6045 | |
|
6046 | 0 | std::vector<double> adfXVal(nRasterXSize); |
6047 | 0 | for (int i = 0; i < nRasterXSize; i++) |
6048 | 0 | adfXVal[i] = i; |
6049 | |
|
6050 | 0 | std::vector<double> adfYVal(nRasterYSize); |
6051 | 0 | for (int i = 0; i < nRasterYSize; i++) |
6052 | 0 | adfYVal[i] = bBottomUp ? nRasterYSize - 1 - i : i; |
6053 | |
|
6054 | 0 | CPLDebug("GDAL_netCDF", "Writing X values"); |
6055 | 0 | int status = nc_put_vara_double(cdfid, nVarXID, startX, countX, |
6056 | 0 | adfXVal.data()); |
6057 | 0 | NCDF_ERR(status); |
6058 | |
|
6059 | 0 | CPLDebug("GDAL_netCDF", "Writing Y values"); |
6060 | 0 | status = nc_put_vara_double(cdfid, nVarYID, startY, countY, |
6061 | 0 | adfYVal.data()); |
6062 | 0 | NCDF_ERR(status); |
6063 | |
|
6064 | 0 | if (pfnProgress) |
6065 | 0 | pfnProgress(0.20, nullptr, pProgressData); |
6066 | |
|
6067 | 0 | size_t start[] = {0, 0}; |
6068 | 0 | size_t count[] = {1, (size_t)nRasterXSize}; |
6069 | 0 | padLatVal = |
6070 | 0 | static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double))); |
6071 | 0 | padLonVal = |
6072 | 0 | static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double))); |
6073 | |
|
6074 | 0 | for (int j = 0; j < nRasterYSize && bOK && status == NC_NOERR; j++) |
6075 | 0 | { |
6076 | 0 | start[0] = j; |
6077 | |
|
6078 | 0 | CPLErr eErr = GDALRasterIO(hBand_Y, GF_Read, 0, |
6079 | 0 | bBottomUp ? nRasterYSize - 1 - j : j, |
6080 | 0 | nRasterXSize, 1, padLatVal, |
6081 | 0 | nRasterXSize, 1, GDT_Float64, 0, 0); |
6082 | 0 | if (eErr == CE_None) |
6083 | 0 | { |
6084 | 0 | eErr = GDALRasterIO(hBand_X, GF_Read, 0, |
6085 | 0 | bBottomUp ? nRasterYSize - 1 - j : j, |
6086 | 0 | nRasterXSize, 1, padLonVal, |
6087 | 0 | nRasterXSize, 1, GDT_Float64, 0, 0); |
6088 | 0 | } |
6089 | |
|
6090 | 0 | if (eErr == CE_None) |
6091 | 0 | { |
6092 | 0 | bOK = true; |
6093 | 0 | } |
6094 | 0 | else |
6095 | 0 | { |
6096 | 0 | bOK = false; |
6097 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
6098 | 0 | "Unable to get scanline %d", j); |
6099 | 0 | } |
6100 | | |
6101 | | // Write data. |
6102 | 0 | if (bOK) |
6103 | 0 | { |
6104 | 0 | status = nc_put_vara_double(cdfid, nVarLatID, start, count, |
6105 | 0 | padLatVal); |
6106 | 0 | NCDF_ERR(status); |
6107 | 0 | status = nc_put_vara_double(cdfid, nVarLonID, start, count, |
6108 | 0 | padLonVal); |
6109 | 0 | NCDF_ERR(status); |
6110 | 0 | } |
6111 | |
|
6112 | 0 | if (pfnProgress && (nRasterYSize / 10) > 0 && |
6113 | 0 | (j % (nRasterYSize / 10) == 0)) |
6114 | 0 | { |
6115 | 0 | dfProgress += 0.08; |
6116 | 0 | pfnProgress(dfProgress, nullptr, pProgressData); |
6117 | 0 | } |
6118 | 0 | } |
6119 | 0 | } |
6120 | | |
6121 | | // If not projected, assume geographic to catch grids without Datum. |
6122 | 0 | else if (bWriteLonLat) |
6123 | 0 | { |
6124 | | // Get latitude values. |
6125 | 0 | const double dfY0 = (!bBottomUp) ? m_adfGeoTransform[3] : |
6126 | | // Invert latitude values. |
6127 | 0 | m_adfGeoTransform[3] + |
6128 | 0 | (m_adfGeoTransform[5] * nRasterYSize); |
6129 | 0 | const double dfDY = m_adfGeoTransform[5]; |
6130 | | |
6131 | | // Override lat values with the ones in GEOLOCATION/Y_VALUES. |
6132 | 0 | if (netCDFDataset::GetMetadataItem("Y_VALUES", "GEOLOCATION") != |
6133 | 0 | nullptr) |
6134 | 0 | { |
6135 | 0 | int nTemp = 0; |
6136 | 0 | padLatVal = Get1DGeolocation("Y_VALUES", nTemp); |
6137 | | // Make sure we got the correct amount, if not fallback to GT */ |
6138 | | // could add test fabs(fabs(padLatVal[0]) - fabs(dfY0)) <= 0.1)) |
6139 | 0 | if (nTemp == nRasterYSize) |
6140 | 0 | { |
6141 | 0 | CPLDebug( |
6142 | 0 | "GDAL_netCDF", |
6143 | 0 | "Using Y_VALUES geolocation metadata for lat values"); |
6144 | 0 | } |
6145 | 0 | else |
6146 | 0 | { |
6147 | 0 | CPLDebug("GDAL_netCDF", |
6148 | 0 | "Got %d elements from Y_VALUES geolocation " |
6149 | 0 | "metadata, need %d", |
6150 | 0 | nTemp, nRasterYSize); |
6151 | 0 | if (padLatVal) |
6152 | 0 | { |
6153 | 0 | CPLFree(padLatVal); |
6154 | 0 | padLatVal = nullptr; |
6155 | 0 | } |
6156 | 0 | } |
6157 | 0 | } |
6158 | |
|
6159 | 0 | if (padLatVal == nullptr) |
6160 | 0 | { |
6161 | 0 | padLatVal = static_cast<double *>( |
6162 | 0 | CPLMalloc(nRasterYSize * sizeof(double))); |
6163 | 0 | for (int i = 0; i < nRasterYSize; i++) |
6164 | 0 | { |
6165 | | // The data point is centered inside the pixel. |
6166 | 0 | if (!bBottomUp) |
6167 | 0 | padLatVal[i] = dfY0 + (i + 0.5) * dfDY; |
6168 | 0 | else // Invert latitude values. |
6169 | 0 | padLatVal[i] = dfY0 - (i + 0.5) * dfDY; |
6170 | 0 | } |
6171 | 0 | } |
6172 | |
|
6173 | 0 | size_t startLat[1] = {0}; |
6174 | 0 | size_t countLat[1] = {static_cast<size_t>(nRasterYSize)}; |
6175 | | |
6176 | | // Get longitude values. |
6177 | 0 | const double dfX0 = m_adfGeoTransform[0]; |
6178 | 0 | const double dfDX = m_adfGeoTransform[1]; |
6179 | |
|
6180 | 0 | padLonVal = |
6181 | 0 | static_cast<double *>(CPLMalloc(nRasterXSize * sizeof(double))); |
6182 | 0 | for (int i = 0; i < nRasterXSize; i++) |
6183 | 0 | { |
6184 | | // The data point is centered inside the pixel. |
6185 | 0 | padLonVal[i] = dfX0 + (i + 0.5) * dfDX; |
6186 | 0 | } |
6187 | |
|
6188 | 0 | size_t startLon[1] = {0}; |
6189 | 0 | size_t countLon[1] = {static_cast<size_t>(nRasterXSize)}; |
6190 | | |
6191 | | // Write latitude and longitude values. |
6192 | | |
6193 | | // Make sure we are in data mode. |
6194 | 0 | SetDefineMode(false); |
6195 | | |
6196 | | // Write values. |
6197 | 0 | CPLDebug("GDAL_netCDF", "Writing lat values"); |
6198 | |
|
6199 | 0 | int status = nc_put_vara_double(cdfid, nVarLatID, startLat, |
6200 | 0 | countLat, padLatVal); |
6201 | 0 | NCDF_ERR(status); |
6202 | |
|
6203 | 0 | CPLDebug("GDAL_netCDF", "Writing lon values"); |
6204 | 0 | status = nc_put_vara_double(cdfid, nVarLonID, startLon, countLon, |
6205 | 0 | padLonVal); |
6206 | 0 | NCDF_ERR(status); |
6207 | |
|
6208 | 0 | } // Not projected. |
6209 | |
|
6210 | 0 | CPLFree(padLatVal); |
6211 | 0 | CPLFree(padLonVal); |
6212 | |
|
6213 | 0 | if (pfnProgress) |
6214 | 0 | pfnProgress(1.00, nullptr, pProgressData); |
6215 | 0 | } |
6216 | |
|
6217 | 0 | if (hDS_X != nullptr) |
6218 | 0 | { |
6219 | 0 | GDALClose(hDS_X); |
6220 | 0 | } |
6221 | 0 | if (hDS_Y != nullptr) |
6222 | 0 | { |
6223 | 0 | GDALClose(hDS_Y); |
6224 | 0 | } |
6225 | |
|
6226 | 0 | return CE_None; |
6227 | 0 | } |
6228 | | |
6229 | | // Write Projection variable to band variable. |
6230 | | // Moved from AddProjectionVars() for cases when bands are added after |
6231 | | // projection. |
6232 | | bool netCDFDataset::AddGridMappingRef() |
6233 | 0 | { |
6234 | 0 | bool bRet = true; |
6235 | 0 | bool bOldDefineMode = bDefineMode; |
6236 | |
|
6237 | 0 | if ((GetAccess() == GA_Update) && (nBands >= 1) && (GetRasterBand(1)) && |
6238 | 0 | ((pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) || |
6239 | 0 | (pszCFProjection != nullptr && !EQUAL(pszCFProjection, "")))) |
6240 | 0 | { |
6241 | 0 | bAddedGridMappingRef = true; |
6242 | | |
6243 | | // Make sure we are in define mode. |
6244 | 0 | SetDefineMode(true); |
6245 | |
|
6246 | 0 | for (int i = 1; i <= nBands; i++) |
6247 | 0 | { |
6248 | 0 | const int nVarId = |
6249 | 0 | static_cast<netCDFRasterBand *>(GetRasterBand(i))->nZId; |
6250 | |
|
6251 | 0 | if (pszCFProjection != nullptr && !EQUAL(pszCFProjection, "")) |
6252 | 0 | { |
6253 | 0 | int status = |
6254 | 0 | nc_put_att_text(cdfid, nVarId, CF_GRD_MAPPING, |
6255 | 0 | strlen(pszCFProjection), pszCFProjection); |
6256 | 0 | NCDF_ERR(status); |
6257 | 0 | if (status != NC_NOERR) |
6258 | 0 | bRet = false; |
6259 | 0 | } |
6260 | 0 | if (pszCFCoordinates != nullptr && !EQUAL(pszCFCoordinates, "")) |
6261 | 0 | { |
6262 | 0 | int status = |
6263 | 0 | nc_put_att_text(cdfid, nVarId, CF_COORDINATES, |
6264 | 0 | strlen(pszCFCoordinates), pszCFCoordinates); |
6265 | 0 | NCDF_ERR(status); |
6266 | 0 | if (status != NC_NOERR) |
6267 | 0 | bRet = false; |
6268 | 0 | } |
6269 | 0 | } |
6270 | | |
6271 | | // Go back to previous define mode. |
6272 | 0 | SetDefineMode(bOldDefineMode); |
6273 | 0 | } |
6274 | 0 | return bRet; |
6275 | 0 | } |
6276 | | |
6277 | | /************************************************************************/ |
6278 | | /* GetGeoTransform() */ |
6279 | | /************************************************************************/ |
6280 | | |
6281 | | CPLErr netCDFDataset::GetGeoTransform(double *padfTransform) |
6282 | | |
6283 | 0 | { |
6284 | 0 | memcpy(padfTransform, m_adfGeoTransform, sizeof(double) * 6); |
6285 | 0 | if (m_bHasGeoTransform) |
6286 | 0 | return CE_None; |
6287 | | |
6288 | 0 | return GDALPamDataset::GetGeoTransform(padfTransform); |
6289 | 0 | } |
6290 | | |
6291 | | /************************************************************************/ |
6292 | | /* rint() */ |
6293 | | /************************************************************************/ |
6294 | | |
6295 | | double netCDFDataset::rint(double dfX) |
6296 | 0 | { |
6297 | 0 | return std::round(dfX); |
6298 | 0 | } |
6299 | | |
6300 | | /************************************************************************/ |
6301 | | /* NCDFReadIsoMetadata() */ |
6302 | | /************************************************************************/ |
6303 | | |
6304 | | static void NCDFReadMetadataAsJson(int cdfid, CPLJSONObject &obj) |
6305 | 0 | { |
6306 | 0 | int nbAttr = 0; |
6307 | 0 | NCDF_ERR(nc_inq_varnatts(cdfid, NC_GLOBAL, &nbAttr)); |
6308 | |
|
6309 | 0 | std::map<std::string, CPLJSONArray> oMapNameToArray; |
6310 | 0 | for (int l = 0; l < nbAttr; l++) |
6311 | 0 | { |
6312 | 0 | char szAttrName[NC_MAX_NAME + 1]; |
6313 | 0 | szAttrName[0] = 0; |
6314 | 0 | NCDF_ERR(nc_inq_attname(cdfid, NC_GLOBAL, l, szAttrName)); |
6315 | |
|
6316 | 0 | char *pszMetaValue = nullptr; |
6317 | 0 | if (NCDFGetAttr(cdfid, NC_GLOBAL, szAttrName, &pszMetaValue) == CE_None) |
6318 | 0 | { |
6319 | 0 | nc_type nAttrType = NC_NAT; |
6320 | 0 | size_t nAttrLen = 0; |
6321 | |
|
6322 | 0 | NCDF_ERR(nc_inq_att(cdfid, NC_GLOBAL, szAttrName, &nAttrType, |
6323 | 0 | &nAttrLen)); |
6324 | |
|
6325 | 0 | std::string osAttrName(szAttrName); |
6326 | 0 | const auto sharpPos = osAttrName.find('#'); |
6327 | 0 | if (sharpPos == std::string::npos) |
6328 | 0 | { |
6329 | 0 | if (nAttrType == NC_DOUBLE || nAttrType == NC_FLOAT) |
6330 | 0 | obj.Add(osAttrName, CPLAtof(pszMetaValue)); |
6331 | 0 | else |
6332 | 0 | obj.Add(osAttrName, pszMetaValue); |
6333 | 0 | } |
6334 | 0 | else |
6335 | 0 | { |
6336 | 0 | osAttrName.resize(sharpPos); |
6337 | 0 | auto iter = oMapNameToArray.find(osAttrName); |
6338 | 0 | if (iter == oMapNameToArray.end()) |
6339 | 0 | { |
6340 | 0 | CPLJSONArray array; |
6341 | 0 | obj.Add(osAttrName, array); |
6342 | 0 | oMapNameToArray[osAttrName] = array; |
6343 | 0 | array.Add(pszMetaValue); |
6344 | 0 | } |
6345 | 0 | else |
6346 | 0 | { |
6347 | 0 | iter->second.Add(pszMetaValue); |
6348 | 0 | } |
6349 | 0 | } |
6350 | 0 | CPLFree(pszMetaValue); |
6351 | 0 | pszMetaValue = nullptr; |
6352 | 0 | } |
6353 | 0 | } |
6354 | |
|
6355 | 0 | int nSubGroups = 0; |
6356 | 0 | int *panSubGroupIds = nullptr; |
6357 | 0 | NCDFGetSubGroups(cdfid, &nSubGroups, &panSubGroupIds); |
6358 | 0 | oMapNameToArray.clear(); |
6359 | 0 | for (int i = 0; i < nSubGroups; i++) |
6360 | 0 | { |
6361 | 0 | CPLJSONObject subObj; |
6362 | 0 | NCDFReadMetadataAsJson(panSubGroupIds[i], subObj); |
6363 | |
|
6364 | 0 | std::string osGroupName; |
6365 | 0 | osGroupName.resize(NC_MAX_NAME); |
6366 | 0 | NCDF_ERR(nc_inq_grpname(panSubGroupIds[i], &osGroupName[0])); |
6367 | 0 | osGroupName.resize(strlen(osGroupName.data())); |
6368 | 0 | const auto sharpPos = osGroupName.find('#'); |
6369 | 0 | if (sharpPos == std::string::npos) |
6370 | 0 | { |
6371 | 0 | obj.Add(osGroupName, subObj); |
6372 | 0 | } |
6373 | 0 | else |
6374 | 0 | { |
6375 | 0 | osGroupName.resize(sharpPos); |
6376 | 0 | auto iter = oMapNameToArray.find(osGroupName); |
6377 | 0 | if (iter == oMapNameToArray.end()) |
6378 | 0 | { |
6379 | 0 | CPLJSONArray array; |
6380 | 0 | obj.Add(osGroupName, array); |
6381 | 0 | oMapNameToArray[osGroupName] = array; |
6382 | 0 | array.Add(subObj); |
6383 | 0 | } |
6384 | 0 | else |
6385 | 0 | { |
6386 | 0 | iter->second.Add(subObj); |
6387 | 0 | } |
6388 | 0 | } |
6389 | 0 | } |
6390 | 0 | CPLFree(panSubGroupIds); |
6391 | 0 | } |
6392 | | |
6393 | | std::string NCDFReadMetadataAsJson(int cdfid) |
6394 | 0 | { |
6395 | 0 | CPLJSONDocument oDoc; |
6396 | 0 | CPLJSONObject oRoot = oDoc.GetRoot(); |
6397 | 0 | NCDFReadMetadataAsJson(cdfid, oRoot); |
6398 | 0 | return oDoc.SaveAsString(); |
6399 | 0 | } |
6400 | | |
6401 | | /************************************************************************/ |
6402 | | /* ReadAttributes() */ |
6403 | | /************************************************************************/ |
6404 | | CPLErr netCDFDataset::ReadAttributes(int cdfidIn, int var) |
6405 | | |
6406 | 422 | { |
6407 | 422 | char *pszVarFullName = nullptr; |
6408 | 422 | ERR_RET(NCDFGetVarFullName(cdfidIn, var, &pszVarFullName)); |
6409 | | |
6410 | | // For metadata in Sentinel 5 |
6411 | 422 | if (STARTS_WITH(pszVarFullName, "/METADATA/")) |
6412 | 0 | { |
6413 | 0 | for (const char *key : |
6414 | 0 | {"ISO_METADATA", "ESA_METADATA", "EOP_METADATA", "QA_STATISTICS", |
6415 | 0 | "GRANULE_DESCRIPTION", "ALGORITHM_SETTINGS"}) |
6416 | 0 | { |
6417 | 0 | if (var == NC_GLOBAL && |
6418 | 0 | strcmp(pszVarFullName, |
6419 | 0 | CPLSPrintf("/METADATA/%s/NC_GLOBAL", key)) == 0) |
6420 | 0 | { |
6421 | 0 | CPLFree(pszVarFullName); |
6422 | 0 | CPLStringList aosList; |
6423 | 0 | aosList.AddString(CPLString(NCDFReadMetadataAsJson(cdfidIn)) |
6424 | 0 | .replaceAll("\\/", '/')); |
6425 | 0 | m_oMapDomainToJSon[key] = std::move(aosList); |
6426 | 0 | return CE_None; |
6427 | 0 | } |
6428 | 0 | } |
6429 | 0 | } |
6430 | 422 | if (STARTS_WITH(pszVarFullName, "/PRODUCT/SUPPORT_DATA/")) |
6431 | 0 | { |
6432 | 0 | CPLFree(pszVarFullName); |
6433 | 0 | CPLStringList aosList; |
6434 | 0 | aosList.AddString( |
6435 | 0 | CPLString(NCDFReadMetadataAsJson(cdfidIn)).replaceAll("\\/", '/')); |
6436 | 0 | m_oMapDomainToJSon["SUPPORT_DATA"] = std::move(aosList); |
6437 | 0 | return CE_None; |
6438 | 0 | } |
6439 | | |
6440 | 422 | size_t nMetaNameSize = |
6441 | 422 | sizeof(char) * (strlen(pszVarFullName) + 1 + NC_MAX_NAME + 1); |
6442 | 422 | char *pszMetaName = static_cast<char *>(CPLMalloc(nMetaNameSize)); |
6443 | | |
6444 | 422 | int nbAttr = 0; |
6445 | 422 | NCDF_ERR(nc_inq_varnatts(cdfidIn, var, &nbAttr)); |
6446 | | |
6447 | 2.61k | for (int l = 0; l < nbAttr; l++) |
6448 | 2.19k | { |
6449 | 2.19k | char szAttrName[NC_MAX_NAME + 1]; |
6450 | 2.19k | szAttrName[0] = 0; |
6451 | 2.19k | NCDF_ERR(nc_inq_attname(cdfidIn, var, l, szAttrName)); |
6452 | 2.19k | snprintf(pszMetaName, nMetaNameSize, "%s#%s", pszVarFullName, |
6453 | 2.19k | szAttrName); |
6454 | | |
6455 | 2.19k | char *pszMetaTemp = nullptr; |
6456 | 2.19k | if (NCDFGetAttr(cdfidIn, var, szAttrName, &pszMetaTemp) == CE_None) |
6457 | 2.19k | { |
6458 | 2.19k | papszMetadata = |
6459 | 2.19k | CSLSetNameValue(papszMetadata, pszMetaName, pszMetaTemp); |
6460 | 2.19k | CPLFree(pszMetaTemp); |
6461 | 2.19k | pszMetaTemp = nullptr; |
6462 | 2.19k | } |
6463 | 0 | else |
6464 | 0 | { |
6465 | 0 | CPLDebug("GDAL_netCDF", "invalid metadata %s", pszMetaName); |
6466 | 0 | } |
6467 | 2.19k | } |
6468 | | |
6469 | 422 | CPLFree(pszVarFullName); |
6470 | 422 | CPLFree(pszMetaName); |
6471 | | |
6472 | 422 | if (var == NC_GLOBAL) |
6473 | 399 | { |
6474 | | // Recurse on sub-groups. |
6475 | 399 | int nSubGroups = 0; |
6476 | 399 | int *panSubGroupIds = nullptr; |
6477 | 399 | NCDFGetSubGroups(cdfidIn, &nSubGroups, &panSubGroupIds); |
6478 | 399 | for (int i = 0; i < nSubGroups; i++) |
6479 | 0 | { |
6480 | 0 | ReadAttributes(panSubGroupIds[i], var); |
6481 | 0 | } |
6482 | 399 | CPLFree(panSubGroupIds); |
6483 | 399 | } |
6484 | | |
6485 | 422 | return CE_None; |
6486 | 422 | } |
6487 | | |
6488 | | /************************************************************************/ |
6489 | | /* netCDFDataset::CreateSubDatasetList() */ |
6490 | | /************************************************************************/ |
6491 | | void netCDFDataset::CreateSubDatasetList(int nGroupId) |
6492 | 0 | { |
6493 | 0 | char szVarStdName[NC_MAX_NAME + 1]; |
6494 | 0 | int *ponDimIds = nullptr; |
6495 | 0 | nc_type nAttype; |
6496 | 0 | size_t nAttlen; |
6497 | |
|
6498 | 0 | netCDFDataset *poDS = this; |
6499 | |
|
6500 | 0 | int nVarCount; |
6501 | 0 | nc_inq_nvars(nGroupId, &nVarCount); |
6502 | |
|
6503 | 0 | const bool bListAllArrays = CPLTestBool( |
6504 | 0 | CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_ARRAYS", "NO")); |
6505 | |
|
6506 | 0 | for (int nVar = 0; nVar < nVarCount; nVar++) |
6507 | 0 | { |
6508 | |
|
6509 | 0 | int nDims; |
6510 | 0 | nc_inq_varndims(nGroupId, nVar, &nDims); |
6511 | |
|
6512 | 0 | if ((bListAllArrays && nDims > 0) || nDims >= 2) |
6513 | 0 | { |
6514 | 0 | ponDimIds = static_cast<int *>(CPLCalloc(nDims, sizeof(int))); |
6515 | 0 | nc_inq_vardimid(nGroupId, nVar, ponDimIds); |
6516 | | |
6517 | | // Create Sub dataset list. |
6518 | 0 | CPLString osDim; |
6519 | 0 | for (int i = 0; i < nDims; i++) |
6520 | 0 | { |
6521 | 0 | size_t nDimLen; |
6522 | 0 | nc_inq_dimlen(nGroupId, ponDimIds[i], &nDimLen); |
6523 | 0 | if (!osDim.empty()) |
6524 | 0 | osDim += 'x'; |
6525 | 0 | osDim += CPLSPrintf("%d", (int)nDimLen); |
6526 | 0 | } |
6527 | 0 | CPLFree(ponDimIds); |
6528 | |
|
6529 | 0 | nc_type nVarType; |
6530 | 0 | nc_inq_vartype(nGroupId, nVar, &nVarType); |
6531 | 0 | const char *pszType = ""; |
6532 | 0 | switch (nVarType) |
6533 | 0 | { |
6534 | 0 | case NC_BYTE: |
6535 | 0 | pszType = "8-bit integer"; |
6536 | 0 | break; |
6537 | 0 | case NC_CHAR: |
6538 | 0 | pszType = "8-bit character"; |
6539 | 0 | break; |
6540 | 0 | case NC_SHORT: |
6541 | 0 | pszType = "16-bit integer"; |
6542 | 0 | break; |
6543 | 0 | case NC_INT: |
6544 | 0 | pszType = "32-bit integer"; |
6545 | 0 | break; |
6546 | 0 | case NC_FLOAT: |
6547 | 0 | pszType = "32-bit floating-point"; |
6548 | 0 | break; |
6549 | 0 | case NC_DOUBLE: |
6550 | 0 | pszType = "64-bit floating-point"; |
6551 | 0 | break; |
6552 | 0 | case NC_UBYTE: |
6553 | 0 | pszType = "8-bit unsigned integer"; |
6554 | 0 | break; |
6555 | 0 | case NC_USHORT: |
6556 | 0 | pszType = "16-bit unsigned integer"; |
6557 | 0 | break; |
6558 | 0 | case NC_UINT: |
6559 | 0 | pszType = "32-bit unsigned integer"; |
6560 | 0 | break; |
6561 | 0 | case NC_INT64: |
6562 | 0 | pszType = "64-bit integer"; |
6563 | 0 | break; |
6564 | 0 | case NC_UINT64: |
6565 | 0 | pszType = "64-bit unsigned integer"; |
6566 | 0 | break; |
6567 | 0 | default: |
6568 | 0 | break; |
6569 | 0 | } |
6570 | | |
6571 | 0 | char *pszName = nullptr; |
6572 | 0 | if (NCDFGetVarFullName(nGroupId, nVar, &pszName) != CE_None) |
6573 | 0 | continue; |
6574 | | |
6575 | 0 | nSubDatasets++; |
6576 | |
|
6577 | 0 | nAttlen = 0; |
6578 | 0 | nc_inq_att(nGroupId, nVar, CF_STD_NAME, &nAttype, &nAttlen); |
6579 | 0 | if (nAttlen < sizeof(szVarStdName) && |
6580 | 0 | nc_get_att_text(nGroupId, nVar, CF_STD_NAME, szVarStdName) == |
6581 | 0 | NC_NOERR) |
6582 | 0 | { |
6583 | 0 | szVarStdName[nAttlen] = '\0'; |
6584 | 0 | } |
6585 | 0 | else |
6586 | 0 | { |
6587 | 0 | snprintf(szVarStdName, sizeof(szVarStdName), "%s", pszName); |
6588 | 0 | } |
6589 | |
|
6590 | 0 | char szTemp[NC_MAX_NAME + 1]; |
6591 | 0 | snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_NAME", |
6592 | 0 | nSubDatasets); |
6593 | |
|
6594 | 0 | if (strchr(pszName, ' ') || strchr(pszName, ':')) |
6595 | 0 | { |
6596 | 0 | poDS->papszSubDatasets = CSLSetNameValue( |
6597 | 0 | poDS->papszSubDatasets, szTemp, |
6598 | 0 | CPLSPrintf("NETCDF:\"%s\":\"%s\"", poDS->osFilename.c_str(), |
6599 | 0 | pszName)); |
6600 | 0 | } |
6601 | 0 | else |
6602 | 0 | { |
6603 | 0 | poDS->papszSubDatasets = CSLSetNameValue( |
6604 | 0 | poDS->papszSubDatasets, szTemp, |
6605 | 0 | CPLSPrintf("NETCDF:\"%s\":%s", poDS->osFilename.c_str(), |
6606 | 0 | pszName)); |
6607 | 0 | } |
6608 | |
|
6609 | 0 | CPLFree(pszName); |
6610 | |
|
6611 | 0 | snprintf(szTemp, sizeof(szTemp), "SUBDATASET_%d_DESC", |
6612 | 0 | nSubDatasets); |
6613 | |
|
6614 | 0 | poDS->papszSubDatasets = |
6615 | 0 | CSLSetNameValue(poDS->papszSubDatasets, szTemp, |
6616 | 0 | CPLSPrintf("[%s] %s (%s)", osDim.c_str(), |
6617 | 0 | szVarStdName, pszType)); |
6618 | 0 | } |
6619 | 0 | } |
6620 | | |
6621 | | // Recurse on sub groups. |
6622 | 0 | int nSubGroups = 0; |
6623 | 0 | int *panSubGroupIds = nullptr; |
6624 | 0 | NCDFGetSubGroups(nGroupId, &nSubGroups, &panSubGroupIds); |
6625 | 0 | for (int i = 0; i < nSubGroups; i++) |
6626 | 0 | { |
6627 | 0 | CreateSubDatasetList(panSubGroupIds[i]); |
6628 | 0 | } |
6629 | 0 | CPLFree(panSubGroupIds); |
6630 | 0 | } |
6631 | | |
6632 | | /************************************************************************/ |
6633 | | /* TestCapability() */ |
6634 | | /************************************************************************/ |
6635 | | |
6636 | | int netCDFDataset::TestCapability(const char *pszCap) |
6637 | 0 | { |
6638 | 0 | if (EQUAL(pszCap, ODsCCreateLayer)) |
6639 | 0 | { |
6640 | 0 | return eAccess == GA_Update && nBands == 0 && |
6641 | 0 | (eMultipleLayerBehavior != SINGLE_LAYER || |
6642 | 0 | this->GetLayerCount() == 0 || bSGSupport); |
6643 | 0 | } |
6644 | 0 | else if (EQUAL(pszCap, ODsCZGeometries)) |
6645 | 0 | return true; |
6646 | | |
6647 | 0 | return false; |
6648 | 0 | } |
6649 | | |
6650 | | /************************************************************************/ |
6651 | | /* GetLayer() */ |
6652 | | /************************************************************************/ |
6653 | | |
6654 | | OGRLayer *netCDFDataset::GetLayer(int nIdx) |
6655 | 46 | { |
6656 | 46 | if (nIdx < 0 || nIdx >= this->GetLayerCount()) |
6657 | 0 | return nullptr; |
6658 | 46 | return papoLayers[nIdx].get(); |
6659 | 46 | } |
6660 | | |
6661 | | /************************************************************************/ |
6662 | | /* ICreateLayer() */ |
6663 | | /************************************************************************/ |
6664 | | |
6665 | | OGRLayer *netCDFDataset::ICreateLayer(const char *pszName, |
6666 | | const OGRGeomFieldDefn *poGeomFieldDefn, |
6667 | | CSLConstList papszOptions) |
6668 | 0 | { |
6669 | 0 | int nLayerCDFId = cdfid; |
6670 | 0 | if (!TestCapability(ODsCCreateLayer)) |
6671 | 0 | return nullptr; |
6672 | | |
6673 | 0 | const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; |
6674 | 0 | const auto poSpatialRef = |
6675 | 0 | poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; |
6676 | |
|
6677 | 0 | CPLString osNetCDFLayerName(pszName); |
6678 | 0 | const netCDFWriterConfigLayer *poLayerConfig = nullptr; |
6679 | 0 | if (oWriterConfig.m_bIsValid) |
6680 | 0 | { |
6681 | 0 | std::map<CPLString, netCDFWriterConfigLayer>::const_iterator |
6682 | 0 | oLayerIter = oWriterConfig.m_oLayers.find(pszName); |
6683 | 0 | if (oLayerIter != oWriterConfig.m_oLayers.end()) |
6684 | 0 | { |
6685 | 0 | poLayerConfig = &(oLayerIter->second); |
6686 | 0 | osNetCDFLayerName = poLayerConfig->m_osNetCDFName; |
6687 | 0 | } |
6688 | 0 | } |
6689 | |
|
6690 | 0 | netCDFDataset *poLayerDataset = nullptr; |
6691 | 0 | if (eMultipleLayerBehavior == SEPARATE_FILES) |
6692 | 0 | { |
6693 | 0 | char **papszDatasetOptions = nullptr; |
6694 | 0 | papszDatasetOptions = CSLSetNameValue( |
6695 | 0 | papszDatasetOptions, "CONFIG_FILE", |
6696 | 0 | CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE")); |
6697 | 0 | papszDatasetOptions = |
6698 | 0 | CSLSetNameValue(papszDatasetOptions, "FORMAT", |
6699 | 0 | CSLFetchNameValue(papszCreationOptions, "FORMAT")); |
6700 | 0 | papszDatasetOptions = CSLSetNameValue( |
6701 | 0 | papszDatasetOptions, "WRITE_GDAL_TAGS", |
6702 | 0 | CSLFetchNameValue(papszCreationOptions, "WRITE_GDAL_TAGS")); |
6703 | 0 | const CPLString osLayerFilename( |
6704 | 0 | CPLFormFilenameSafe(osFilename, osNetCDFLayerName, "nc")); |
6705 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
6706 | 0 | poLayerDataset = |
6707 | 0 | CreateLL(osLayerFilename, 0, 0, 0, papszDatasetOptions); |
6708 | 0 | CPLReleaseMutex(hNCMutex); |
6709 | 0 | CSLDestroy(papszDatasetOptions); |
6710 | 0 | if (poLayerDataset == nullptr) |
6711 | 0 | return nullptr; |
6712 | | |
6713 | 0 | nLayerCDFId = poLayerDataset->cdfid; |
6714 | 0 | NCDFAddGDALHistory(nLayerCDFId, osLayerFilename, bWriteGDALVersion, |
6715 | 0 | bWriteGDALHistory, "", "Create", |
6716 | 0 | NCDF_CONVENTIONS_CF_V1_6); |
6717 | 0 | } |
6718 | 0 | else if (eMultipleLayerBehavior == SEPARATE_GROUPS) |
6719 | 0 | { |
6720 | 0 | SetDefineMode(true); |
6721 | |
|
6722 | 0 | nLayerCDFId = -1; |
6723 | 0 | int status = nc_def_grp(cdfid, osNetCDFLayerName, &nLayerCDFId); |
6724 | 0 | NCDF_ERR(status); |
6725 | 0 | if (status != NC_NOERR) |
6726 | 0 | return nullptr; |
6727 | | |
6728 | 0 | NCDFAddGDALHistory(nLayerCDFId, osFilename, bWriteGDALVersion, |
6729 | 0 | bWriteGDALHistory, "", "Create", |
6730 | 0 | NCDF_CONVENTIONS_CF_V1_6); |
6731 | 0 | } |
6732 | | |
6733 | | // Make a clone to workaround a bug in released MapServer versions |
6734 | | // that destroys the passed SRS instead of releasing it . |
6735 | 0 | OGRSpatialReference *poSRS = nullptr; |
6736 | 0 | if (poSpatialRef) |
6737 | 0 | { |
6738 | 0 | poSRS = poSpatialRef->Clone(); |
6739 | 0 | poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
6740 | 0 | } |
6741 | 0 | std::shared_ptr<netCDFLayer> poLayer( |
6742 | 0 | new netCDFLayer(poLayerDataset ? poLayerDataset : this, nLayerCDFId, |
6743 | 0 | osNetCDFLayerName, eGType, poSRS)); |
6744 | 0 | if (poSRS != nullptr) |
6745 | 0 | poSRS->Release(); |
6746 | | |
6747 | | // Fetch layer creation options coming from config file |
6748 | 0 | char **papszNewOptions = CSLDuplicate(papszOptions); |
6749 | 0 | if (oWriterConfig.m_bIsValid) |
6750 | 0 | { |
6751 | 0 | std::map<CPLString, CPLString>::const_iterator oIter; |
6752 | 0 | for (oIter = oWriterConfig.m_oLayerCreationOptions.begin(); |
6753 | 0 | oIter != oWriterConfig.m_oLayerCreationOptions.end(); ++oIter) |
6754 | 0 | { |
6755 | 0 | papszNewOptions = |
6756 | 0 | CSLSetNameValue(papszNewOptions, oIter->first, oIter->second); |
6757 | 0 | } |
6758 | 0 | if (poLayerConfig != nullptr) |
6759 | 0 | { |
6760 | 0 | for (oIter = poLayerConfig->m_oLayerCreationOptions.begin(); |
6761 | 0 | oIter != poLayerConfig->m_oLayerCreationOptions.end(); ++oIter) |
6762 | 0 | { |
6763 | 0 | papszNewOptions = CSLSetNameValue(papszNewOptions, oIter->first, |
6764 | 0 | oIter->second); |
6765 | 0 | } |
6766 | 0 | } |
6767 | 0 | } |
6768 | |
|
6769 | 0 | const bool bRet = poLayer->Create(papszNewOptions, poLayerConfig); |
6770 | 0 | CSLDestroy(papszNewOptions); |
6771 | |
|
6772 | 0 | if (!bRet) |
6773 | 0 | { |
6774 | 0 | return nullptr; |
6775 | 0 | } |
6776 | | |
6777 | 0 | if (poLayerDataset != nullptr) |
6778 | 0 | apoVectorDatasets.push_back(poLayerDataset); |
6779 | |
|
6780 | 0 | papoLayers.push_back(poLayer); |
6781 | 0 | return poLayer.get(); |
6782 | 0 | } |
6783 | | |
6784 | | /************************************************************************/ |
6785 | | /* CloneAttributes() */ |
6786 | | /************************************************************************/ |
6787 | | |
6788 | | bool netCDFDataset::CloneAttributes(int old_cdfid, int new_cdfid, int nSrcVarId, |
6789 | | int nDstVarId) |
6790 | 0 | { |
6791 | 0 | int nAttCount = -1; |
6792 | 0 | int status = nc_inq_varnatts(old_cdfid, nSrcVarId, &nAttCount); |
6793 | 0 | NCDF_ERR(status); |
6794 | |
|
6795 | 0 | for (int i = 0; i < nAttCount; i++) |
6796 | 0 | { |
6797 | 0 | char szName[NC_MAX_NAME + 1]; |
6798 | 0 | szName[0] = 0; |
6799 | 0 | status = nc_inq_attname(old_cdfid, nSrcVarId, i, szName); |
6800 | 0 | NCDF_ERR(status); |
6801 | |
|
6802 | 0 | status = |
6803 | 0 | nc_copy_att(old_cdfid, nSrcVarId, szName, new_cdfid, nDstVarId); |
6804 | 0 | NCDF_ERR(status); |
6805 | 0 | if (status != NC_NOERR) |
6806 | 0 | return false; |
6807 | 0 | } |
6808 | | |
6809 | 0 | return true; |
6810 | 0 | } |
6811 | | |
6812 | | /************************************************************************/ |
6813 | | /* CloneVariableContent() */ |
6814 | | /************************************************************************/ |
6815 | | |
6816 | | bool netCDFDataset::CloneVariableContent(int old_cdfid, int new_cdfid, |
6817 | | int nSrcVarId, int nDstVarId) |
6818 | 0 | { |
6819 | 0 | int nVarDimCount = -1; |
6820 | 0 | int status = nc_inq_varndims(old_cdfid, nSrcVarId, &nVarDimCount); |
6821 | 0 | NCDF_ERR(status); |
6822 | 0 | int anDimIds[] = {-1, 1}; |
6823 | 0 | status = nc_inq_vardimid(old_cdfid, nSrcVarId, anDimIds); |
6824 | 0 | NCDF_ERR(status); |
6825 | 0 | nc_type nc_datatype = NC_NAT; |
6826 | 0 | status = nc_inq_vartype(old_cdfid, nSrcVarId, &nc_datatype); |
6827 | 0 | NCDF_ERR(status); |
6828 | 0 | size_t nTypeSize = 0; |
6829 | 0 | switch (nc_datatype) |
6830 | 0 | { |
6831 | 0 | case NC_BYTE: |
6832 | 0 | case NC_CHAR: |
6833 | 0 | nTypeSize = 1; |
6834 | 0 | break; |
6835 | 0 | case NC_SHORT: |
6836 | 0 | nTypeSize = 2; |
6837 | 0 | break; |
6838 | 0 | case NC_INT: |
6839 | 0 | nTypeSize = 4; |
6840 | 0 | break; |
6841 | 0 | case NC_FLOAT: |
6842 | 0 | nTypeSize = 4; |
6843 | 0 | break; |
6844 | 0 | case NC_DOUBLE: |
6845 | 0 | nTypeSize = 8; |
6846 | 0 | break; |
6847 | 0 | case NC_UBYTE: |
6848 | 0 | nTypeSize = 1; |
6849 | 0 | break; |
6850 | 0 | case NC_USHORT: |
6851 | 0 | nTypeSize = 2; |
6852 | 0 | break; |
6853 | 0 | case NC_UINT: |
6854 | 0 | nTypeSize = 4; |
6855 | 0 | break; |
6856 | 0 | case NC_INT64: |
6857 | 0 | case NC_UINT64: |
6858 | 0 | nTypeSize = 8; |
6859 | 0 | break; |
6860 | 0 | case NC_STRING: |
6861 | 0 | nTypeSize = sizeof(char *); |
6862 | 0 | break; |
6863 | 0 | default: |
6864 | 0 | { |
6865 | 0 | CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type: %d", |
6866 | 0 | nc_datatype); |
6867 | 0 | return false; |
6868 | 0 | } |
6869 | 0 | } |
6870 | | |
6871 | 0 | size_t nElems = 1; |
6872 | 0 | size_t anStart[NC_MAX_DIMS]; |
6873 | 0 | size_t anCount[NC_MAX_DIMS]; |
6874 | 0 | size_t nRecords = 1; |
6875 | 0 | for (int i = 0; i < nVarDimCount; i++) |
6876 | 0 | { |
6877 | 0 | anStart[i] = 0; |
6878 | 0 | if (i == 0) |
6879 | 0 | { |
6880 | 0 | anCount[i] = 1; |
6881 | 0 | status = nc_inq_dimlen(old_cdfid, anDimIds[i], &nRecords); |
6882 | 0 | NCDF_ERR(status); |
6883 | 0 | } |
6884 | 0 | else |
6885 | 0 | { |
6886 | 0 | anCount[i] = 0; |
6887 | 0 | status = nc_inq_dimlen(old_cdfid, anDimIds[i], &anCount[i]); |
6888 | 0 | NCDF_ERR(status); |
6889 | 0 | nElems *= anCount[i]; |
6890 | 0 | } |
6891 | 0 | } |
6892 | | |
6893 | | /* Workaround in some cases a netCDF bug: |
6894 | | * https://github.com/Unidata/netcdf-c/pull/1442 */ |
6895 | 0 | if (nRecords > 0 && nRecords < 10 * 1000 * 1000 / (nElems * nTypeSize)) |
6896 | 0 | { |
6897 | 0 | nElems *= nRecords; |
6898 | 0 | anCount[0] = nRecords; |
6899 | 0 | nRecords = 1; |
6900 | 0 | } |
6901 | |
|
6902 | 0 | void *pBuffer = VSI_MALLOC2_VERBOSE(nElems, nTypeSize); |
6903 | 0 | if (pBuffer == nullptr) |
6904 | 0 | return false; |
6905 | | |
6906 | 0 | for (size_t iRecord = 0; iRecord < nRecords; iRecord++) |
6907 | 0 | { |
6908 | 0 | anStart[0] = iRecord; |
6909 | |
|
6910 | 0 | switch (nc_datatype) |
6911 | 0 | { |
6912 | 0 | case NC_BYTE: |
6913 | 0 | status = |
6914 | 0 | nc_get_vara_schar(old_cdfid, nSrcVarId, anStart, anCount, |
6915 | 0 | static_cast<signed char *>(pBuffer)); |
6916 | 0 | if (!status) |
6917 | 0 | status = nc_put_vara_schar( |
6918 | 0 | new_cdfid, nDstVarId, anStart, anCount, |
6919 | 0 | static_cast<signed char *>(pBuffer)); |
6920 | 0 | break; |
6921 | 0 | case NC_CHAR: |
6922 | 0 | status = |
6923 | 0 | nc_get_vara_text(old_cdfid, nSrcVarId, anStart, anCount, |
6924 | 0 | static_cast<char *>(pBuffer)); |
6925 | 0 | if (!status) |
6926 | 0 | status = |
6927 | 0 | nc_put_vara_text(new_cdfid, nDstVarId, anStart, anCount, |
6928 | 0 | static_cast<char *>(pBuffer)); |
6929 | 0 | break; |
6930 | 0 | case NC_SHORT: |
6931 | 0 | status = |
6932 | 0 | nc_get_vara_short(old_cdfid, nSrcVarId, anStart, anCount, |
6933 | 0 | static_cast<short *>(pBuffer)); |
6934 | 0 | if (!status) |
6935 | 0 | status = nc_put_vara_short(new_cdfid, nDstVarId, anStart, |
6936 | 0 | anCount, |
6937 | 0 | static_cast<short *>(pBuffer)); |
6938 | 0 | break; |
6939 | 0 | case NC_INT: |
6940 | 0 | status = nc_get_vara_int(old_cdfid, nSrcVarId, anStart, anCount, |
6941 | 0 | static_cast<int *>(pBuffer)); |
6942 | 0 | if (!status) |
6943 | 0 | status = |
6944 | 0 | nc_put_vara_int(new_cdfid, nDstVarId, anStart, anCount, |
6945 | 0 | static_cast<int *>(pBuffer)); |
6946 | 0 | break; |
6947 | 0 | case NC_FLOAT: |
6948 | 0 | status = |
6949 | 0 | nc_get_vara_float(old_cdfid, nSrcVarId, anStart, anCount, |
6950 | 0 | static_cast<float *>(pBuffer)); |
6951 | 0 | if (!status) |
6952 | 0 | status = nc_put_vara_float(new_cdfid, nDstVarId, anStart, |
6953 | 0 | anCount, |
6954 | 0 | static_cast<float *>(pBuffer)); |
6955 | 0 | break; |
6956 | 0 | case NC_DOUBLE: |
6957 | 0 | status = |
6958 | 0 | nc_get_vara_double(old_cdfid, nSrcVarId, anStart, anCount, |
6959 | 0 | static_cast<double *>(pBuffer)); |
6960 | 0 | if (!status) |
6961 | 0 | status = nc_put_vara_double(new_cdfid, nDstVarId, anStart, |
6962 | 0 | anCount, |
6963 | 0 | static_cast<double *>(pBuffer)); |
6964 | 0 | break; |
6965 | 0 | case NC_STRING: |
6966 | 0 | status = |
6967 | 0 | nc_get_vara_string(old_cdfid, nSrcVarId, anStart, anCount, |
6968 | 0 | static_cast<char **>(pBuffer)); |
6969 | 0 | if (!status) |
6970 | 0 | { |
6971 | 0 | status = nc_put_vara_string( |
6972 | 0 | new_cdfid, nDstVarId, anStart, anCount, |
6973 | 0 | static_cast<const char **>(pBuffer)); |
6974 | 0 | nc_free_string(nElems, static_cast<char **>(pBuffer)); |
6975 | 0 | } |
6976 | 0 | break; |
6977 | | |
6978 | 0 | case NC_UBYTE: |
6979 | 0 | status = |
6980 | 0 | nc_get_vara_uchar(old_cdfid, nSrcVarId, anStart, anCount, |
6981 | 0 | static_cast<unsigned char *>(pBuffer)); |
6982 | 0 | if (!status) |
6983 | 0 | status = nc_put_vara_uchar( |
6984 | 0 | new_cdfid, nDstVarId, anStart, anCount, |
6985 | 0 | static_cast<unsigned char *>(pBuffer)); |
6986 | 0 | break; |
6987 | 0 | case NC_USHORT: |
6988 | 0 | status = |
6989 | 0 | nc_get_vara_ushort(old_cdfid, nSrcVarId, anStart, anCount, |
6990 | 0 | static_cast<unsigned short *>(pBuffer)); |
6991 | 0 | if (!status) |
6992 | 0 | status = nc_put_vara_ushort( |
6993 | 0 | new_cdfid, nDstVarId, anStart, anCount, |
6994 | 0 | static_cast<unsigned short *>(pBuffer)); |
6995 | 0 | break; |
6996 | 0 | case NC_UINT: |
6997 | 0 | status = |
6998 | 0 | nc_get_vara_uint(old_cdfid, nSrcVarId, anStart, anCount, |
6999 | 0 | static_cast<unsigned int *>(pBuffer)); |
7000 | 0 | if (!status) |
7001 | 0 | status = |
7002 | 0 | nc_put_vara_uint(new_cdfid, nDstVarId, anStart, anCount, |
7003 | 0 | static_cast<unsigned int *>(pBuffer)); |
7004 | 0 | break; |
7005 | 0 | case NC_INT64: |
7006 | 0 | status = |
7007 | 0 | nc_get_vara_longlong(old_cdfid, nSrcVarId, anStart, anCount, |
7008 | 0 | static_cast<long long *>(pBuffer)); |
7009 | 0 | if (!status) |
7010 | 0 | status = nc_put_vara_longlong( |
7011 | 0 | new_cdfid, nDstVarId, anStart, anCount, |
7012 | 0 | static_cast<long long *>(pBuffer)); |
7013 | 0 | break; |
7014 | 0 | case NC_UINT64: |
7015 | 0 | status = nc_get_vara_ulonglong( |
7016 | 0 | old_cdfid, nSrcVarId, anStart, anCount, |
7017 | 0 | static_cast<unsigned long long *>(pBuffer)); |
7018 | 0 | if (!status) |
7019 | 0 | status = nc_put_vara_ulonglong( |
7020 | 0 | new_cdfid, nDstVarId, anStart, anCount, |
7021 | 0 | static_cast<unsigned long long *>(pBuffer)); |
7022 | 0 | break; |
7023 | 0 | default: |
7024 | 0 | status = NC_EBADTYPE; |
7025 | 0 | } |
7026 | | |
7027 | 0 | NCDF_ERR(status); |
7028 | 0 | if (status != NC_NOERR) |
7029 | 0 | { |
7030 | 0 | VSIFree(pBuffer); |
7031 | 0 | return false; |
7032 | 0 | } |
7033 | 0 | } |
7034 | | |
7035 | 0 | VSIFree(pBuffer); |
7036 | 0 | return true; |
7037 | 0 | } |
7038 | | |
7039 | | /************************************************************************/ |
7040 | | /* NCDFIsUnlimitedDim() */ |
7041 | | /************************************************************************/ |
7042 | | |
7043 | | bool NCDFIsUnlimitedDim(bool bIsNC4, int cdfid, int nDimId) |
7044 | 0 | { |
7045 | 0 | if (bIsNC4) |
7046 | 0 | { |
7047 | 0 | int nUnlimitedDims = 0; |
7048 | 0 | nc_inq_unlimdims(cdfid, &nUnlimitedDims, nullptr); |
7049 | 0 | bool bFound = false; |
7050 | 0 | if (nUnlimitedDims) |
7051 | 0 | { |
7052 | 0 | int *panUnlimitedDimIds = |
7053 | 0 | static_cast<int *>(CPLMalloc(sizeof(int) * nUnlimitedDims)); |
7054 | 0 | nc_inq_unlimdims(cdfid, nullptr, panUnlimitedDimIds); |
7055 | 0 | for (int i = 0; i < nUnlimitedDims; i++) |
7056 | 0 | { |
7057 | 0 | if (panUnlimitedDimIds[i] == nDimId) |
7058 | 0 | { |
7059 | 0 | bFound = true; |
7060 | 0 | break; |
7061 | 0 | } |
7062 | 0 | } |
7063 | 0 | CPLFree(panUnlimitedDimIds); |
7064 | 0 | } |
7065 | 0 | return bFound; |
7066 | 0 | } |
7067 | 0 | else |
7068 | 0 | { |
7069 | 0 | int nUnlimitedDimId = -1; |
7070 | 0 | nc_inq(cdfid, nullptr, nullptr, nullptr, &nUnlimitedDimId); |
7071 | 0 | return nDimId == nUnlimitedDimId; |
7072 | 0 | } |
7073 | 0 | } |
7074 | | |
7075 | | /************************************************************************/ |
7076 | | /* CloneGrp() */ |
7077 | | /************************************************************************/ |
7078 | | |
7079 | | bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4, |
7080 | | int nLayerId, int nDimIdToGrow, size_t nNewSize) |
7081 | 0 | { |
7082 | | // Clone dimensions |
7083 | 0 | int nDimCount = -1; |
7084 | 0 | int status = nc_inq_ndims(nOldGrpId, &nDimCount); |
7085 | 0 | NCDF_ERR(status); |
7086 | 0 | if (nDimCount < 0 || nDimCount > NC_MAX_DIMS) |
7087 | 0 | return false; |
7088 | 0 | int anDimIds[NC_MAX_DIMS]; |
7089 | 0 | int nUnlimiDimID = -1; |
7090 | 0 | status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID); |
7091 | 0 | NCDF_ERR(status); |
7092 | 0 | if (bIsNC4) |
7093 | 0 | { |
7094 | | // In NC4, the dimension ids of a group are not necessarily in |
7095 | | // [0,nDimCount-1] range |
7096 | 0 | int nDimCount2 = -1; |
7097 | 0 | status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE); |
7098 | 0 | NCDF_ERR(status); |
7099 | 0 | CPLAssert(nDimCount == nDimCount2); |
7100 | 0 | } |
7101 | 0 | else |
7102 | 0 | { |
7103 | 0 | for (int i = 0; i < nDimCount; i++) |
7104 | 0 | anDimIds[i] = i; |
7105 | 0 | } |
7106 | 0 | for (int i = 0; i < nDimCount; i++) |
7107 | 0 | { |
7108 | 0 | char szDimName[NC_MAX_NAME + 1]; |
7109 | 0 | szDimName[0] = 0; |
7110 | 0 | size_t nLen = 0; |
7111 | 0 | const int nDimId = anDimIds[i]; |
7112 | 0 | status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen); |
7113 | 0 | NCDF_ERR(status); |
7114 | 0 | if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId)) |
7115 | 0 | nLen = NC_UNLIMITED; |
7116 | 0 | else if (nDimId == nDimIdToGrow && nOldGrpId == nLayerId) |
7117 | 0 | nLen = nNewSize; |
7118 | 0 | int nNewDimId = -1; |
7119 | 0 | status = nc_def_dim(nNewGrpId, szDimName, nLen, &nNewDimId); |
7120 | 0 | NCDF_ERR(status); |
7121 | 0 | CPLAssert(nDimId == nNewDimId); |
7122 | 0 | if (status != NC_NOERR) |
7123 | 0 | { |
7124 | 0 | return false; |
7125 | 0 | } |
7126 | 0 | } |
7127 | | |
7128 | | // Clone main attributes |
7129 | 0 | if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL)) |
7130 | 0 | { |
7131 | 0 | return false; |
7132 | 0 | } |
7133 | | |
7134 | | // Clone variable definitions |
7135 | 0 | int nVarCount = -1; |
7136 | 0 | status = nc_inq_nvars(nOldGrpId, &nVarCount); |
7137 | 0 | NCDF_ERR(status); |
7138 | |
|
7139 | 0 | for (int i = 0; i < nVarCount; i++) |
7140 | 0 | { |
7141 | 0 | char szVarName[NC_MAX_NAME + 1]; |
7142 | 0 | szVarName[0] = 0; |
7143 | 0 | status = nc_inq_varname(nOldGrpId, i, szVarName); |
7144 | 0 | NCDF_ERR(status); |
7145 | 0 | nc_type nc_datatype = NC_NAT; |
7146 | 0 | status = nc_inq_vartype(nOldGrpId, i, &nc_datatype); |
7147 | 0 | NCDF_ERR(status); |
7148 | 0 | int nVarDimCount = -1; |
7149 | 0 | status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount); |
7150 | 0 | NCDF_ERR(status); |
7151 | 0 | status = nc_inq_vardimid(nOldGrpId, i, anDimIds); |
7152 | 0 | NCDF_ERR(status); |
7153 | 0 | int nNewVarId = -1; |
7154 | 0 | status = nc_def_var(nNewGrpId, szVarName, nc_datatype, nVarDimCount, |
7155 | 0 | anDimIds, &nNewVarId); |
7156 | 0 | NCDF_ERR(status); |
7157 | 0 | CPLAssert(i == nNewVarId); |
7158 | 0 | if (status != NC_NOERR) |
7159 | 0 | { |
7160 | 0 | return false; |
7161 | 0 | } |
7162 | | |
7163 | 0 | if (!CloneAttributes(nOldGrpId, nNewGrpId, i, i)) |
7164 | 0 | { |
7165 | 0 | return false; |
7166 | 0 | } |
7167 | 0 | } |
7168 | | |
7169 | 0 | status = nc_enddef(nNewGrpId); |
7170 | 0 | NCDF_ERR(status); |
7171 | 0 | if (status != NC_NOERR) |
7172 | 0 | { |
7173 | 0 | return false; |
7174 | 0 | } |
7175 | | |
7176 | | // Clone variable content |
7177 | 0 | for (int i = 0; i < nVarCount; i++) |
7178 | 0 | { |
7179 | 0 | if (!CloneVariableContent(nOldGrpId, nNewGrpId, i, i)) |
7180 | 0 | { |
7181 | 0 | return false; |
7182 | 0 | } |
7183 | 0 | } |
7184 | | |
7185 | 0 | return true; |
7186 | 0 | } |
7187 | | |
7188 | | /************************************************************************/ |
7189 | | /* GrowDim() */ |
7190 | | /************************************************************************/ |
7191 | | |
7192 | | bool netCDFDataset::GrowDim(int nLayerId, int nDimIdToGrow, size_t nNewSize) |
7193 | 0 | { |
7194 | 0 | int nCreationMode; |
7195 | | // Set nCreationMode based on eFormat. |
7196 | 0 | switch (eFormat) |
7197 | 0 | { |
7198 | 0 | #ifdef NETCDF_HAS_NC2 |
7199 | 0 | case NCDF_FORMAT_NC2: |
7200 | 0 | nCreationMode = NC_CLOBBER | NC_64BIT_OFFSET; |
7201 | 0 | break; |
7202 | 0 | #endif |
7203 | 0 | case NCDF_FORMAT_NC4: |
7204 | 0 | nCreationMode = NC_CLOBBER | NC_NETCDF4; |
7205 | 0 | break; |
7206 | 0 | case NCDF_FORMAT_NC4C: |
7207 | 0 | nCreationMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL; |
7208 | 0 | break; |
7209 | 0 | case NCDF_FORMAT_NC: |
7210 | 0 | default: |
7211 | 0 | nCreationMode = NC_CLOBBER; |
7212 | 0 | break; |
7213 | 0 | } |
7214 | | |
7215 | 0 | int new_cdfid = -1; |
7216 | 0 | CPLString osTmpFilename(osFilename + ".tmp"); |
7217 | 0 | CPLString osFilenameForNCCreate(osTmpFilename); |
7218 | | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8) |
7219 | | if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) |
7220 | | { |
7221 | | char *pszTemp = |
7222 | | CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP"); |
7223 | | osFilenameForNCCreate = pszTemp; |
7224 | | CPLFree(pszTemp); |
7225 | | } |
7226 | | #endif |
7227 | 0 | int status = nc_create(osFilenameForNCCreate, nCreationMode, &new_cdfid); |
7228 | 0 | NCDF_ERR(status); |
7229 | 0 | if (status != NC_NOERR) |
7230 | 0 | return false; |
7231 | | |
7232 | 0 | if (!CloneGrp(cdfid, new_cdfid, eFormat == NCDF_FORMAT_NC4, nLayerId, |
7233 | 0 | nDimIdToGrow, nNewSize)) |
7234 | 0 | { |
7235 | 0 | GDAL_nc_close(new_cdfid); |
7236 | 0 | return false; |
7237 | 0 | } |
7238 | | |
7239 | 0 | int nGroupCount = 0; |
7240 | 0 | std::vector<CPLString> oListGrpName; |
7241 | 0 | if (eFormat == NCDF_FORMAT_NC4 && |
7242 | 0 | nc_inq_grps(cdfid, &nGroupCount, nullptr) == NC_NOERR && |
7243 | 0 | nGroupCount > 0) |
7244 | 0 | { |
7245 | 0 | int *panGroupIds = |
7246 | 0 | static_cast<int *>(CPLMalloc(sizeof(int) * nGroupCount)); |
7247 | 0 | status = nc_inq_grps(cdfid, nullptr, panGroupIds); |
7248 | 0 | NCDF_ERR(status); |
7249 | 0 | for (int i = 0; i < nGroupCount; i++) |
7250 | 0 | { |
7251 | 0 | char szGroupName[NC_MAX_NAME + 1]; |
7252 | 0 | szGroupName[0] = 0; |
7253 | 0 | NCDF_ERR(nc_inq_grpname(panGroupIds[i], szGroupName)); |
7254 | 0 | int nNewGrpId = -1; |
7255 | 0 | status = nc_def_grp(new_cdfid, szGroupName, &nNewGrpId); |
7256 | 0 | NCDF_ERR(status); |
7257 | 0 | if (status != NC_NOERR) |
7258 | 0 | { |
7259 | 0 | CPLFree(panGroupIds); |
7260 | 0 | GDAL_nc_close(new_cdfid); |
7261 | 0 | return false; |
7262 | 0 | } |
7263 | 0 | if (!CloneGrp(panGroupIds[i], nNewGrpId, /*bIsNC4=*/true, nLayerId, |
7264 | 0 | nDimIdToGrow, nNewSize)) |
7265 | 0 | { |
7266 | 0 | CPLFree(panGroupIds); |
7267 | 0 | GDAL_nc_close(new_cdfid); |
7268 | 0 | return false; |
7269 | 0 | } |
7270 | 0 | } |
7271 | 0 | CPLFree(panGroupIds); |
7272 | |
|
7273 | 0 | for (int i = 0; i < this->GetLayerCount(); i++) |
7274 | 0 | { |
7275 | 0 | auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get()); |
7276 | 0 | if (poLayer) |
7277 | 0 | { |
7278 | 0 | char szGroupName[NC_MAX_NAME + 1]; |
7279 | 0 | szGroupName[0] = 0; |
7280 | 0 | status = nc_inq_grpname(poLayer->GetCDFID(), szGroupName); |
7281 | 0 | NCDF_ERR(status); |
7282 | 0 | oListGrpName.push_back(szGroupName); |
7283 | 0 | } |
7284 | 0 | } |
7285 | 0 | } |
7286 | | |
7287 | 0 | GDAL_nc_close(cdfid); |
7288 | 0 | cdfid = -1; |
7289 | 0 | GDAL_nc_close(new_cdfid); |
7290 | |
|
7291 | 0 | CPLString osOriFilename(osFilename + ".ori"); |
7292 | 0 | if (VSIRename(osFilename, osOriFilename) != 0 || |
7293 | 0 | VSIRename(osTmpFilename, osFilename) != 0) |
7294 | 0 | { |
7295 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Renaming of files failed"); |
7296 | 0 | return false; |
7297 | 0 | } |
7298 | 0 | VSIUnlink(osOriFilename); |
7299 | |
|
7300 | 0 | CPLString osFilenameForNCOpen(osFilename); |
7301 | | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8) |
7302 | | if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) |
7303 | | { |
7304 | | char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP"); |
7305 | | osFilenameForNCOpen = pszTemp; |
7306 | | CPLFree(pszTemp); |
7307 | | } |
7308 | | #endif |
7309 | 0 | status = GDAL_nc_open(osFilenameForNCOpen, NC_WRITE, &cdfid); |
7310 | 0 | NCDF_ERR(status); |
7311 | 0 | if (status != NC_NOERR) |
7312 | 0 | return false; |
7313 | 0 | bDefineMode = false; |
7314 | |
|
7315 | 0 | if (!oListGrpName.empty()) |
7316 | 0 | { |
7317 | 0 | for (int i = 0; i < this->GetLayerCount(); i++) |
7318 | 0 | { |
7319 | 0 | auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get()); |
7320 | 0 | if (poLayer) |
7321 | 0 | { |
7322 | 0 | int nNewLayerCDFID = -1; |
7323 | 0 | status = nc_inq_ncid(cdfid, oListGrpName[i].c_str(), |
7324 | 0 | &nNewLayerCDFID); |
7325 | 0 | NCDF_ERR(status); |
7326 | 0 | poLayer->SetCDFID(nNewLayerCDFID); |
7327 | 0 | } |
7328 | 0 | } |
7329 | 0 | } |
7330 | 0 | else |
7331 | 0 | { |
7332 | 0 | for (int i = 0; i < this->GetLayerCount(); i++) |
7333 | 0 | { |
7334 | 0 | auto poLayer = dynamic_cast<netCDFLayer *>(papoLayers[i].get()); |
7335 | 0 | if (poLayer) |
7336 | 0 | poLayer->SetCDFID(cdfid); |
7337 | 0 | } |
7338 | 0 | } |
7339 | |
|
7340 | 0 | return true; |
7341 | 0 | } |
7342 | | |
7343 | | #ifdef ENABLE_NCDUMP |
7344 | | |
7345 | | /************************************************************************/ |
7346 | | /* netCDFDatasetCreateTempFile() */ |
7347 | | /************************************************************************/ |
7348 | | |
7349 | | /* Create a netCDF file from a text dump (format of ncdump) */ |
7350 | | /* Mostly to easy fuzzing of the driver, while still generating valid */ |
7351 | | /* netCDF files. */ |
7352 | | /* Note: not all data types are supported ! */ |
7353 | | bool netCDFDatasetCreateTempFile(NetCDFFormatEnum eFormat, |
7354 | | const char *pszTmpFilename, VSILFILE *fpSrc) |
7355 | 399 | { |
7356 | 399 | CPL_IGNORE_RET_VAL(eFormat); |
7357 | 399 | int nCreateMode = NC_CLOBBER; |
7358 | 399 | if (eFormat == NCDF_FORMAT_NC4) |
7359 | 8 | nCreateMode |= NC_NETCDF4; |
7360 | 391 | else if (eFormat == NCDF_FORMAT_NC4C) |
7361 | 141 | nCreateMode |= NC_NETCDF4 | NC_CLASSIC_MODEL; |
7362 | 399 | int nCdfId = -1; |
7363 | 399 | int status = nc_create(pszTmpFilename, nCreateMode, &nCdfId); |
7364 | 399 | if (status != NC_NOERR) |
7365 | 0 | { |
7366 | 0 | return false; |
7367 | 0 | } |
7368 | 399 | VSIFSeekL(fpSrc, 0, SEEK_SET); |
7369 | 399 | const char *pszLine; |
7370 | 399 | constexpr int SECTION_NONE = 0; |
7371 | 399 | constexpr int SECTION_DIMENSIONS = 1; |
7372 | 399 | constexpr int SECTION_VARIABLES = 2; |
7373 | 399 | constexpr int SECTION_DATA = 3; |
7374 | 399 | int nActiveSection = SECTION_NONE; |
7375 | 399 | std::map<CPLString, int> oMapDimToId; |
7376 | 399 | std::map<int, int> oMapDimIdToDimLen; |
7377 | 399 | std::map<CPLString, int> oMapVarToId; |
7378 | 399 | std::map<int, std::vector<int>> oMapVarIdToVectorOfDimId; |
7379 | 399 | std::map<int, int> oMapVarIdToType; |
7380 | 399 | std::set<CPLString> oSetAttrDefined; |
7381 | 399 | oMapVarToId[""] = -1; |
7382 | 399 | size_t nTotalVarSize = 0; |
7383 | 664k | while ((pszLine = CPLReadLineL(fpSrc)) != nullptr) |
7384 | 664k | { |
7385 | 664k | if (STARTS_WITH(pszLine, "dimensions:") && |
7386 | 664k | nActiveSection == SECTION_NONE) |
7387 | 399 | { |
7388 | 399 | nActiveSection = SECTION_DIMENSIONS; |
7389 | 399 | } |
7390 | 663k | else if (STARTS_WITH(pszLine, "variables:") && |
7391 | 663k | nActiveSection == SECTION_DIMENSIONS) |
7392 | 365 | { |
7393 | 365 | nActiveSection = SECTION_VARIABLES; |
7394 | 365 | } |
7395 | 663k | else if (STARTS_WITH(pszLine, "data:") && |
7396 | 663k | nActiveSection == SECTION_VARIABLES) |
7397 | 219 | { |
7398 | 219 | nActiveSection = SECTION_DATA; |
7399 | 219 | status = nc_enddef(nCdfId); |
7400 | 219 | if (status != NC_NOERR) |
7401 | 0 | { |
7402 | 0 | CPLDebug("netCDF", "nc_enddef() failed: %s", |
7403 | 0 | nc_strerror(status)); |
7404 | 0 | } |
7405 | 219 | } |
7406 | 663k | else if (nActiveSection == SECTION_DIMENSIONS) |
7407 | 103k | { |
7408 | 103k | char **papszTokens = CSLTokenizeString2(pszLine, " \t=;", 0); |
7409 | 103k | if (CSLCount(papszTokens) == 2) |
7410 | 23.6k | { |
7411 | 23.6k | const char *pszDimName = papszTokens[0]; |
7412 | 23.6k | bool bValidName = true; |
7413 | 23.6k | if (STARTS_WITH(pszDimName, "_nc4_non_coord_")) |
7414 | 0 | { |
7415 | | // This is an internal netcdf prefix. Using it may |
7416 | | // cause memory leaks. |
7417 | 0 | bValidName = false; |
7418 | 0 | } |
7419 | 23.6k | if (!bValidName) |
7420 | 0 | { |
7421 | 0 | CPLDebug("netCDF", |
7422 | 0 | "nc_def_dim(%s) failed: invalid name found", |
7423 | 0 | pszDimName); |
7424 | 0 | CSLDestroy(papszTokens); |
7425 | 0 | continue; |
7426 | 0 | } |
7427 | | |
7428 | 23.6k | const bool bIsASCII = |
7429 | 23.6k | CPLIsASCII(pszDimName, static_cast<size_t>(-1)); |
7430 | 23.6k | if (!bIsASCII) |
7431 | 3.09k | { |
7432 | | // Workaround https://github.com/Unidata/netcdf-c/pull/450 |
7433 | 3.09k | CPLDebug("netCDF", |
7434 | 3.09k | "nc_def_dim(%s) failed: rejected because " |
7435 | 3.09k | "of non-ASCII characters", |
7436 | 3.09k | pszDimName); |
7437 | 3.09k | CSLDestroy(papszTokens); |
7438 | 3.09k | continue; |
7439 | 3.09k | } |
7440 | 20.5k | int nDimSize = EQUAL(papszTokens[1], "UNLIMITED") |
7441 | 20.5k | ? NC_UNLIMITED |
7442 | 20.5k | : atoi(papszTokens[1]); |
7443 | 20.5k | if (nDimSize >= 1000) |
7444 | 83 | nDimSize = 1000; // to avoid very long processing |
7445 | 20.5k | if (nDimSize >= 0) |
7446 | 14.1k | { |
7447 | 14.1k | int nDimId = -1; |
7448 | 14.1k | status = nc_def_dim(nCdfId, pszDimName, nDimSize, &nDimId); |
7449 | 14.1k | if (status != NC_NOERR) |
7450 | 13.0k | { |
7451 | 13.0k | CPLDebug("netCDF", "nc_def_dim(%s, %d) failed: %s", |
7452 | 13.0k | pszDimName, nDimSize, nc_strerror(status)); |
7453 | 13.0k | } |
7454 | 1.09k | else |
7455 | 1.09k | { |
7456 | | #ifdef DEBUG_VERBOSE |
7457 | | CPLDebug("netCDF", "nc_def_dim(%s, %d) (%s) succeeded", |
7458 | | pszDimName, nDimSize, pszLine); |
7459 | | #endif |
7460 | 1.09k | oMapDimToId[pszDimName] = nDimId; |
7461 | 1.09k | oMapDimIdToDimLen[nDimId] = nDimSize; |
7462 | 1.09k | } |
7463 | 14.1k | } |
7464 | 20.5k | } |
7465 | 100k | CSLDestroy(papszTokens); |
7466 | 100k | } |
7467 | 559k | else if (nActiveSection == SECTION_VARIABLES) |
7468 | 377k | { |
7469 | 527k | while (*pszLine == ' ' || *pszLine == '\t') |
7470 | 150k | pszLine++; |
7471 | 377k | const char *pszColumn = strchr(pszLine, ':'); |
7472 | 377k | const char *pszEqual = strchr(pszLine, '='); |
7473 | 377k | if (pszColumn == nullptr) |
7474 | 315k | { |
7475 | 315k | char **papszTokens = CSLTokenizeString2(pszLine, " \t=(),;", 0); |
7476 | 315k | if (CSLCount(papszTokens) >= 2) |
7477 | 192k | { |
7478 | 192k | const char *pszVarName = papszTokens[1]; |
7479 | 192k | bool bValidName = true; |
7480 | 192k | if (STARTS_WITH(pszVarName, "_nc4_non_coord_")) |
7481 | 0 | { |
7482 | | // This is an internal netcdf prefix. Using it may |
7483 | | // cause memory leaks. |
7484 | 0 | bValidName = false; |
7485 | 0 | } |
7486 | 1.64M | for (int i = 0; pszVarName[i]; i++) |
7487 | 1.45M | { |
7488 | 1.45M | if (!((pszVarName[i] >= 'a' && pszVarName[i] <= 'z') || |
7489 | 1.45M | (pszVarName[i] >= 'A' && pszVarName[i] <= 'Z') || |
7490 | 1.45M | (pszVarName[i] >= '0' && pszVarName[i] <= '9') || |
7491 | 1.45M | pszVarName[i] == '_')) |
7492 | 372k | { |
7493 | 372k | bValidName = false; |
7494 | 372k | } |
7495 | 1.45M | } |
7496 | 192k | if (!bValidName) |
7497 | 82.2k | { |
7498 | 82.2k | CPLDebug( |
7499 | 82.2k | "netCDF", |
7500 | 82.2k | "nc_def_var(%s) failed: illegal character found", |
7501 | 82.2k | pszVarName); |
7502 | 82.2k | CSLDestroy(papszTokens); |
7503 | 82.2k | continue; |
7504 | 82.2k | } |
7505 | 110k | if (oMapVarToId.find(pszVarName) != oMapVarToId.end()) |
7506 | 41.2k | { |
7507 | 41.2k | CPLDebug("netCDF", |
7508 | 41.2k | "nc_def_var(%s) failed: already defined", |
7509 | 41.2k | pszVarName); |
7510 | 41.2k | CSLDestroy(papszTokens); |
7511 | 41.2k | continue; |
7512 | 41.2k | } |
7513 | 69.3k | const char *pszVarType = papszTokens[0]; |
7514 | 69.3k | int nc_datatype = NC_BYTE; |
7515 | 69.3k | size_t nDataTypeSize = 1; |
7516 | 69.3k | if (EQUAL(pszVarType, "char")) |
7517 | 1.31k | { |
7518 | 1.31k | nc_datatype = NC_CHAR; |
7519 | 1.31k | nDataTypeSize = 1; |
7520 | 1.31k | } |
7521 | 68.0k | else if (EQUAL(pszVarType, "byte")) |
7522 | 0 | { |
7523 | 0 | nc_datatype = NC_BYTE; |
7524 | 0 | nDataTypeSize = 1; |
7525 | 0 | } |
7526 | 68.0k | else if (EQUAL(pszVarType, "short")) |
7527 | 0 | { |
7528 | 0 | nc_datatype = NC_SHORT; |
7529 | 0 | nDataTypeSize = 2; |
7530 | 0 | } |
7531 | 68.0k | else if (EQUAL(pszVarType, "int")) |
7532 | 35 | { |
7533 | 35 | nc_datatype = NC_INT; |
7534 | 35 | nDataTypeSize = 4; |
7535 | 35 | } |
7536 | 68.0k | else if (EQUAL(pszVarType, "float")) |
7537 | 0 | { |
7538 | 0 | nc_datatype = NC_FLOAT; |
7539 | 0 | nDataTypeSize = 4; |
7540 | 0 | } |
7541 | 68.0k | else if (EQUAL(pszVarType, "double")) |
7542 | 43 | { |
7543 | 43 | nc_datatype = NC_DOUBLE; |
7544 | 43 | nDataTypeSize = 8; |
7545 | 43 | } |
7546 | 67.9k | else if (EQUAL(pszVarType, "ubyte")) |
7547 | 0 | { |
7548 | 0 | nc_datatype = NC_UBYTE; |
7549 | 0 | nDataTypeSize = 1; |
7550 | 0 | } |
7551 | 67.9k | else if (EQUAL(pszVarType, "ushort")) |
7552 | 0 | { |
7553 | 0 | nc_datatype = NC_USHORT; |
7554 | 0 | nDataTypeSize = 2; |
7555 | 0 | } |
7556 | 67.9k | else if (EQUAL(pszVarType, "uint")) |
7557 | 18 | { |
7558 | 18 | nc_datatype = NC_UINT; |
7559 | 18 | nDataTypeSize = 4; |
7560 | 18 | } |
7561 | 67.9k | else if (EQUAL(pszVarType, "int64")) |
7562 | 0 | { |
7563 | 0 | nc_datatype = NC_INT64; |
7564 | 0 | nDataTypeSize = 8; |
7565 | 0 | } |
7566 | 67.9k | else if (EQUAL(pszVarType, "uint64")) |
7567 | 0 | { |
7568 | 0 | nc_datatype = NC_UINT64; |
7569 | 0 | nDataTypeSize = 8; |
7570 | 0 | } |
7571 | | |
7572 | 69.3k | int nDims = CSLCount(papszTokens) - 2; |
7573 | 69.3k | if (nDims >= 32) |
7574 | 88 | { |
7575 | | // The number of dimensions in a netCDFv4 file is |
7576 | | // limited by #define H5S_MAX_RANK 32 |
7577 | | // but libnetcdf doesn't check that... |
7578 | 88 | CPLDebug("netCDF", |
7579 | 88 | "nc_def_var(%s) failed: too many dimensions", |
7580 | 88 | pszVarName); |
7581 | 88 | CSLDestroy(papszTokens); |
7582 | 88 | continue; |
7583 | 88 | } |
7584 | 69.2k | std::vector<int> aoDimIds; |
7585 | 69.2k | bool bFailed = false; |
7586 | 69.2k | size_t nSize = 1; |
7587 | 83.0k | for (int i = 0; i < nDims; i++) |
7588 | 76.9k | { |
7589 | 76.9k | const char *pszDimName = papszTokens[2 + i]; |
7590 | 76.9k | if (oMapDimToId.find(pszDimName) == oMapDimToId.end()) |
7591 | 63.2k | { |
7592 | 63.2k | bFailed = true; |
7593 | 63.2k | break; |
7594 | 63.2k | } |
7595 | 13.7k | const int nDimId = oMapDimToId[pszDimName]; |
7596 | 13.7k | aoDimIds.push_back(nDimId); |
7597 | | |
7598 | 13.7k | const size_t nDimSize = oMapDimIdToDimLen[nDimId]; |
7599 | 13.7k | if (nDimSize != 0) |
7600 | 5.51k | { |
7601 | 5.51k | if (nSize > |
7602 | 5.51k | std::numeric_limits<size_t>::max() / nDimSize) |
7603 | 0 | { |
7604 | 0 | bFailed = true; |
7605 | 0 | break; |
7606 | 0 | } |
7607 | 5.51k | else |
7608 | 5.51k | { |
7609 | 5.51k | nSize *= nDimSize; |
7610 | 5.51k | } |
7611 | 5.51k | } |
7612 | 13.7k | } |
7613 | 69.2k | if (bFailed) |
7614 | 63.2k | { |
7615 | 63.2k | CPLDebug("netCDF", |
7616 | 63.2k | "nc_def_var(%s) failed: unknown dimension(s)", |
7617 | 63.2k | pszVarName); |
7618 | 63.2k | CSLDestroy(papszTokens); |
7619 | 63.2k | continue; |
7620 | 63.2k | } |
7621 | 6.05k | if (nSize > 100U * 1024 * 1024 / nDataTypeSize) |
7622 | 0 | { |
7623 | 0 | CPLDebug("netCDF", |
7624 | 0 | "nc_def_var(%s) failed: too large data", |
7625 | 0 | pszVarName); |
7626 | 0 | CSLDestroy(papszTokens); |
7627 | 0 | continue; |
7628 | 0 | } |
7629 | 6.05k | if (nTotalVarSize > |
7630 | 6.05k | std::numeric_limits<size_t>::max() - nSize || |
7631 | 6.05k | nTotalVarSize + nSize > 100 * 1024 * 1024) |
7632 | 0 | { |
7633 | 0 | CPLDebug("netCDF", |
7634 | 0 | "nc_def_var(%s) failed: too large data", |
7635 | 0 | pszVarName); |
7636 | 0 | CSLDestroy(papszTokens); |
7637 | 0 | continue; |
7638 | 0 | } |
7639 | 6.05k | nTotalVarSize += nSize; |
7640 | | |
7641 | 6.05k | int nVarId = -1; |
7642 | 6.05k | status = |
7643 | 6.05k | nc_def_var(nCdfId, pszVarName, nc_datatype, nDims, |
7644 | 6.05k | (nDims) ? &aoDimIds[0] : nullptr, &nVarId); |
7645 | 6.05k | if (status != NC_NOERR) |
7646 | 255 | { |
7647 | 255 | CPLDebug("netCDF", "nc_def_var(%s) failed: %s", |
7648 | 255 | pszVarName, nc_strerror(status)); |
7649 | 255 | } |
7650 | 5.79k | else |
7651 | 5.79k | { |
7652 | | #ifdef DEBUG_VERBOSE |
7653 | | CPLDebug("netCDF", "nc_def_var(%s) (%s) succeeded", |
7654 | | pszVarName, pszLine); |
7655 | | #endif |
7656 | 5.79k | oMapVarToId[pszVarName] = nVarId; |
7657 | 5.79k | oMapVarIdToType[nVarId] = nc_datatype; |
7658 | 5.79k | oMapVarIdToVectorOfDimId[nVarId] = std::move(aoDimIds); |
7659 | 5.79k | } |
7660 | 6.05k | } |
7661 | 128k | CSLDestroy(papszTokens); |
7662 | 128k | } |
7663 | 62.2k | else if (pszEqual != nullptr && pszEqual - pszColumn > 0) |
7664 | 20.3k | { |
7665 | 20.3k | CPLString osVarName(pszLine, pszColumn - pszLine); |
7666 | 20.3k | CPLString osAttrName(pszColumn + 1, pszEqual - pszColumn - 1); |
7667 | 20.3k | osAttrName.Trim(); |
7668 | 20.3k | if (oMapVarToId.find(osVarName) == oMapVarToId.end()) |
7669 | 5.97k | { |
7670 | 5.97k | CPLDebug("netCDF", |
7671 | 5.97k | "nc_put_att(%s:%s) failed: " |
7672 | 5.97k | "no corresponding variable", |
7673 | 5.97k | osVarName.c_str(), osAttrName.c_str()); |
7674 | 5.97k | continue; |
7675 | 5.97k | } |
7676 | 14.3k | bool bValidName = true; |
7677 | 239k | for (size_t i = 0; i < osAttrName.size(); i++) |
7678 | 225k | { |
7679 | 225k | if (!((osAttrName[i] >= 'a' && osAttrName[i] <= 'z') || |
7680 | 225k | (osAttrName[i] >= 'A' && osAttrName[i] <= 'Z') || |
7681 | 225k | (osAttrName[i] >= '0' && osAttrName[i] <= '9') || |
7682 | 225k | osAttrName[i] == '_')) |
7683 | 84.0k | { |
7684 | 84.0k | bValidName = false; |
7685 | 84.0k | } |
7686 | 225k | } |
7687 | 14.3k | if (!bValidName) |
7688 | 2.36k | { |
7689 | 2.36k | CPLDebug( |
7690 | 2.36k | "netCDF", |
7691 | 2.36k | "nc_put_att(%s:%s) failed: illegal character found", |
7692 | 2.36k | osVarName.c_str(), osAttrName.c_str()); |
7693 | 2.36k | continue; |
7694 | 2.36k | } |
7695 | 11.9k | if (oSetAttrDefined.find(osVarName + ":" + osAttrName) != |
7696 | 11.9k | oSetAttrDefined.end()) |
7697 | 7.59k | { |
7698 | 7.59k | CPLDebug("netCDF", |
7699 | 7.59k | "nc_put_att(%s:%s) failed: already defined", |
7700 | 7.59k | osVarName.c_str(), osAttrName.c_str()); |
7701 | 7.59k | continue; |
7702 | 7.59k | } |
7703 | | |
7704 | 4.39k | const int nVarId = oMapVarToId[osVarName]; |
7705 | 4.39k | const char *pszValue = pszEqual + 1; |
7706 | 6.73k | while (*pszValue == ' ') |
7707 | 2.33k | pszValue++; |
7708 | | |
7709 | 4.39k | status = NC_EBADTYPE; |
7710 | 4.39k | if (*pszValue == '"') |
7711 | 493 | { |
7712 | | // For _FillValue, the attribute type should match |
7713 | | // the variable type. Leaks memory with NC4 otherwise |
7714 | 493 | if (osAttrName == "_FillValue") |
7715 | 0 | { |
7716 | 0 | CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s", |
7717 | 0 | osVarName.c_str(), osAttrName.c_str(), |
7718 | 0 | nc_strerror(status)); |
7719 | 0 | continue; |
7720 | 0 | } |
7721 | | |
7722 | | // Unquote and unescape string value |
7723 | 493 | CPLString osVal(pszValue + 1); |
7724 | 5.24k | while (!osVal.empty()) |
7725 | 5.00k | { |
7726 | 5.00k | if (osVal.back() == ';' || osVal.back() == ' ') |
7727 | 4.75k | { |
7728 | 4.75k | osVal.pop_back(); |
7729 | 4.75k | } |
7730 | 258 | else if (osVal.back() == '"') |
7731 | 19 | { |
7732 | 19 | osVal.pop_back(); |
7733 | 19 | break; |
7734 | 19 | } |
7735 | 239 | else |
7736 | 239 | { |
7737 | 239 | break; |
7738 | 239 | } |
7739 | 5.00k | } |
7740 | 493 | osVal.replaceAll("\\\"", '"'); |
7741 | 493 | status = nc_put_att_text(nCdfId, nVarId, osAttrName, |
7742 | 493 | osVal.size(), osVal.c_str()); |
7743 | 493 | } |
7744 | 3.90k | else |
7745 | 3.90k | { |
7746 | 3.90k | CPLString osVal(pszValue); |
7747 | 3.94k | while (!osVal.empty()) |
7748 | 3.85k | { |
7749 | 3.85k | if (osVal.back() == ';' || osVal.back() == ' ') |
7750 | 38 | { |
7751 | 38 | osVal.pop_back(); |
7752 | 38 | } |
7753 | 3.81k | else |
7754 | 3.81k | { |
7755 | 3.81k | break; |
7756 | 3.81k | } |
7757 | 3.85k | } |
7758 | 3.90k | int nc_datatype = -1; |
7759 | 3.90k | if (!osVal.empty() && osVal.back() == 'b') |
7760 | 2 | { |
7761 | 2 | nc_datatype = NC_BYTE; |
7762 | 2 | osVal.pop_back(); |
7763 | 2 | } |
7764 | 3.90k | else if (!osVal.empty() && osVal.back() == 's') |
7765 | 0 | { |
7766 | 0 | nc_datatype = NC_SHORT; |
7767 | 0 | osVal.pop_back(); |
7768 | 0 | } |
7769 | 3.90k | if (CPLGetValueType(osVal) == CPL_VALUE_INTEGER) |
7770 | 1.72k | { |
7771 | 1.72k | if (nc_datatype < 0) |
7772 | 1.72k | nc_datatype = NC_INT; |
7773 | 1.72k | } |
7774 | 2.17k | else if (CPLGetValueType(osVal) == CPL_VALUE_REAL) |
7775 | 141 | { |
7776 | 141 | nc_datatype = NC_DOUBLE; |
7777 | 141 | } |
7778 | 2.03k | else |
7779 | 2.03k | { |
7780 | 2.03k | nc_datatype = -1; |
7781 | 2.03k | } |
7782 | | |
7783 | | // For _FillValue, check that the attribute type matches |
7784 | | // the variable type. Leaks memory with NC4 otherwise |
7785 | 3.90k | if (osAttrName == "_FillValue") |
7786 | 0 | { |
7787 | 0 | if (nVarId < 0 || |
7788 | 0 | nc_datatype != oMapVarIdToType[nVarId]) |
7789 | 0 | { |
7790 | 0 | nc_datatype = -1; |
7791 | 0 | } |
7792 | 0 | } |
7793 | | |
7794 | 3.90k | if (nc_datatype == NC_BYTE) |
7795 | 1 | { |
7796 | 1 | signed char chVal = |
7797 | 1 | static_cast<signed char>(atoi(osVal)); |
7798 | 1 | status = nc_put_att_schar(nCdfId, nVarId, osAttrName, |
7799 | 1 | NC_BYTE, 1, &chVal); |
7800 | 1 | } |
7801 | 3.90k | else if (nc_datatype == NC_SHORT) |
7802 | 0 | { |
7803 | 0 | short nVal = static_cast<short>(atoi(osVal)); |
7804 | 0 | status = nc_put_att_short(nCdfId, nVarId, osAttrName, |
7805 | 0 | NC_SHORT, 1, &nVal); |
7806 | 0 | } |
7807 | 3.90k | else if (nc_datatype == NC_INT) |
7808 | 1.72k | { |
7809 | 1.72k | int nVal = static_cast<int>(atoi(osVal)); |
7810 | 1.72k | status = nc_put_att_int(nCdfId, nVarId, osAttrName, |
7811 | 1.72k | NC_INT, 1, &nVal); |
7812 | 1.72k | } |
7813 | 2.17k | else if (nc_datatype == NC_DOUBLE) |
7814 | 141 | { |
7815 | 141 | double dfVal = CPLAtof(osVal); |
7816 | 141 | status = nc_put_att_double(nCdfId, nVarId, osAttrName, |
7817 | 141 | NC_DOUBLE, 1, &dfVal); |
7818 | 141 | } |
7819 | 3.90k | } |
7820 | 4.39k | if (status != NC_NOERR) |
7821 | 2.10k | { |
7822 | 2.10k | CPLDebug("netCDF", "nc_put_att_(%s:%s) failed: %s", |
7823 | 2.10k | osVarName.c_str(), osAttrName.c_str(), |
7824 | 2.10k | nc_strerror(status)); |
7825 | 2.10k | } |
7826 | 2.29k | else |
7827 | 2.29k | { |
7828 | 2.29k | oSetAttrDefined.insert(osVarName + ":" + osAttrName); |
7829 | | #ifdef DEBUG_VERBOSE |
7830 | | CPLDebug("netCDF", "nc_put_att_(%s:%s) (%s) succeeded", |
7831 | | osVarName.c_str(), osAttrName.c_str(), pszLine); |
7832 | | #endif |
7833 | 2.29k | } |
7834 | 4.39k | } |
7835 | 377k | } |
7836 | 182k | else if (nActiveSection == SECTION_DATA) |
7837 | 181k | { |
7838 | 301k | while (*pszLine == ' ' || *pszLine == '\t') |
7839 | 120k | pszLine++; |
7840 | 181k | const char *pszEqual = strchr(pszLine, '='); |
7841 | 181k | if (pszEqual) |
7842 | 41.6k | { |
7843 | 41.6k | CPLString osVarName(pszLine, pszEqual - pszLine); |
7844 | 41.6k | osVarName.Trim(); |
7845 | 41.6k | if (oMapVarToId.find(osVarName) == oMapVarToId.end()) |
7846 | 39.8k | continue; |
7847 | 1.73k | const int nVarId = oMapVarToId[osVarName]; |
7848 | 1.73k | CPLString osAccVal(pszEqual + 1); |
7849 | 1.73k | osAccVal.Trim(); |
7850 | 26.4k | while (osAccVal.empty() || osAccVal.back() != ';') |
7851 | 24.8k | { |
7852 | 24.8k | pszLine = CPLReadLineL(fpSrc); |
7853 | 24.8k | if (pszLine == nullptr) |
7854 | 143 | break; |
7855 | 24.7k | CPLString osVal(pszLine); |
7856 | 24.7k | osVal.Trim(); |
7857 | 24.7k | osAccVal += osVal; |
7858 | 24.7k | } |
7859 | 1.73k | if (pszLine == nullptr) |
7860 | 143 | break; |
7861 | 1.58k | osAccVal.pop_back(); |
7862 | | |
7863 | 1.58k | const std::vector<int> aoDimIds = |
7864 | 1.58k | oMapVarIdToVectorOfDimId[nVarId]; |
7865 | 1.58k | size_t nSize = 1; |
7866 | 1.58k | std::vector<size_t> aoStart, aoEdge; |
7867 | 1.58k | aoStart.resize(aoDimIds.size()); |
7868 | 1.58k | aoEdge.resize(aoDimIds.size()); |
7869 | 4.26k | for (size_t i = 0; i < aoDimIds.size(); ++i) |
7870 | 2.67k | { |
7871 | 2.67k | const size_t nDimSize = oMapDimIdToDimLen[aoDimIds[i]]; |
7872 | 2.67k | if (nDimSize != 0 && |
7873 | 2.67k | nSize > std::numeric_limits<size_t>::max() / nDimSize) |
7874 | 0 | { |
7875 | 0 | nSize = 0; |
7876 | 0 | } |
7877 | 2.67k | else |
7878 | 2.67k | { |
7879 | 2.67k | nSize *= nDimSize; |
7880 | 2.67k | } |
7881 | 2.67k | aoStart[i] = 0; |
7882 | 2.67k | aoEdge[i] = nDimSize; |
7883 | 2.67k | } |
7884 | | |
7885 | 1.58k | status = NC_EBADTYPE; |
7886 | 1.58k | if (nSize == 0) |
7887 | 0 | { |
7888 | | // Might happen with a unlimited dimension |
7889 | 0 | } |
7890 | 1.58k | else if (oMapVarIdToType[nVarId] == NC_DOUBLE) |
7891 | 0 | { |
7892 | 0 | if (!aoStart.empty()) |
7893 | 0 | { |
7894 | 0 | char **papszTokens = |
7895 | 0 | CSLTokenizeString2(osAccVal, " ,;", 0); |
7896 | 0 | size_t nTokens = CSLCount(papszTokens); |
7897 | 0 | if (nTokens >= nSize) |
7898 | 0 | { |
7899 | 0 | double *padfVals = static_cast<double *>( |
7900 | 0 | VSI_CALLOC_VERBOSE(nSize, sizeof(double))); |
7901 | 0 | if (padfVals) |
7902 | 0 | { |
7903 | 0 | for (size_t i = 0; i < nSize; i++) |
7904 | 0 | { |
7905 | 0 | padfVals[i] = CPLAtof(papszTokens[i]); |
7906 | 0 | } |
7907 | 0 | status = nc_put_vara_double( |
7908 | 0 | nCdfId, nVarId, &aoStart[0], &aoEdge[0], |
7909 | 0 | padfVals); |
7910 | 0 | VSIFree(padfVals); |
7911 | 0 | } |
7912 | 0 | } |
7913 | 0 | CSLDestroy(papszTokens); |
7914 | 0 | } |
7915 | 0 | } |
7916 | 1.58k | else if (oMapVarIdToType[nVarId] == NC_BYTE) |
7917 | 304 | { |
7918 | 304 | if (!aoStart.empty()) |
7919 | 301 | { |
7920 | 301 | char **papszTokens = |
7921 | 301 | CSLTokenizeString2(osAccVal, " ,;", 0); |
7922 | 301 | size_t nTokens = CSLCount(papszTokens); |
7923 | 301 | if (nTokens >= nSize) |
7924 | 1 | { |
7925 | 1 | signed char *panVals = static_cast<signed char *>( |
7926 | 1 | VSI_CALLOC_VERBOSE(nSize, sizeof(signed char))); |
7927 | 1 | if (panVals) |
7928 | 1 | { |
7929 | 1.00k | for (size_t i = 0; i < nSize; i++) |
7930 | 1.00k | { |
7931 | 1.00k | panVals[i] = static_cast<signed char>( |
7932 | 1.00k | atoi(papszTokens[i])); |
7933 | 1.00k | } |
7934 | 1 | status = nc_put_vara_schar(nCdfId, nVarId, |
7935 | 1 | &aoStart[0], |
7936 | 1 | &aoEdge[0], panVals); |
7937 | 1 | VSIFree(panVals); |
7938 | 1 | } |
7939 | 1 | } |
7940 | 301 | CSLDestroy(papszTokens); |
7941 | 301 | } |
7942 | 304 | } |
7943 | 1.28k | else if (oMapVarIdToType[nVarId] == NC_CHAR) |
7944 | 1.06k | { |
7945 | 1.06k | if (aoStart.size() == 2) |
7946 | 1.06k | { |
7947 | 1.06k | std::vector<CPLString> aoStrings; |
7948 | 1.06k | bool bInString = false; |
7949 | 1.06k | CPLString osCurString; |
7950 | 217k | for (size_t i = 0; i < osAccVal.size();) |
7951 | 216k | { |
7952 | 216k | if (!bInString) |
7953 | 78.3k | { |
7954 | 78.3k | if (osAccVal[i] == '"') |
7955 | 12.7k | { |
7956 | 12.7k | bInString = true; |
7957 | 12.7k | osCurString.clear(); |
7958 | 12.7k | } |
7959 | 78.3k | i++; |
7960 | 78.3k | } |
7961 | 138k | else if (osAccVal[i] == '\\' && |
7962 | 138k | i + 1 < osAccVal.size() && |
7963 | 138k | osAccVal[i + 1] == '"') |
7964 | 1.07k | { |
7965 | 1.07k | osCurString += '"'; |
7966 | 1.07k | i += 2; |
7967 | 1.07k | } |
7968 | 137k | else if (osAccVal[i] == '"') |
7969 | 12.0k | { |
7970 | 12.0k | aoStrings.push_back(osCurString); |
7971 | 12.0k | osCurString.clear(); |
7972 | 12.0k | bInString = false; |
7973 | 12.0k | i++; |
7974 | 12.0k | } |
7975 | 125k | else |
7976 | 125k | { |
7977 | 125k | osCurString += osAccVal[i]; |
7978 | 125k | i++; |
7979 | 125k | } |
7980 | 216k | } |
7981 | 1.06k | const size_t nRecords = oMapDimIdToDimLen[aoDimIds[0]]; |
7982 | 1.06k | const size_t nWidth = oMapDimIdToDimLen[aoDimIds[1]]; |
7983 | 1.06k | size_t nIters = aoStrings.size(); |
7984 | 1.06k | if (nIters > nRecords) |
7985 | 58 | nIters = nRecords; |
7986 | 11.9k | for (size_t i = 0; i < nIters; i++) |
7987 | 10.9k | { |
7988 | 10.9k | size_t anIndex[2]; |
7989 | 10.9k | anIndex[0] = i; |
7990 | 10.9k | anIndex[1] = 0; |
7991 | 10.9k | size_t anCount[2]; |
7992 | 10.9k | anCount[0] = 1; |
7993 | 10.9k | anCount[1] = aoStrings[i].size(); |
7994 | 10.9k | if (anCount[1] > nWidth) |
7995 | 21 | anCount[1] = nWidth; |
7996 | 10.9k | status = |
7997 | 10.9k | nc_put_vara_text(nCdfId, nVarId, anIndex, |
7998 | 10.9k | anCount, aoStrings[i].c_str()); |
7999 | 10.9k | if (status != NC_NOERR) |
8000 | 0 | break; |
8001 | 10.9k | } |
8002 | 1.06k | } |
8003 | 1.06k | } |
8004 | 1.58k | if (status != NC_NOERR) |
8005 | 527 | { |
8006 | 527 | CPLDebug("netCDF", "nc_put_var_(%s) failed: %s", |
8007 | 527 | osVarName.c_str(), nc_strerror(status)); |
8008 | 527 | } |
8009 | 1.58k | } |
8010 | 181k | } |
8011 | 664k | } |
8012 | | |
8013 | 399 | GDAL_nc_close(nCdfId); |
8014 | 399 | return true; |
8015 | 399 | } |
8016 | | |
8017 | | #endif // ENABLE_NCDUMP |
8018 | | |
8019 | | /************************************************************************/ |
8020 | | /* Open() */ |
8021 | | /************************************************************************/ |
8022 | | |
8023 | | GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo) |
8024 | | |
8025 | 399 | { |
8026 | | #ifdef NCDF_DEBUG |
8027 | | CPLDebug("GDAL_netCDF", "\n=====\nOpen(), filename=[%s]", |
8028 | | poOpenInfo->pszFilename); |
8029 | | #endif |
8030 | | |
8031 | | // Does this appear to be a netcdf file? |
8032 | 399 | NetCDFFormatEnum eTmpFormat = NCDF_FORMAT_NONE; |
8033 | 399 | if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:")) |
8034 | 399 | { |
8035 | 399 | eTmpFormat = netCDFIdentifyFormat(poOpenInfo, /* bCheckExt = */ true); |
8036 | | #ifdef NCDF_DEBUG |
8037 | | CPLDebug("GDAL_netCDF", "identified format %d", eTmpFormat); |
8038 | | #endif |
8039 | | // Note: not calling Identify() directly, because we want the file type. |
8040 | | // Only support NCDF_FORMAT* formats. |
8041 | 399 | if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat || |
8042 | 399 | NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat) |
8043 | 399 | { |
8044 | | // ok |
8045 | 399 | } |
8046 | 0 | else if (eTmpFormat == NCDF_FORMAT_HDF4 && |
8047 | 0 | poOpenInfo->IsSingleAllowedDriver("netCDF")) |
8048 | 0 | { |
8049 | | // ok |
8050 | 0 | } |
8051 | 0 | else |
8052 | 0 | { |
8053 | 0 | return nullptr; |
8054 | 0 | } |
8055 | 399 | } |
8056 | 0 | else |
8057 | 0 | { |
8058 | 0 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
8059 | | // We don't necessarily want to catch bugs in libnetcdf ... |
8060 | 0 | if (CPLGetConfigOption("DISABLE_OPEN_REAL_NETCDF_FILES", nullptr)) |
8061 | 0 | { |
8062 | 0 | return nullptr; |
8063 | 0 | } |
8064 | 0 | #endif |
8065 | 0 | } |
8066 | | |
8067 | 399 | if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER) |
8068 | 0 | { |
8069 | 0 | return OpenMultiDim(poOpenInfo); |
8070 | 0 | } |
8071 | | |
8072 | 399 | CPLMutexHolderD(&hNCMutex); |
8073 | | |
8074 | 399 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with |
8075 | | // GDALDataset own mutex. |
8076 | 399 | netCDFDataset *poDS = new netCDFDataset(); |
8077 | 399 | poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions); |
8078 | 399 | CPLAcquireMutex(hNCMutex, 1000.0); |
8079 | | |
8080 | 399 | poDS->SetDescription(poOpenInfo->pszFilename); |
8081 | | |
8082 | | // Check if filename start with NETCDF: tag. |
8083 | 399 | bool bTreatAsSubdataset = false; |
8084 | 399 | CPLString osSubdatasetName; |
8085 | | |
8086 | 399 | #ifdef ENABLE_NCDUMP |
8087 | 399 | const char *pszHeader = |
8088 | 399 | reinterpret_cast<const char *>(poOpenInfo->pabyHeader); |
8089 | 399 | if (poOpenInfo->fpL != nullptr && STARTS_WITH(pszHeader, "netcdf ") && |
8090 | 399 | strstr(pszHeader, "dimensions:") && strstr(pszHeader, "variables:")) |
8091 | 399 | { |
8092 | | // By default create a temporary file that will be destroyed, |
8093 | | // unless NETCDF_TMP_FILE is defined. Can be useful to see which |
8094 | | // netCDF file has been generated from a potential fuzzed input. |
8095 | 399 | poDS->osFilename = CPLGetConfigOption("NETCDF_TMP_FILE", ""); |
8096 | 399 | if (poDS->osFilename.empty()) |
8097 | 399 | { |
8098 | 399 | poDS->bFileToDestroyAtClosing = true; |
8099 | 399 | poDS->osFilename = CPLGenerateTempFilenameSafe("netcdf_tmp"); |
8100 | 399 | } |
8101 | 399 | if (!netCDFDatasetCreateTempFile(eTmpFormat, poDS->osFilename, |
8102 | 399 | poOpenInfo->fpL)) |
8103 | 0 | { |
8104 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8105 | | // deadlock with GDALDataset own mutex. |
8106 | 0 | delete poDS; |
8107 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8108 | 0 | return nullptr; |
8109 | 0 | } |
8110 | 399 | bTreatAsSubdataset = false; |
8111 | 399 | poDS->eFormat = eTmpFormat; |
8112 | 399 | } |
8113 | 0 | else |
8114 | 0 | #endif |
8115 | | |
8116 | 0 | if (STARTS_WITH_CI(poOpenInfo->pszFilename, "NETCDF:")) |
8117 | 0 | { |
8118 | 0 | char **papszName = |
8119 | 0 | CSLTokenizeString2(poOpenInfo->pszFilename, ":", |
8120 | 0 | CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES); |
8121 | |
|
8122 | 0 | if (CSLCount(papszName) >= 3 && |
8123 | 0 | ((strlen(papszName[1]) == 1 && /* D:\\bla */ |
8124 | 0 | (papszName[2][0] == '/' || papszName[2][0] == '\\')) || |
8125 | 0 | EQUAL(papszName[1], "http") || EQUAL(papszName[1], "https") || |
8126 | 0 | EQUAL(papszName[1], "/vsicurl/http") || |
8127 | 0 | EQUAL(papszName[1], "/vsicurl/https") || |
8128 | 0 | EQUAL(papszName[1], "/vsicurl_streaming/http") || |
8129 | 0 | EQUAL(papszName[1], "/vsicurl_streaming/https"))) |
8130 | 0 | { |
8131 | 0 | const int nCountBefore = CSLCount(papszName); |
8132 | 0 | CPLString osTmp = papszName[1]; |
8133 | 0 | osTmp += ':'; |
8134 | 0 | osTmp += papszName[2]; |
8135 | 0 | CPLFree(papszName[1]); |
8136 | 0 | CPLFree(papszName[2]); |
8137 | 0 | papszName[1] = CPLStrdup(osTmp); |
8138 | 0 | memmove(papszName + 2, papszName + 3, |
8139 | 0 | (nCountBefore - 2) * sizeof(char *)); |
8140 | 0 | } |
8141 | |
|
8142 | 0 | if (CSLCount(papszName) == 3) |
8143 | 0 | { |
8144 | 0 | poDS->osFilename = papszName[1]; |
8145 | 0 | osSubdatasetName = papszName[2]; |
8146 | 0 | bTreatAsSubdataset = true; |
8147 | 0 | CSLDestroy(papszName); |
8148 | 0 | } |
8149 | 0 | else if (CSLCount(papszName) == 2) |
8150 | 0 | { |
8151 | 0 | poDS->osFilename = papszName[1]; |
8152 | 0 | osSubdatasetName = ""; |
8153 | 0 | bTreatAsSubdataset = false; |
8154 | 0 | CSLDestroy(papszName); |
8155 | 0 | } |
8156 | 0 | else |
8157 | 0 | { |
8158 | 0 | CSLDestroy(papszName); |
8159 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8160 | | // deadlock with GDALDataset own mutex. |
8161 | 0 | delete poDS; |
8162 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8163 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
8164 | 0 | "Failed to parse NETCDF: prefix string into expected 2, 3 " |
8165 | 0 | "or 4 fields."); |
8166 | 0 | return nullptr; |
8167 | 0 | } |
8168 | | |
8169 | 0 | if (!STARTS_WITH(poDS->osFilename, "http://") && |
8170 | 0 | !STARTS_WITH(poDS->osFilename, "https://")) |
8171 | 0 | { |
8172 | | // Identify Format from real file, with bCheckExt=FALSE. |
8173 | 0 | GDALOpenInfo *poOpenInfo2 = |
8174 | 0 | new GDALOpenInfo(poDS->osFilename.c_str(), GA_ReadOnly); |
8175 | 0 | poDS->eFormat = |
8176 | 0 | netCDFIdentifyFormat(poOpenInfo2, /* bCheckExt = */ false); |
8177 | 0 | delete poOpenInfo2; |
8178 | 0 | if (NCDF_FORMAT_NONE == poDS->eFormat || |
8179 | 0 | NCDF_FORMAT_UNKNOWN == poDS->eFormat) |
8180 | 0 | { |
8181 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8182 | | // deadlock with GDALDataset own mutex. |
8183 | 0 | delete poDS; |
8184 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8185 | 0 | return nullptr; |
8186 | 0 | } |
8187 | 0 | } |
8188 | 0 | } |
8189 | 0 | else |
8190 | 0 | { |
8191 | 0 | poDS->osFilename = poOpenInfo->pszFilename; |
8192 | 0 | bTreatAsSubdataset = false; |
8193 | 0 | poDS->eFormat = eTmpFormat; |
8194 | 0 | } |
8195 | | |
8196 | | // Try opening the dataset. |
8197 | | #if defined(NCDF_DEBUG) && defined(ENABLE_UFFD) |
8198 | | CPLDebug("GDAL_netCDF", "calling nc_open_mem(%s)", |
8199 | | poDS->osFilename.c_str()); |
8200 | | #elif defined(NCDF_DEBUG) && !defined(ENABLE_UFFD) |
8201 | | CPLDebug("GDAL_netCDF", "calling nc_open(%s)", poDS->osFilename.c_str()); |
8202 | | #endif |
8203 | 399 | int cdfid = -1; |
8204 | 399 | const int nMode = ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0) |
8205 | 399 | ? NC_WRITE |
8206 | 399 | : NC_NOWRITE; |
8207 | 399 | CPLString osFilenameForNCOpen(poDS->osFilename); |
8208 | | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8) |
8209 | | if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) |
8210 | | { |
8211 | | char *pszTemp = CPLRecode(osFilenameForNCOpen, CPL_ENC_UTF8, "CP_ACP"); |
8212 | | osFilenameForNCOpen = pszTemp; |
8213 | | CPLFree(pszTemp); |
8214 | | } |
8215 | | #endif |
8216 | 399 | int status2 = -1; |
8217 | | |
8218 | 399 | #ifdef ENABLE_UFFD |
8219 | 399 | cpl_uffd_context *pCtx = nullptr; |
8220 | 399 | #endif |
8221 | | |
8222 | 399 | if (STARTS_WITH(osFilenameForNCOpen, "/vsimem/") && |
8223 | 399 | poOpenInfo->eAccess == GA_ReadOnly) |
8224 | 0 | { |
8225 | 0 | vsi_l_offset nLength = 0; |
8226 | 0 | poDS->fpVSIMEM = VSIFOpenL(osFilenameForNCOpen, "rb"); |
8227 | 0 | if (poDS->fpVSIMEM) |
8228 | 0 | { |
8229 | | // We assume that the file will not be modified. If it is, then |
8230 | | // pabyBuffer might become invalid. |
8231 | 0 | GByte *pabyBuffer = |
8232 | 0 | VSIGetMemFileBuffer(osFilenameForNCOpen, &nLength, false); |
8233 | 0 | if (pabyBuffer) |
8234 | 0 | { |
8235 | 0 | status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), |
8236 | 0 | nMode, static_cast<size_t>(nLength), |
8237 | 0 | pabyBuffer, &cdfid); |
8238 | 0 | } |
8239 | 0 | } |
8240 | 0 | } |
8241 | 399 | else |
8242 | 399 | { |
8243 | 399 | const bool bVsiFile = |
8244 | 399 | !strncmp(osFilenameForNCOpen, "/vsi", strlen("/vsi")); |
8245 | 399 | #ifdef ENABLE_UFFD |
8246 | 399 | bool bReadOnly = (poOpenInfo->eAccess == GA_ReadOnly); |
8247 | 399 | void *pVma = nullptr; |
8248 | 399 | uint64_t nVmaSize = 0; |
8249 | | |
8250 | 399 | if (bVsiFile) |
8251 | 0 | { |
8252 | 0 | if (bReadOnly) |
8253 | 0 | { |
8254 | 0 | if (CPLIsUserFaultMappingSupported()) |
8255 | 0 | { |
8256 | 0 | pCtx = CPLCreateUserFaultMapping(osFilenameForNCOpen, &pVma, |
8257 | 0 | &nVmaSize); |
8258 | 0 | } |
8259 | 0 | else |
8260 | 0 | { |
8261 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
8262 | 0 | "Opening a /vsi file with the netCDF driver " |
8263 | 0 | "requires Linux userfaultfd to be available. " |
8264 | 0 | "If running from Docker, " |
8265 | 0 | "--security-opt seccomp=unconfined might be " |
8266 | 0 | "needed.%s", |
8267 | 0 | ((poDS->eFormat == NCDF_FORMAT_NC4 || |
8268 | 0 | poDS->eFormat == NCDF_FORMAT_HDF5) && |
8269 | 0 | GDALGetDriverByName("HDF5")) |
8270 | 0 | ? " Or you may set the GDAL_SKIP=netCDF " |
8271 | 0 | "configuration option to force the use of " |
8272 | 0 | "the HDF5 driver." |
8273 | 0 | : ""); |
8274 | 0 | } |
8275 | 0 | } |
8276 | 0 | else |
8277 | 0 | { |
8278 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
8279 | 0 | "Opening a /vsi file with the netCDF driver is only " |
8280 | 0 | "supported in read-only mode"); |
8281 | 0 | } |
8282 | 0 | } |
8283 | 399 | if (pCtx != nullptr && pVma != nullptr && nVmaSize > 0) |
8284 | 0 | { |
8285 | | // netCDF code, at least for netCDF 4.7.0, is confused by filenames |
8286 | | // like /vsicurl/http[s]://example.com/foo.nc, so just pass the |
8287 | | // final part |
8288 | 0 | status2 = nc_open_mem(CPLGetFilename(osFilenameForNCOpen), nMode, |
8289 | 0 | static_cast<size_t>(nVmaSize), pVma, &cdfid); |
8290 | 0 | } |
8291 | 399 | else |
8292 | 399 | status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid); |
8293 | | #else |
8294 | | if (bVsiFile) |
8295 | | { |
8296 | | CPLError( |
8297 | | CE_Failure, CPLE_AppDefined, |
8298 | | "Opening a /vsi file with the netCDF driver requires Linux " |
8299 | | "userfaultfd to be available.%s", |
8300 | | ((poDS->eFormat == NCDF_FORMAT_NC4 || |
8301 | | poDS->eFormat == NCDF_FORMAT_HDF5) && |
8302 | | GDALGetDriverByName("HDF5")) |
8303 | | ? " Or you may set the GDAL_SKIP=netCDF " |
8304 | | "configuration option to force the use of the HDF5 " |
8305 | | "driver." |
8306 | | : ""); |
8307 | | status2 = NC_EIO; |
8308 | | } |
8309 | | else |
8310 | | { |
8311 | | status2 = GDAL_nc_open(osFilenameForNCOpen, nMode, &cdfid); |
8312 | | } |
8313 | | #endif |
8314 | 399 | } |
8315 | 399 | if (status2 != NC_NOERR) |
8316 | 0 | { |
8317 | | #ifdef NCDF_DEBUG |
8318 | | CPLDebug("GDAL_netCDF", "error opening"); |
8319 | | #endif |
8320 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
8321 | | // with GDALDataset own mutex. |
8322 | 0 | delete poDS; |
8323 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8324 | 0 | return nullptr; |
8325 | 0 | } |
8326 | | #ifdef NCDF_DEBUG |
8327 | | CPLDebug("GDAL_netCDF", "got cdfid=%d", cdfid); |
8328 | | #endif |
8329 | | |
8330 | 399 | #if defined(ENABLE_NCDUMP) && !defined(_WIN32) |
8331 | | // Try to destroy the temporary file right now on Unix |
8332 | 399 | if (poDS->bFileToDestroyAtClosing) |
8333 | 399 | { |
8334 | 399 | if (VSIUnlink(poDS->osFilename) == 0) |
8335 | 399 | { |
8336 | 399 | poDS->bFileToDestroyAtClosing = false; |
8337 | 399 | } |
8338 | 399 | } |
8339 | 399 | #endif |
8340 | | |
8341 | | // Is this a real netCDF file? |
8342 | 399 | int ndims; |
8343 | 399 | int ngatts; |
8344 | 399 | int nvars; |
8345 | 399 | int unlimdimid; |
8346 | 399 | int status = nc_inq(cdfid, &ndims, &nvars, &ngatts, &unlimdimid); |
8347 | 399 | if (status != NC_NOERR) |
8348 | 0 | { |
8349 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
8350 | | // with GDALDataset own mutex. |
8351 | 0 | delete poDS; |
8352 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8353 | 0 | return nullptr; |
8354 | 0 | } |
8355 | | |
8356 | | // Get file type from netcdf. |
8357 | 399 | int nTmpFormat = NCDF_FORMAT_NONE; |
8358 | 399 | status = nc_inq_format(cdfid, &nTmpFormat); |
8359 | 399 | if (status != NC_NOERR) |
8360 | 0 | { |
8361 | 0 | NCDF_ERR(status); |
8362 | 0 | } |
8363 | 399 | else |
8364 | 399 | { |
8365 | 399 | CPLDebug("GDAL_netCDF", |
8366 | 399 | "driver detected file type=%d, libnetcdf detected type=%d", |
8367 | 399 | poDS->eFormat, nTmpFormat); |
8368 | 399 | if (static_cast<NetCDFFormatEnum>(nTmpFormat) != poDS->eFormat) |
8369 | 0 | { |
8370 | | // Warn if file detection conflicts with that from libnetcdf |
8371 | | // except for NC4C, which we have no way of detecting initially. |
8372 | 0 | if (nTmpFormat != NCDF_FORMAT_NC4C && |
8373 | 0 | !STARTS_WITH(poDS->osFilename, "http://") && |
8374 | 0 | !STARTS_WITH(poDS->osFilename, "https://")) |
8375 | 0 | { |
8376 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8377 | 0 | "NetCDF driver detected file type=%d, but libnetcdf " |
8378 | 0 | "detected type=%d", |
8379 | 0 | poDS->eFormat, nTmpFormat); |
8380 | 0 | } |
8381 | 0 | CPLDebug("GDAL_netCDF", "setting file type to %d, was %d", |
8382 | 0 | nTmpFormat, poDS->eFormat); |
8383 | 0 | poDS->eFormat = static_cast<NetCDFFormatEnum>(nTmpFormat); |
8384 | 0 | } |
8385 | 399 | } |
8386 | | |
8387 | | // Does the request variable exist? |
8388 | 399 | if (bTreatAsSubdataset) |
8389 | 0 | { |
8390 | 0 | int dummy; |
8391 | 0 | if (NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &dummy, |
8392 | 0 | &dummy) != CE_None) |
8393 | 0 | { |
8394 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8395 | 0 | "%s is a netCDF file, but %s is not a variable.", |
8396 | 0 | poOpenInfo->pszFilename, osSubdatasetName.c_str()); |
8397 | |
|
8398 | 0 | GDAL_nc_close(cdfid); |
8399 | 0 | #ifdef ENABLE_UFFD |
8400 | 0 | NETCDF_UFFD_UNMAP(pCtx); |
8401 | 0 | #endif |
8402 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8403 | | // deadlock with GDALDataset own mutex. |
8404 | 0 | delete poDS; |
8405 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8406 | 0 | return nullptr; |
8407 | 0 | } |
8408 | 0 | } |
8409 | | |
8410 | | // Figure out whether or not the listed dataset has support for simple |
8411 | | // geometries (CF-1.8) |
8412 | 399 | poDS->nCFVersion = nccfdriver::getCFVersion(cdfid); |
8413 | 399 | bool bHasSimpleGeometries = false; // but not necessarily valid |
8414 | 399 | if (poDS->nCFVersion >= 1.8) |
8415 | 0 | { |
8416 | 0 | bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid); |
8417 | 0 | if (bHasSimpleGeometries) |
8418 | 0 | { |
8419 | 0 | poDS->bSGSupport = true; |
8420 | 0 | poDS->vcdf.enableFullVirtualMode(); |
8421 | 0 | } |
8422 | 0 | } |
8423 | | |
8424 | 399 | char szConventions[NC_MAX_NAME + 1]; |
8425 | 399 | szConventions[0] = '\0'; |
8426 | 399 | nc_type nAttype = NC_NAT; |
8427 | 399 | size_t nAttlen = 0; |
8428 | 399 | nc_inq_att(cdfid, NC_GLOBAL, "Conventions", &nAttype, &nAttlen); |
8429 | 399 | if (nAttlen >= sizeof(szConventions) || |
8430 | 399 | nc_get_att_text(cdfid, NC_GLOBAL, "Conventions", szConventions) != |
8431 | 399 | NC_NOERR) |
8432 | 364 | { |
8433 | 364 | CPLDebug("GDAL_netCDF", "No UNIDATA NC_GLOBAL:Conventions attribute"); |
8434 | | // Note that 'Conventions' is always capital 'C' in CF spec. |
8435 | 364 | } |
8436 | 35 | else |
8437 | 35 | { |
8438 | 35 | szConventions[nAttlen] = '\0'; |
8439 | 35 | } |
8440 | | |
8441 | | // Create band information objects. |
8442 | 399 | CPLDebug("GDAL_netCDF", "var_count = %d", nvars); |
8443 | | |
8444 | | // Create a corresponding GDALDataset. |
8445 | | // Create Netcdf Subdataset if filename as NETCDF tag. |
8446 | 399 | poDS->cdfid = cdfid; |
8447 | 399 | #ifdef ENABLE_UFFD |
8448 | 399 | poDS->pCtx = pCtx; |
8449 | 399 | #endif |
8450 | 399 | poDS->eAccess = poOpenInfo->eAccess; |
8451 | 399 | poDS->bDefineMode = false; |
8452 | | |
8453 | 399 | poDS->ReadAttributes(cdfid, NC_GLOBAL); |
8454 | | |
8455 | | // Identify coordinate and boundary variables that we should |
8456 | | // ignore as Raster Bands. |
8457 | 399 | char **papszIgnoreVars = nullptr; |
8458 | 399 | NCDFGetCoordAndBoundVarFullNames(cdfid, &papszIgnoreVars); |
8459 | | // Filter variables to keep only valid 2+D raster bands and vector fields. |
8460 | 399 | int nRasterVars = 0; |
8461 | 399 | int nIgnoredVars = 0; |
8462 | 399 | int nGroupID = -1; |
8463 | 399 | int nVarID = -1; |
8464 | | |
8465 | 399 | std::map<std::array<int, 3>, std::vector<std::pair<int, int>>> |
8466 | 399 | oMap2DDimsToGroupAndVar; |
8467 | 399 | if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 && |
8468 | 399 | STARTS_WITH(CSLFetchNameValueDef(poDS->papszMetadata, |
8469 | 399 | "NC_GLOBAL#mission_name", ""), |
8470 | 399 | "Sentinel 3") && |
8471 | 399 | EQUAL(CSLFetchNameValueDef(poDS->papszMetadata, |
8472 | 399 | "NC_GLOBAL#altimeter_sensor_name", ""), |
8473 | 399 | "SRAL") && |
8474 | 399 | EQUAL(CSLFetchNameValueDef(poDS->papszMetadata, |
8475 | 399 | "NC_GLOBAL#radiometer_sensor_name", ""), |
8476 | 399 | "MWR")) |
8477 | 0 | { |
8478 | 0 | if (poDS->eAccess == GA_Update) |
8479 | 0 | { |
8480 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8481 | | // deadlock with GDALDataset own mutex. |
8482 | 0 | delete poDS; |
8483 | 0 | return nullptr; |
8484 | 0 | } |
8485 | 0 | poDS->ProcessSentinel3_SRAL_MWR(); |
8486 | 0 | } |
8487 | 399 | else |
8488 | 399 | { |
8489 | 399 | poDS->FilterVars(cdfid, (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0, |
8490 | 399 | (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 && |
8491 | 399 | !bHasSimpleGeometries, |
8492 | 399 | papszIgnoreVars, &nRasterVars, &nGroupID, &nVarID, |
8493 | 399 | &nIgnoredVars, oMap2DDimsToGroupAndVar); |
8494 | 399 | } |
8495 | 399 | CSLDestroy(papszIgnoreVars); |
8496 | | |
8497 | | // Case where there is no raster variable |
8498 | 399 | if (nRasterVars == 0 && !bTreatAsSubdataset) |
8499 | 399 | { |
8500 | 399 | poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata); |
8501 | 399 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
8502 | | // with GDALDataset own mutex. |
8503 | 399 | poDS->TryLoadXML(); |
8504 | | // If the dataset has been opened in raster mode only, exit |
8505 | 399 | if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 && |
8506 | 399 | (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0) |
8507 | 0 | { |
8508 | 0 | delete poDS; |
8509 | 0 | poDS = nullptr; |
8510 | 0 | } |
8511 | | // Otherwise if the dataset is opened in vector mode, that there is |
8512 | | // no vector layer and we are in read-only, exit too. |
8513 | 399 | else if (poDS->GetLayerCount() == 0 && |
8514 | 399 | (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 && |
8515 | 399 | poOpenInfo->eAccess == GA_ReadOnly) |
8516 | 376 | { |
8517 | 376 | delete poDS; |
8518 | 376 | poDS = nullptr; |
8519 | 376 | } |
8520 | 399 | CPLAcquireMutex(hNCMutex, 1000.0); |
8521 | 399 | return poDS; |
8522 | 399 | } |
8523 | | |
8524 | | // We have more than one variable with 2 dimensions in the |
8525 | | // file, then treat this as a subdataset container dataset. |
8526 | 0 | bool bSeveralVariablesAsBands = false; |
8527 | 0 | const bool bListAllArrays = CPLTestBool( |
8528 | 0 | CSLFetchNameValueDef(poDS->papszOpenOptions, "LIST_ALL_ARRAYS", "NO")); |
8529 | 0 | if (bListAllArrays || ((nRasterVars > 1) && !bTreatAsSubdataset)) |
8530 | 0 | { |
8531 | 0 | if (CPLFetchBool(poOpenInfo->papszOpenOptions, "VARIABLES_AS_BANDS", |
8532 | 0 | false) && |
8533 | 0 | oMap2DDimsToGroupAndVar.size() == 1) |
8534 | 0 | { |
8535 | 0 | std::tie(nGroupID, nVarID) = |
8536 | 0 | oMap2DDimsToGroupAndVar.begin()->second.front(); |
8537 | 0 | bSeveralVariablesAsBands = true; |
8538 | 0 | } |
8539 | 0 | else |
8540 | 0 | { |
8541 | 0 | poDS->CreateSubDatasetList(cdfid); |
8542 | 0 | poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata); |
8543 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8544 | | // deadlock with GDALDataset own mutex. |
8545 | 0 | poDS->TryLoadXML(); |
8546 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8547 | 0 | return poDS; |
8548 | 0 | } |
8549 | 0 | } |
8550 | | |
8551 | | // If we are not treating things as a subdataset, then capture |
8552 | | // the name of the single available variable as the subdataset. |
8553 | 0 | if (!bTreatAsSubdataset) |
8554 | 0 | { |
8555 | 0 | char *pszVarName = nullptr; |
8556 | 0 | NCDF_ERR(NCDFGetVarFullName(nGroupID, nVarID, &pszVarName)); |
8557 | 0 | osSubdatasetName = (pszVarName != nullptr ? pszVarName : ""); |
8558 | 0 | CPLFree(pszVarName); |
8559 | 0 | } |
8560 | | |
8561 | | // We have ignored at least one variable, so we should report them |
8562 | | // as subdatasets for reference. |
8563 | 0 | if (nIgnoredVars > 0 && !bTreatAsSubdataset) |
8564 | 0 | { |
8565 | 0 | CPLDebug("GDAL_netCDF", |
8566 | 0 | "As %d variables were ignored, creating subdataset list " |
8567 | 0 | "for reference. Variable #%d [%s] is the main variable", |
8568 | 0 | nIgnoredVars, nVarID, osSubdatasetName.c_str()); |
8569 | 0 | poDS->CreateSubDatasetList(cdfid); |
8570 | 0 | } |
8571 | | |
8572 | | // Open the NETCDF subdataset NETCDF:"filename":subdataset. |
8573 | 0 | int var = -1; |
8574 | 0 | NCDFOpenSubDataset(cdfid, osSubdatasetName.c_str(), &nGroupID, &var); |
8575 | | // Now we can forget the root cdfid and only use the selected group. |
8576 | 0 | cdfid = nGroupID; |
8577 | 0 | int nd = 0; |
8578 | 0 | nc_inq_varndims(cdfid, var, &nd); |
8579 | |
|
8580 | 0 | poDS->m_anDimIds.resize(nd); |
8581 | | |
8582 | | // X, Y, Z position in array |
8583 | 0 | std::vector<int> anBandDimPos(nd); |
8584 | |
|
8585 | 0 | nc_inq_vardimid(cdfid, var, poDS->m_anDimIds.data()); |
8586 | | |
8587 | | // Check if somebody tried to pass a variable with less than 1D. |
8588 | 0 | if (nd < 1) |
8589 | 0 | { |
8590 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8591 | 0 | "Variable has %d dimension(s) - not supported.", nd); |
8592 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
8593 | | // with GDALDataset own mutex. |
8594 | 0 | delete poDS; |
8595 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8596 | 0 | return nullptr; |
8597 | 0 | } |
8598 | | |
8599 | | // CF-1 Convention |
8600 | | // |
8601 | | // Dimensions to appear in the relative order T, then Z, then Y, |
8602 | | // then X to the file. All other dimensions should, whenever |
8603 | | // possible, be placed to the left of the spatiotemporal |
8604 | | // dimensions. |
8605 | | |
8606 | | // Verify that dimensions are in the {T,Z,Y,X} or {T,Z,Y,X} order |
8607 | | // Ideally we should detect for other ordering and act accordingly |
8608 | | // Only done if file has Conventions=CF-* and only prints warning |
8609 | | // To disable set GDAL_NETCDF_VERIFY_DIMS=NO and to use only |
8610 | | // attributes (not varnames) set GDAL_NETCDF_VERIFY_DIMS=STRICT |
8611 | 0 | const bool bCheckDims = |
8612 | 0 | CPLTestBool(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES")) && |
8613 | 0 | STARTS_WITH_CI(szConventions, "CF"); |
8614 | |
|
8615 | 0 | if (nd >= 2 && bCheckDims) |
8616 | 0 | { |
8617 | 0 | char szDimName1[NC_MAX_NAME + 1] = {}; |
8618 | 0 | char szDimName2[NC_MAX_NAME + 1] = {}; |
8619 | 0 | status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 1], szDimName1); |
8620 | 0 | NCDF_ERR(status); |
8621 | 0 | status = nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 2], szDimName2); |
8622 | 0 | NCDF_ERR(status); |
8623 | 0 | if (NCDFIsVarLongitude(cdfid, -1, szDimName1) == false && |
8624 | 0 | NCDFIsVarProjectionX(cdfid, -1, szDimName1) == false) |
8625 | 0 | { |
8626 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8627 | 0 | "dimension #%d (%s) is not a Longitude/X dimension.", |
8628 | 0 | nd - 1, szDimName1); |
8629 | 0 | } |
8630 | 0 | if (NCDFIsVarLatitude(cdfid, -1, szDimName2) == false && |
8631 | 0 | NCDFIsVarProjectionY(cdfid, -1, szDimName2) == false) |
8632 | 0 | { |
8633 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8634 | 0 | "dimension #%d (%s) is not a Latitude/Y dimension.", |
8635 | 0 | nd - 2, szDimName2); |
8636 | 0 | } |
8637 | 0 | if ((NCDFIsVarLongitude(cdfid, -1, szDimName2) || |
8638 | 0 | NCDFIsVarProjectionX(cdfid, -1, szDimName2)) && |
8639 | 0 | (NCDFIsVarLatitude(cdfid, -1, szDimName1) || |
8640 | 0 | NCDFIsVarProjectionY(cdfid, -1, szDimName1))) |
8641 | 0 | { |
8642 | 0 | poDS->bSwitchedXY = true; |
8643 | 0 | } |
8644 | 0 | if (nd >= 3) |
8645 | 0 | { |
8646 | 0 | char szDimName3[NC_MAX_NAME + 1] = {}; |
8647 | 0 | status = |
8648 | 0 | nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 3], szDimName3); |
8649 | 0 | NCDF_ERR(status); |
8650 | 0 | if (nd >= 4) |
8651 | 0 | { |
8652 | 0 | char szDimName4[NC_MAX_NAME + 1] = {}; |
8653 | 0 | status = |
8654 | 0 | nc_inq_dimname(cdfid, poDS->m_anDimIds[nd - 4], szDimName4); |
8655 | 0 | NCDF_ERR(status); |
8656 | 0 | if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false) |
8657 | 0 | { |
8658 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8659 | 0 | "dimension #%d (%s) is not a Vertical dimension.", |
8660 | 0 | nd - 3, szDimName3); |
8661 | 0 | } |
8662 | 0 | if (NCDFIsVarTimeCoord(cdfid, -1, szDimName4) == false) |
8663 | 0 | { |
8664 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8665 | 0 | "dimension #%d (%s) is not a Time dimension.", |
8666 | 0 | nd - 4, szDimName4); |
8667 | 0 | } |
8668 | 0 | } |
8669 | 0 | else |
8670 | 0 | { |
8671 | 0 | if (NCDFIsVarVerticalCoord(cdfid, -1, szDimName3) == false && |
8672 | 0 | NCDFIsVarTimeCoord(cdfid, -1, szDimName3) == false) |
8673 | 0 | { |
8674 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8675 | 0 | "dimension #%d (%s) is not a " |
8676 | 0 | "Time or Vertical dimension.", |
8677 | 0 | nd - 3, szDimName3); |
8678 | 0 | } |
8679 | 0 | } |
8680 | 0 | } |
8681 | 0 | } |
8682 | | |
8683 | | // For example for EMIT data (https://earth.jpl.nasa.gov/emit/data/data-portal/coverage-and-forecasts/), |
8684 | | // dimension order is downtrack, crosstrack, bands |
8685 | 0 | bool bYXBandOrder = false; |
8686 | 0 | if (nd == 3) |
8687 | 0 | { |
8688 | 0 | char szDimName[NC_MAX_NAME + 1] = {}; |
8689 | 0 | status = nc_inq_dimname(cdfid, poDS->m_anDimIds[2], szDimName); |
8690 | 0 | NCDF_ERR(status); |
8691 | 0 | bYXBandOrder = |
8692 | 0 | strcmp(szDimName, "bands") == 0 || strcmp(szDimName, "band") == 0; |
8693 | 0 | } |
8694 | | |
8695 | | // Get X dimensions information. |
8696 | 0 | size_t xdim; |
8697 | 0 | poDS->nXDimID = poDS->m_anDimIds[bYXBandOrder ? 1 : nd - 1]; |
8698 | 0 | nc_inq_dimlen(cdfid, poDS->nXDimID, &xdim); |
8699 | | |
8700 | | // Get Y dimension information. |
8701 | 0 | size_t ydim; |
8702 | 0 | if (nd >= 2) |
8703 | 0 | { |
8704 | 0 | poDS->nYDimID = poDS->m_anDimIds[bYXBandOrder ? 0 : nd - 2]; |
8705 | 0 | nc_inq_dimlen(cdfid, poDS->nYDimID, &ydim); |
8706 | 0 | } |
8707 | 0 | else |
8708 | 0 | { |
8709 | 0 | poDS->nYDimID = -1; |
8710 | 0 | ydim = 1; |
8711 | 0 | } |
8712 | |
|
8713 | 0 | if (xdim > INT_MAX || ydim > INT_MAX) |
8714 | 0 | { |
8715 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
8716 | 0 | "Invalid raster dimensions: " CPL_FRMT_GUIB "x" CPL_FRMT_GUIB, |
8717 | 0 | static_cast<GUIntBig>(xdim), static_cast<GUIntBig>(ydim)); |
8718 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
8719 | | // with GDALDataset own mutex. |
8720 | 0 | delete poDS; |
8721 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8722 | 0 | return nullptr; |
8723 | 0 | } |
8724 | | |
8725 | 0 | poDS->nRasterXSize = static_cast<int>(xdim); |
8726 | 0 | poDS->nRasterYSize = static_cast<int>(ydim); |
8727 | |
|
8728 | 0 | unsigned int k = 0; |
8729 | 0 | for (int j = 0; j < nd; j++) |
8730 | 0 | { |
8731 | 0 | if (poDS->m_anDimIds[j] == poDS->nXDimID) |
8732 | 0 | { |
8733 | 0 | anBandDimPos[0] = j; // Save Position of XDim |
8734 | 0 | k++; |
8735 | 0 | } |
8736 | 0 | if (poDS->m_anDimIds[j] == poDS->nYDimID) |
8737 | 0 | { |
8738 | 0 | anBandDimPos[1] = j; // Save Position of YDim |
8739 | 0 | k++; |
8740 | 0 | } |
8741 | 0 | } |
8742 | | // X and Y Dimension Ids were not found! |
8743 | 0 | if ((nd >= 2 && k != 2) || (nd == 1 && k != 1)) |
8744 | 0 | { |
8745 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
8746 | | // with GDALDataset own mutex. |
8747 | 0 | delete poDS; |
8748 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8749 | 0 | return nullptr; |
8750 | 0 | } |
8751 | | |
8752 | | // Read Metadata for this variable. |
8753 | | |
8754 | | // Should disable as is also done at band level, except driver needs the |
8755 | | // variables as metadata (e.g. projection). |
8756 | 0 | poDS->ReadAttributes(cdfid, var); |
8757 | | |
8758 | | // Read Metadata for each dimension. |
8759 | 0 | int *panDimIds = nullptr; |
8760 | 0 | NCDFGetVisibleDims(cdfid, &ndims, &panDimIds); |
8761 | | // With NetCDF-4 groups panDimIds is not always [0..dim_count-1] like |
8762 | | // in NetCDF-3 because we see only the dimensions of the selected group |
8763 | | // and its parents. |
8764 | | // poDS->papszDimName is indexed by dim IDs, so it must contains all IDs |
8765 | | // [0..max(panDimIds)], but they are not all useful so we fill names |
8766 | | // of useless dims with empty string. |
8767 | 0 | if (panDimIds) |
8768 | 0 | { |
8769 | 0 | const int nMaxDimId = *std::max_element(panDimIds, panDimIds + ndims); |
8770 | 0 | std::set<int> oSetExistingDimIds; |
8771 | 0 | for (int i = 0; i < ndims; i++) |
8772 | 0 | { |
8773 | 0 | oSetExistingDimIds.insert(panDimIds[i]); |
8774 | 0 | } |
8775 | 0 | std::set<int> oSetDimIdsUsedByVar; |
8776 | 0 | for (int i = 0; i < nd; i++) |
8777 | 0 | { |
8778 | 0 | oSetDimIdsUsedByVar.insert(poDS->m_anDimIds[i]); |
8779 | 0 | } |
8780 | 0 | for (int j = 0; j <= nMaxDimId; j++) |
8781 | 0 | { |
8782 | | // Is j dim used? |
8783 | 0 | if (oSetExistingDimIds.find(j) != oSetExistingDimIds.end()) |
8784 | 0 | { |
8785 | | // Useful dim. |
8786 | 0 | char szTemp[NC_MAX_NAME + 1] = {}; |
8787 | 0 | status = nc_inq_dimname(cdfid, j, szTemp); |
8788 | 0 | if (status != NC_NOERR) |
8789 | 0 | { |
8790 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8791 | | // deadlock with GDALDataset own |
8792 | | // mutex. |
8793 | 0 | delete poDS; |
8794 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8795 | 0 | return nullptr; |
8796 | 0 | } |
8797 | 0 | poDS->papszDimName.AddString(szTemp); |
8798 | |
|
8799 | 0 | if (oSetDimIdsUsedByVar.find(j) != oSetDimIdsUsedByVar.end()) |
8800 | 0 | { |
8801 | 0 | int nDimGroupId = -1; |
8802 | 0 | int nDimVarId = -1; |
8803 | 0 | if (NCDFResolveVar(cdfid, poDS->papszDimName[j], |
8804 | 0 | &nDimGroupId, &nDimVarId) == CE_None) |
8805 | 0 | { |
8806 | 0 | poDS->ReadAttributes(nDimGroupId, nDimVarId); |
8807 | 0 | } |
8808 | 0 | } |
8809 | 0 | } |
8810 | 0 | else |
8811 | 0 | { |
8812 | | // Useless dim. |
8813 | 0 | poDS->papszDimName.AddString(""); |
8814 | 0 | } |
8815 | 0 | } |
8816 | 0 | CPLFree(panDimIds); |
8817 | 0 | } |
8818 | | |
8819 | | // Set projection info. |
8820 | 0 | std::vector<std::string> aosRemovedMDItems; |
8821 | 0 | if (nd > 1) |
8822 | 0 | { |
8823 | 0 | poDS->SetProjectionFromVar(cdfid, var, |
8824 | 0 | /*bReadSRSOnly=*/false, |
8825 | 0 | /* pszGivenGM = */ nullptr, |
8826 | 0 | /* returnProjStr = */ nullptr, |
8827 | 0 | /* sg = */ nullptr, &aosRemovedMDItems); |
8828 | 0 | } |
8829 | | |
8830 | | // Override bottom-up with GDAL_NETCDF_BOTTOMUP config option. |
8831 | 0 | const char *pszValue = CPLGetConfigOption("GDAL_NETCDF_BOTTOMUP", nullptr); |
8832 | 0 | if (pszValue) |
8833 | 0 | { |
8834 | 0 | poDS->bBottomUp = CPLTestBool(pszValue); |
8835 | 0 | CPLDebug("GDAL_netCDF", |
8836 | 0 | "set bBottomUp=%d because GDAL_NETCDF_BOTTOMUP=%s", |
8837 | 0 | static_cast<int>(poDS->bBottomUp), pszValue); |
8838 | 0 | } |
8839 | | |
8840 | | // Save non-spatial dimension info. |
8841 | |
|
8842 | 0 | int *panBandZLev = nullptr; |
8843 | 0 | int nDim = (nd >= 2) ? 2 : 1; |
8844 | 0 | size_t lev_count; |
8845 | 0 | size_t nTotLevCount = 1; |
8846 | 0 | nc_type nType = NC_NAT; |
8847 | |
|
8848 | 0 | CPLString osExtraDimNames; |
8849 | |
|
8850 | 0 | if (nd > 2) |
8851 | 0 | { |
8852 | 0 | nDim = 2; |
8853 | 0 | panBandZLev = static_cast<int *>(CPLCalloc(nd - 2, sizeof(int))); |
8854 | |
|
8855 | 0 | osExtraDimNames = "{"; |
8856 | |
|
8857 | 0 | char szDimName[NC_MAX_NAME + 1] = {}; |
8858 | |
|
8859 | 0 | bool bREPORT_EXTRA_DIM_VALUESWarningEmitted = false; |
8860 | 0 | for (int j = 0; j < nd; j++) |
8861 | 0 | { |
8862 | 0 | if ((poDS->m_anDimIds[j] != poDS->nXDimID) && |
8863 | 0 | (poDS->m_anDimIds[j] != poDS->nYDimID)) |
8864 | 0 | { |
8865 | 0 | nc_inq_dimlen(cdfid, poDS->m_anDimIds[j], &lev_count); |
8866 | 0 | nTotLevCount *= lev_count; |
8867 | 0 | panBandZLev[nDim - 2] = static_cast<int>(lev_count); |
8868 | 0 | anBandDimPos[nDim] = j; // Save Position of ZDim |
8869 | | // Save non-spatial dimension names. |
8870 | 0 | if (nc_inq_dimname(cdfid, poDS->m_anDimIds[j], szDimName) == |
8871 | 0 | NC_NOERR) |
8872 | 0 | { |
8873 | 0 | osExtraDimNames += szDimName; |
8874 | 0 | if (j < nd - 3) |
8875 | 0 | { |
8876 | 0 | osExtraDimNames += ","; |
8877 | 0 | } |
8878 | |
|
8879 | 0 | int nIdxGroupID = -1; |
8880 | 0 | int nIdxVarID = Get1DVariableIndexedByDimension( |
8881 | 0 | cdfid, poDS->m_anDimIds[j], szDimName, true, |
8882 | 0 | &nIdxGroupID); |
8883 | 0 | poDS->m_anExtraDimGroupIds.push_back(nIdxGroupID); |
8884 | 0 | poDS->m_anExtraDimVarIds.push_back(nIdxVarID); |
8885 | |
|
8886 | 0 | if (nIdxVarID >= 0) |
8887 | 0 | { |
8888 | 0 | nc_inq_vartype(nIdxGroupID, nIdxVarID, &nType); |
8889 | 0 | char szExtraDimDef[NC_MAX_NAME + 1]; |
8890 | 0 | snprintf(szExtraDimDef, sizeof(szExtraDimDef), |
8891 | 0 | "{%ld,%d}", (long)lev_count, nType); |
8892 | 0 | char szTemp[NC_MAX_NAME + 32 + 1]; |
8893 | 0 | snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF", |
8894 | 0 | szDimName); |
8895 | 0 | poDS->papszMetadata = CSLSetNameValue( |
8896 | 0 | poDS->papszMetadata, szTemp, szExtraDimDef); |
8897 | | |
8898 | | // Retrieving data for unlimited dimensions might be |
8899 | | // costly on network storage, so don't do it. |
8900 | | // Each band will capture the value along the extra |
8901 | | // dimension in its NETCDF_DIM_xxxx band metadata item |
8902 | | // Addresses use case of |
8903 | | // https://lists.osgeo.org/pipermail/gdal-dev/2023-May/057209.html |
8904 | 0 | const bool bIsLocal = |
8905 | 0 | VSIIsLocal(osFilenameForNCOpen.c_str()); |
8906 | 0 | bool bListDimValues = |
8907 | 0 | bIsLocal || lev_count == 1 || |
8908 | 0 | !NCDFIsUnlimitedDim(poDS->eFormat == |
8909 | 0 | NCDF_FORMAT_NC4, |
8910 | 0 | cdfid, poDS->m_anDimIds[j]); |
8911 | 0 | const char *pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES = |
8912 | 0 | CPLGetConfigOption( |
8913 | 0 | "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES", nullptr); |
8914 | 0 | if (pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES) |
8915 | 0 | { |
8916 | 0 | bListDimValues = CPLTestBool( |
8917 | 0 | pszGDAL_NETCDF_REPORT_EXTRA_DIM_VALUES); |
8918 | 0 | } |
8919 | 0 | else if (!bListDimValues && !bIsLocal && |
8920 | 0 | !bREPORT_EXTRA_DIM_VALUESWarningEmitted) |
8921 | 0 | { |
8922 | 0 | bREPORT_EXTRA_DIM_VALUESWarningEmitted = true; |
8923 | 0 | CPLDebug( |
8924 | 0 | "GDAL_netCDF", |
8925 | 0 | "Listing extra dimension values is skipped " |
8926 | 0 | "because this dataset is hosted on a network " |
8927 | 0 | "file system, and such an operation could be " |
8928 | 0 | "slow. If you still want to proceed, set the " |
8929 | 0 | "GDAL_NETCDF_REPORT_EXTRA_DIM_VALUES " |
8930 | 0 | "configuration option to YES"); |
8931 | 0 | } |
8932 | 0 | if (bListDimValues) |
8933 | 0 | { |
8934 | 0 | char *pszTemp = nullptr; |
8935 | 0 | if (NCDFGet1DVar(nIdxGroupID, nIdxVarID, |
8936 | 0 | &pszTemp) == CE_None) |
8937 | 0 | { |
8938 | 0 | snprintf(szTemp, sizeof(szTemp), |
8939 | 0 | "NETCDF_DIM_%s_VALUES", szDimName); |
8940 | 0 | poDS->papszMetadata = CSLSetNameValue( |
8941 | 0 | poDS->papszMetadata, szTemp, pszTemp); |
8942 | 0 | CPLFree(pszTemp); |
8943 | 0 | } |
8944 | 0 | } |
8945 | 0 | } |
8946 | 0 | } |
8947 | 0 | else |
8948 | 0 | { |
8949 | 0 | poDS->m_anExtraDimGroupIds.push_back(-1); |
8950 | 0 | poDS->m_anExtraDimVarIds.push_back(-1); |
8951 | 0 | } |
8952 | |
|
8953 | 0 | nDim++; |
8954 | 0 | } |
8955 | 0 | } |
8956 | 0 | osExtraDimNames += "}"; |
8957 | 0 | poDS->papszMetadata = CSLSetNameValue( |
8958 | 0 | poDS->papszMetadata, "NETCDF_DIM_EXTRA", osExtraDimNames); |
8959 | 0 | } |
8960 | | |
8961 | | // Store Metadata. |
8962 | 0 | for (const auto &osStr : aosRemovedMDItems) |
8963 | 0 | poDS->papszMetadata = |
8964 | 0 | CSLSetNameValue(poDS->papszMetadata, osStr.c_str(), nullptr); |
8965 | |
|
8966 | 0 | poDS->GDALPamDataset::SetMetadata(poDS->papszMetadata); |
8967 | | |
8968 | | // Create bands. |
8969 | | |
8970 | | // Arbitrary threshold. |
8971 | 0 | int nMaxBandCount = |
8972 | 0 | atoi(CPLGetConfigOption("GDAL_MAX_BAND_COUNT", "32768")); |
8973 | 0 | if (nMaxBandCount <= 0) |
8974 | 0 | nMaxBandCount = 32768; |
8975 | 0 | if (nTotLevCount > static_cast<unsigned int>(nMaxBandCount)) |
8976 | 0 | { |
8977 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
8978 | 0 | "Limiting number of bands to %d instead of %u", nMaxBandCount, |
8979 | 0 | static_cast<unsigned int>(nTotLevCount)); |
8980 | 0 | nTotLevCount = static_cast<unsigned int>(nMaxBandCount); |
8981 | 0 | } |
8982 | 0 | if (poDS->nRasterXSize == 0 || poDS->nRasterYSize == 0) |
8983 | 0 | { |
8984 | 0 | poDS->nRasterXSize = 0; |
8985 | 0 | poDS->nRasterYSize = 0; |
8986 | 0 | nTotLevCount = 0; |
8987 | 0 | if (poDS->GetLayerCount() == 0) |
8988 | 0 | { |
8989 | 0 | CPLFree(panBandZLev); |
8990 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
8991 | | // deadlock with GDALDataset own mutex. |
8992 | 0 | delete poDS; |
8993 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
8994 | 0 | return nullptr; |
8995 | 0 | } |
8996 | 0 | } |
8997 | 0 | if (bSeveralVariablesAsBands) |
8998 | 0 | { |
8999 | 0 | const auto &listVariables = oMap2DDimsToGroupAndVar.begin()->second; |
9000 | 0 | for (int iBand = 0; iBand < static_cast<int>(listVariables.size()); |
9001 | 0 | ++iBand) |
9002 | 0 | { |
9003 | 0 | int bandVarGroupId = listVariables[iBand].first; |
9004 | 0 | int bandVarId = listVariables[iBand].second; |
9005 | 0 | netCDFRasterBand *poBand = new netCDFRasterBand( |
9006 | 0 | netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, bandVarGroupId, |
9007 | 0 | bandVarId, nDim, 0, nullptr, anBandDimPos.data(), iBand + 1); |
9008 | 0 | poDS->SetBand(iBand + 1, poBand); |
9009 | 0 | } |
9010 | 0 | } |
9011 | 0 | else |
9012 | 0 | { |
9013 | 0 | for (unsigned int lev = 0; lev < nTotLevCount; lev++) |
9014 | 0 | { |
9015 | 0 | netCDFRasterBand *poBand = new netCDFRasterBand( |
9016 | 0 | netCDFRasterBand::CONSTRUCTOR_OPEN(), poDS, cdfid, var, nDim, |
9017 | 0 | lev, panBandZLev, anBandDimPos.data(), lev + 1); |
9018 | 0 | poDS->SetBand(lev + 1, poBand); |
9019 | 0 | } |
9020 | 0 | } |
9021 | |
|
9022 | 0 | if (panBandZLev) |
9023 | 0 | CPLFree(panBandZLev); |
9024 | | // Handle angular geographic coordinates here |
9025 | | |
9026 | | // Initialize any PAM information. |
9027 | 0 | if (bTreatAsSubdataset) |
9028 | 0 | { |
9029 | 0 | poDS->SetPhysicalFilename(poDS->osFilename); |
9030 | 0 | poDS->SetSubdatasetName(osSubdatasetName); |
9031 | 0 | } |
9032 | |
|
9033 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with |
9034 | | // GDALDataset own mutex. |
9035 | 0 | poDS->TryLoadXML(); |
9036 | |
|
9037 | 0 | if (bTreatAsSubdataset) |
9038 | 0 | poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::"); |
9039 | 0 | else |
9040 | 0 | poDS->oOvManager.Initialize(poDS, poDS->osFilename); |
9041 | |
|
9042 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
9043 | |
|
9044 | 0 | return poDS; |
9045 | 0 | } |
9046 | | |
9047 | | /************************************************************************/ |
9048 | | /* CopyMetadata() */ |
9049 | | /* */ |
9050 | | /* Create a copy of metadata for NC_GLOBAL or a variable */ |
9051 | | /************************************************************************/ |
9052 | | |
9053 | | static void CopyMetadata(GDALDataset *poSrcDS, GDALRasterBand *poSrcBand, |
9054 | | GDALRasterBand *poDstBand, int nCdfId, int CDFVarID, |
9055 | | const char *pszPrefix) |
9056 | 0 | { |
9057 | | // Remove the following band meta but set them later from band data. |
9058 | 0 | const char *const papszIgnoreBand[] = { |
9059 | 0 | CF_ADD_OFFSET, CF_SCALE_FACTOR, "valid_range", "_Unsigned", |
9060 | 0 | NCDF_FillValue, "coordinates", nullptr}; |
9061 | 0 | const char *const papszIgnoreGlobal[] = {"NETCDF_DIM_EXTRA", nullptr}; |
9062 | |
|
9063 | 0 | CSLConstList papszMetadata = nullptr; |
9064 | 0 | if (poSrcDS) |
9065 | 0 | { |
9066 | 0 | papszMetadata = poSrcDS->GetMetadata(); |
9067 | 0 | } |
9068 | 0 | else if (poSrcBand) |
9069 | 0 | { |
9070 | 0 | papszMetadata = poSrcBand->GetMetadata(); |
9071 | 0 | } |
9072 | |
|
9073 | 0 | for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(papszMetadata)) |
9074 | 0 | { |
9075 | | #ifdef NCDF_DEBUG |
9076 | | CPLDebug("GDAL_netCDF", "copy metadata [%s]=[%s]", pszKey, pszValue); |
9077 | | #endif |
9078 | |
|
9079 | 0 | CPLString osMetaName(pszKey); |
9080 | | |
9081 | | // Check for items that match pszPrefix if applicable. |
9082 | 0 | if (pszPrefix && !EQUAL(pszPrefix, "")) |
9083 | 0 | { |
9084 | | // Remove prefix. |
9085 | 0 | if (STARTS_WITH(osMetaName.c_str(), pszPrefix)) |
9086 | 0 | { |
9087 | 0 | osMetaName = osMetaName.substr(strlen(pszPrefix)); |
9088 | 0 | } |
9089 | | // Only copy items that match prefix. |
9090 | 0 | else |
9091 | 0 | { |
9092 | 0 | continue; |
9093 | 0 | } |
9094 | 0 | } |
9095 | | |
9096 | | // Fix various issues with metadata translation. |
9097 | 0 | if (CDFVarID == NC_GLOBAL) |
9098 | 0 | { |
9099 | | // Do not copy items in papszIgnoreGlobal and NETCDF_DIM_*. |
9100 | 0 | if ((CSLFindString(papszIgnoreGlobal, osMetaName) != -1) || |
9101 | 0 | (STARTS_WITH(osMetaName, "NETCDF_DIM_"))) |
9102 | 0 | continue; |
9103 | | // Remove NC_GLOBAL prefix for netcdf global Metadata. |
9104 | 0 | else if (STARTS_WITH(osMetaName, "NC_GLOBAL#")) |
9105 | 0 | { |
9106 | 0 | osMetaName = osMetaName.substr(strlen("NC_GLOBAL#")); |
9107 | 0 | } |
9108 | | // GDAL Metadata renamed as GDAL-[meta]. |
9109 | 0 | else if (strstr(osMetaName, "#") == nullptr) |
9110 | 0 | { |
9111 | 0 | osMetaName = "GDAL_" + osMetaName; |
9112 | 0 | } |
9113 | | // Keep time, lev and depth information for safe-keeping. |
9114 | | // Time and vertical coordinate handling need improvements. |
9115 | | /* |
9116 | | else if( STARTS_WITH(szMetaName, "time#") ) |
9117 | | { |
9118 | | szMetaName[4] = '-'; |
9119 | | } |
9120 | | else if( STARTS_WITH(szMetaName, "lev#") ) |
9121 | | { |
9122 | | szMetaName[3] = '-'; |
9123 | | } |
9124 | | else if( STARTS_WITH(szMetaName, "depth#") ) |
9125 | | { |
9126 | | szMetaName[5] = '-'; |
9127 | | } |
9128 | | */ |
9129 | | // Only copy data without # (previously all data was copied). |
9130 | 0 | if (strstr(osMetaName, "#") != nullptr) |
9131 | 0 | continue; |
9132 | | // netCDF attributes do not like the '#' character. |
9133 | | // for( unsigned int h=0; h < strlen(szMetaName) -1 ; h++ ) { |
9134 | | // if( szMetaName[h] == '#') szMetaName[h] = '-'; |
9135 | | // } |
9136 | 0 | } |
9137 | 0 | else |
9138 | 0 | { |
9139 | | // Do not copy varname, stats, NETCDF_DIM_*, nodata |
9140 | | // and items in papszIgnoreBand. |
9141 | 0 | if (STARTS_WITH(osMetaName, "NETCDF_VARNAME") || |
9142 | 0 | STARTS_WITH(osMetaName, "STATISTICS_") || |
9143 | 0 | STARTS_WITH(osMetaName, "NETCDF_DIM_") || |
9144 | 0 | STARTS_WITH(osMetaName, "missing_value") || |
9145 | 0 | STARTS_WITH(osMetaName, "_FillValue") || |
9146 | 0 | CSLFindString(papszIgnoreBand, osMetaName) != -1) |
9147 | 0 | continue; |
9148 | 0 | } |
9149 | | |
9150 | | #ifdef NCDF_DEBUG |
9151 | | CPLDebug("GDAL_netCDF", "copy name=[%s] value=[%s]", osMetaName.c_str(), |
9152 | | pszValue); |
9153 | | #endif |
9154 | 0 | if (NCDFPutAttr(nCdfId, CDFVarID, osMetaName, pszValue) != CE_None) |
9155 | 0 | { |
9156 | 0 | CPLDebug("GDAL_netCDF", "NCDFPutAttr(%d, %d, %s, %s) failed", |
9157 | 0 | nCdfId, CDFVarID, osMetaName.c_str(), pszValue); |
9158 | 0 | } |
9159 | 0 | } |
9160 | | |
9161 | | // Set add_offset and scale_factor here if present. |
9162 | 0 | if (poSrcBand && poDstBand) |
9163 | 0 | { |
9164 | |
|
9165 | 0 | int bGotAddOffset = FALSE; |
9166 | 0 | const double dfAddOffset = poSrcBand->GetOffset(&bGotAddOffset); |
9167 | 0 | int bGotScale = FALSE; |
9168 | 0 | const double dfScale = poSrcBand->GetScale(&bGotScale); |
9169 | |
|
9170 | 0 | if (bGotAddOffset && dfAddOffset != 0.0) |
9171 | 0 | poDstBand->SetOffset(dfAddOffset); |
9172 | 0 | if (bGotScale && dfScale != 1.0) |
9173 | 0 | poDstBand->SetScale(dfScale); |
9174 | 0 | } |
9175 | 0 | } |
9176 | | |
9177 | | /************************************************************************/ |
9178 | | /* CreateLL() */ |
9179 | | /* */ |
9180 | | /* Shared functionality between netCDFDataset::Create() and */ |
9181 | | /* netCDF::CreateCopy() for creating netcdf file based on a set of */ |
9182 | | /* options and a configuration. */ |
9183 | | /************************************************************************/ |
9184 | | |
9185 | | netCDFDataset *netCDFDataset::CreateLL(const char *pszFilename, int nXSize, |
9186 | | int nYSize, int nBandsIn, |
9187 | | char **papszOptions) |
9188 | 0 | { |
9189 | 0 | if (!((nXSize == 0 && nYSize == 0 && nBandsIn == 0) || |
9190 | 0 | (nXSize > 0 && nYSize > 0 && nBandsIn > 0))) |
9191 | 0 | { |
9192 | 0 | return nullptr; |
9193 | 0 | } |
9194 | | |
9195 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock with |
9196 | | // GDALDataset own mutex. |
9197 | 0 | netCDFDataset *poDS = new netCDFDataset(); |
9198 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
9199 | |
|
9200 | 0 | poDS->nRasterXSize = nXSize; |
9201 | 0 | poDS->nRasterYSize = nYSize; |
9202 | 0 | poDS->eAccess = GA_Update; |
9203 | 0 | poDS->osFilename = pszFilename; |
9204 | | |
9205 | | // From gtiff driver, is this ok? |
9206 | | /* |
9207 | | poDS->nBlockXSize = nXSize; |
9208 | | poDS->nBlockYSize = 1; |
9209 | | poDS->nBlocksPerBand = |
9210 | | DIV_ROUND_UP((nYSize, poDS->nBlockYSize)) |
9211 | | * DIV_ROUND_UP((nXSize, poDS->nBlockXSize)); |
9212 | | */ |
9213 | | |
9214 | | // process options. |
9215 | 0 | poDS->papszCreationOptions = CSLDuplicate(papszOptions); |
9216 | 0 | poDS->ProcessCreationOptions(); |
9217 | |
|
9218 | 0 | if (poDS->eMultipleLayerBehavior == SEPARATE_FILES) |
9219 | 0 | { |
9220 | 0 | VSIStatBuf sStat; |
9221 | 0 | if (VSIStat(pszFilename, &sStat) == 0) |
9222 | 0 | { |
9223 | 0 | if (!VSI_ISDIR(sStat.st_mode)) |
9224 | 0 | { |
9225 | 0 | CPLError(CE_Failure, CPLE_FileIO, |
9226 | 0 | "%s is an existing file, but not a directory", |
9227 | 0 | pszFilename); |
9228 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
9229 | | // deadlock with GDALDataset own |
9230 | | // mutex. |
9231 | 0 | delete poDS; |
9232 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
9233 | 0 | return nullptr; |
9234 | 0 | } |
9235 | 0 | } |
9236 | 0 | else if (VSIMkdir(pszFilename, 0755) != 0) |
9237 | 0 | { |
9238 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s directory", |
9239 | 0 | pszFilename); |
9240 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
9241 | | // deadlock with GDALDataset own mutex. |
9242 | 0 | delete poDS; |
9243 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
9244 | 0 | return nullptr; |
9245 | 0 | } |
9246 | | |
9247 | 0 | return poDS; |
9248 | 0 | } |
9249 | | // Create the dataset. |
9250 | 0 | CPLString osFilenameForNCCreate(pszFilename); |
9251 | | #if defined(_WIN32) && !defined(NETCDF_USES_UTF8) |
9252 | | if (CPLTestBool(CPLGetConfigOption("GDAL_FILENAME_IS_UTF8", "YES"))) |
9253 | | { |
9254 | | char *pszTemp = |
9255 | | CPLRecode(osFilenameForNCCreate, CPL_ENC_UTF8, "CP_ACP"); |
9256 | | osFilenameForNCCreate = pszTemp; |
9257 | | CPLFree(pszTemp); |
9258 | | } |
9259 | | #endif |
9260 | |
|
9261 | | #if defined(_WIN32) |
9262 | | { |
9263 | | // Works around bug of msys2 netCDF 4.9.0 package where nc_create() |
9264 | | // crashes |
9265 | | VSIStatBuf sStat; |
9266 | | const std::string osDirname = |
9267 | | CPLGetDirnameSafe(osFilenameForNCCreate.c_str()); |
9268 | | if (VSIStat(osDirname.c_str(), &sStat) != 0) |
9269 | | { |
9270 | | CPLError(CE_Failure, CPLE_OpenFailed, |
9271 | | "Unable to create netCDF file %s: non existing output " |
9272 | | "directory", |
9273 | | pszFilename); |
9274 | | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll |
9275 | | // deadlock with GDALDataset own mutex. |
9276 | | delete poDS; |
9277 | | CPLAcquireMutex(hNCMutex, 1000.0); |
9278 | | return nullptr; |
9279 | | } |
9280 | | } |
9281 | | #endif |
9282 | |
|
9283 | 0 | int status = |
9284 | 0 | nc_create(osFilenameForNCCreate, poDS->nCreateMode, &(poDS->cdfid)); |
9285 | | |
9286 | | // Put into define mode. |
9287 | 0 | poDS->SetDefineMode(true); |
9288 | |
|
9289 | 0 | if (status != NC_NOERR) |
9290 | 0 | { |
9291 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
9292 | 0 | "Unable to create netCDF file %s (Error code %d): %s .", |
9293 | 0 | pszFilename, status, nc_strerror(status)); |
9294 | 0 | CPLReleaseMutex(hNCMutex); // Release mutex otherwise we'll deadlock |
9295 | | // with GDALDataset own mutex. |
9296 | 0 | delete poDS; |
9297 | 0 | CPLAcquireMutex(hNCMutex, 1000.0); |
9298 | 0 | return nullptr; |
9299 | 0 | } |
9300 | | |
9301 | | // Define dimensions. |
9302 | 0 | if (nXSize > 0 && nYSize > 0) |
9303 | 0 | { |
9304 | 0 | poDS->papszDimName.AddString(NCDF_DIMNAME_X); |
9305 | 0 | status = |
9306 | 0 | nc_def_dim(poDS->cdfid, NCDF_DIMNAME_X, nXSize, &(poDS->nXDimID)); |
9307 | 0 | NCDF_ERR(status); |
9308 | 0 | CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d", |
9309 | 0 | poDS->cdfid, NCDF_DIMNAME_X, nXSize, poDS->nXDimID); |
9310 | |
|
9311 | 0 | poDS->papszDimName.AddString(NCDF_DIMNAME_Y); |
9312 | 0 | status = |
9313 | 0 | nc_def_dim(poDS->cdfid, NCDF_DIMNAME_Y, nYSize, &(poDS->nYDimID)); |
9314 | 0 | NCDF_ERR(status); |
9315 | 0 | CPLDebug("GDAL_netCDF", "status nc_def_dim(%d, %s, %d, -) got id %d", |
9316 | 0 | poDS->cdfid, NCDF_DIMNAME_Y, nYSize, poDS->nYDimID); |
9317 | 0 | } |
9318 | |
|
9319 | 0 | return poDS; |
9320 | 0 | } |
9321 | | |
9322 | | /************************************************************************/ |
9323 | | /* Create() */ |
9324 | | /************************************************************************/ |
9325 | | |
9326 | | GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize, |
9327 | | int nYSize, int nBandsIn, GDALDataType eType, |
9328 | | char **papszOptions) |
9329 | 0 | { |
9330 | 0 | CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::Create(%s, ...)", |
9331 | 0 | pszFilename); |
9332 | |
|
9333 | 0 | const char *legacyCreationOp = |
9334 | 0 | CSLFetchNameValueDef(papszOptions, "GEOMETRY_ENCODING", "CF_1.8"); |
9335 | 0 | std::string legacyCreationOp_s = std::string(legacyCreationOp); |
9336 | | |
9337 | | // Check legacy creation op FIRST |
9338 | |
|
9339 | 0 | bool legacyCreateMode = false; |
9340 | |
|
9341 | 0 | if (nXSize != 0 || nYSize != 0 || nBandsIn != 0) |
9342 | 0 | { |
9343 | 0 | legacyCreateMode = true; |
9344 | 0 | } |
9345 | 0 | else if (legacyCreationOp_s == "CF_1.8") |
9346 | 0 | { |
9347 | 0 | legacyCreateMode = false; |
9348 | 0 | } |
9349 | | |
9350 | 0 | else if (legacyCreationOp_s == "WKT") |
9351 | 0 | { |
9352 | 0 | legacyCreateMode = true; |
9353 | 0 | } |
9354 | | |
9355 | 0 | else |
9356 | 0 | { |
9357 | 0 | CPLError( |
9358 | 0 | CE_Failure, CPLE_NotSupported, |
9359 | 0 | "Dataset creation option GEOMETRY_ENCODING=%s is not supported.", |
9360 | 0 | legacyCreationOp_s.c_str()); |
9361 | 0 | return nullptr; |
9362 | 0 | } |
9363 | | |
9364 | 0 | CPLStringList aosOptions(CSLDuplicate(papszOptions)); |
9365 | 0 | if (aosOptions.FetchNameValue("FORMAT") == nullptr && |
9366 | 0 | (eType == GDT_UInt16 || eType == GDT_UInt32 || eType == GDT_UInt64 || |
9367 | 0 | eType == GDT_Int64)) |
9368 | 0 | { |
9369 | 0 | CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type"); |
9370 | 0 | aosOptions.SetNameValue("FORMAT", "NC4"); |
9371 | 0 | } |
9372 | |
|
9373 | 0 | CPLStringList aosBandNames; |
9374 | 0 | if (const char *pszBandNames = aosOptions.FetchNameValue("BAND_NAMES")) |
9375 | 0 | { |
9376 | 0 | aosBandNames = |
9377 | 0 | CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS); |
9378 | |
|
9379 | 0 | if (aosBandNames.Count() != nBandsIn) |
9380 | 0 | { |
9381 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
9382 | 0 | "Attempted to create netCDF with %d bands but %d names " |
9383 | 0 | "provided in BAND_NAMES.", |
9384 | 0 | nBandsIn, aosBandNames.Count()); |
9385 | |
|
9386 | 0 | return nullptr; |
9387 | 0 | } |
9388 | 0 | } |
9389 | | |
9390 | 0 | CPLMutexHolderD(&hNCMutex); |
9391 | |
|
9392 | 0 | auto poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, nBandsIn, |
9393 | 0 | aosOptions.List()); |
9394 | |
|
9395 | 0 | if (!poDS) |
9396 | 0 | return nullptr; |
9397 | | |
9398 | 0 | if (!legacyCreateMode) |
9399 | 0 | { |
9400 | 0 | poDS->bSGSupport = true; |
9401 | 0 | poDS->vcdf.enableFullVirtualMode(); |
9402 | 0 | } |
9403 | | |
9404 | 0 | else |
9405 | 0 | { |
9406 | 0 | poDS->bSGSupport = false; |
9407 | 0 | } |
9408 | | |
9409 | | // Should we write signed or unsigned byte? |
9410 | | // TODO should this only be done in Create() |
9411 | 0 | poDS->bSignedData = true; |
9412 | 0 | const char *pszValue = CSLFetchNameValueDef(papszOptions, "PIXELTYPE", ""); |
9413 | 0 | if (eType == GDT_Byte && !EQUAL(pszValue, "SIGNEDBYTE")) |
9414 | 0 | poDS->bSignedData = false; |
9415 | | |
9416 | | // Add Conventions, GDAL info and history. |
9417 | 0 | if (poDS->cdfid >= 0) |
9418 | 0 | { |
9419 | 0 | const char *CF_Vector_Conv = |
9420 | 0 | poDS->bSGSupport || |
9421 | | // Use of variable length strings require CF-1.8 |
9422 | 0 | EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4") |
9423 | 0 | ? NCDF_CONVENTIONS_CF_V1_8 |
9424 | 0 | : NCDF_CONVENTIONS_CF_V1_6; |
9425 | 0 | poDS->bWriteGDALVersion = CPLTestBool( |
9426 | 0 | CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES")); |
9427 | 0 | poDS->bWriteGDALHistory = CPLTestBool( |
9428 | 0 | CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES")); |
9429 | 0 | NCDFAddGDALHistory(poDS->cdfid, pszFilename, poDS->bWriteGDALVersion, |
9430 | 0 | poDS->bWriteGDALHistory, "", "Create", |
9431 | 0 | (nBandsIn == 0) ? CF_Vector_Conv |
9432 | 0 | : GDAL_DEFAULT_NCDF_CONVENTIONS); |
9433 | 0 | } |
9434 | | |
9435 | | // Define bands. |
9436 | 0 | for (int iBand = 1; iBand <= nBandsIn; iBand++) |
9437 | 0 | { |
9438 | 0 | const char *pszBandName = |
9439 | 0 | aosBandNames.empty() ? nullptr : aosBandNames[iBand - 1]; |
9440 | |
|
9441 | 0 | poDS->SetBand(iBand, new netCDFRasterBand( |
9442 | 0 | netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, |
9443 | 0 | eType, iBand, poDS->bSignedData, pszBandName)); |
9444 | 0 | } |
9445 | |
|
9446 | 0 | CPLDebug("GDAL_netCDF", "netCDFDataset::Create(%s, ...) done", pszFilename); |
9447 | | // Return same dataset. |
9448 | 0 | return poDS; |
9449 | 0 | } |
9450 | | |
9451 | | template <class T> |
9452 | | static CPLErr NCDFCopyBand(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand, |
9453 | | int nXSize, int nYSize, GDALProgressFunc pfnProgress, |
9454 | | void *pProgressData) |
9455 | 0 | { |
9456 | 0 | GDALDataType eDT = poSrcBand->GetRasterDataType(); |
9457 | 0 | CPLErr eErr = CE_None; |
9458 | 0 | T *patScanline = static_cast<T *>(CPLMalloc(nXSize * sizeof(T))); |
9459 | |
|
9460 | 0 | for (int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++) |
9461 | 0 | { |
9462 | 0 | eErr = poSrcBand->RasterIO(GF_Read, 0, iLine, nXSize, 1, patScanline, |
9463 | 0 | nXSize, 1, eDT, 0, 0, nullptr); |
9464 | 0 | if (eErr != CE_None) |
9465 | 0 | { |
9466 | 0 | CPLDebug( |
9467 | 0 | "GDAL_netCDF", |
9468 | 0 | "NCDFCopyBand(), poSrcBand->RasterIO() returned error code %d", |
9469 | 0 | eErr); |
9470 | 0 | } |
9471 | 0 | else |
9472 | 0 | { |
9473 | 0 | eErr = |
9474 | 0 | poDstBand->RasterIO(GF_Write, 0, iLine, nXSize, 1, patScanline, |
9475 | 0 | nXSize, 1, eDT, 0, 0, nullptr); |
9476 | 0 | if (eErr != CE_None) |
9477 | 0 | CPLDebug("GDAL_netCDF", |
9478 | 0 | "NCDFCopyBand(), poDstBand->RasterIO() returned error " |
9479 | 0 | "code %d", |
9480 | 0 | eErr); |
9481 | 0 | } |
9482 | |
|
9483 | 0 | if (nYSize > 10 && (iLine % (nYSize / 10) == 1)) |
9484 | 0 | { |
9485 | 0 | if (!pfnProgress(1.0 * iLine / nYSize, nullptr, pProgressData)) |
9486 | 0 | { |
9487 | 0 | eErr = CE_Failure; |
9488 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, |
9489 | 0 | "User terminated CreateCopy()"); |
9490 | 0 | } |
9491 | 0 | } |
9492 | 0 | } |
9493 | |
|
9494 | 0 | CPLFree(patScanline); |
9495 | |
|
9496 | 0 | pfnProgress(1.0, nullptr, pProgressData); |
9497 | |
|
9498 | 0 | return eErr; |
9499 | 0 | } Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned char>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<signed char>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<short>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned short>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned int>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<int>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<unsigned long>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<long>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<float>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) Unexecuted instantiation: netcdfdataset.cpp:CPLErr NCDFCopyBand<double>(GDALRasterBand*, GDALRasterBand*, int, int, int (*)(double, char const*, void*), void*) |
9500 | | |
9501 | | /************************************************************************/ |
9502 | | /* CreateCopy() */ |
9503 | | /************************************************************************/ |
9504 | | |
9505 | | GDALDataset * |
9506 | | netCDFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS, |
9507 | | CPL_UNUSED int bStrict, char **papszOptions, |
9508 | | GDALProgressFunc pfnProgress, void *pProgressData) |
9509 | 0 | { |
9510 | 0 | CPLMutexHolderD(&hNCMutex); |
9511 | |
|
9512 | 0 | CPLDebug("GDAL_netCDF", "\n=====\nnetCDFDataset::CreateCopy(%s, ...)", |
9513 | 0 | pszFilename); |
9514 | |
|
9515 | 0 | if (poSrcDS->GetRootGroup()) |
9516 | 0 | { |
9517 | 0 | auto poDrv = GDALDriver::FromHandle(GDALGetDriverByName("netCDF")); |
9518 | 0 | if (poDrv) |
9519 | 0 | { |
9520 | 0 | return poDrv->DefaultCreateCopy(pszFilename, poSrcDS, bStrict, |
9521 | 0 | papszOptions, pfnProgress, |
9522 | 0 | pProgressData); |
9523 | 0 | } |
9524 | 0 | } |
9525 | | |
9526 | 0 | const int nBands = poSrcDS->GetRasterCount(); |
9527 | 0 | const int nXSize = poSrcDS->GetRasterXSize(); |
9528 | 0 | const int nYSize = poSrcDS->GetRasterYSize(); |
9529 | 0 | const char *pszWKT = poSrcDS->GetProjectionRef(); |
9530 | | |
9531 | | // Check input bands for errors. |
9532 | 0 | if (nBands == 0) |
9533 | 0 | { |
9534 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
9535 | 0 | "NetCDF driver does not support " |
9536 | 0 | "source dataset with zero band."); |
9537 | 0 | return nullptr; |
9538 | 0 | } |
9539 | | |
9540 | 0 | GDALDataType eDT = GDT_Unknown; |
9541 | 0 | GDALRasterBand *poSrcBand = nullptr; |
9542 | 0 | for (int iBand = 1; iBand <= nBands; iBand++) |
9543 | 0 | { |
9544 | 0 | poSrcBand = poSrcDS->GetRasterBand(iBand); |
9545 | 0 | eDT = poSrcBand->GetRasterDataType(); |
9546 | 0 | if (eDT == GDT_Unknown || GDALDataTypeIsComplex(eDT)) |
9547 | 0 | { |
9548 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
9549 | 0 | "NetCDF driver does not support source dataset with band " |
9550 | 0 | "of complex type."); |
9551 | 0 | return nullptr; |
9552 | 0 | } |
9553 | 0 | } |
9554 | | |
9555 | 0 | CPLStringList aosBandNames; |
9556 | 0 | if (const char *pszBandNames = |
9557 | 0 | CSLFetchNameValue(papszOptions, "BAND_NAMES")) |
9558 | 0 | { |
9559 | 0 | aosBandNames = |
9560 | 0 | CSLTokenizeString2(pszBandNames, ",", CSLT_HONOURSTRINGS); |
9561 | |
|
9562 | 0 | if (aosBandNames.Count() != nBands) |
9563 | 0 | { |
9564 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
9565 | 0 | "Attempted to create netCDF with %d bands but %d names " |
9566 | 0 | "provided in BAND_NAMES.", |
9567 | 0 | nBands, aosBandNames.Count()); |
9568 | |
|
9569 | 0 | return nullptr; |
9570 | 0 | } |
9571 | 0 | } |
9572 | | |
9573 | 0 | if (!pfnProgress(0.0, nullptr, pProgressData)) |
9574 | 0 | return nullptr; |
9575 | | |
9576 | | // Same as in Create(). |
9577 | 0 | CPLStringList aosOptions(CSLDuplicate(papszOptions)); |
9578 | 0 | if (aosOptions.FetchNameValue("FORMAT") == nullptr && |
9579 | 0 | (eDT == GDT_UInt16 || eDT == GDT_UInt32 || eDT == GDT_UInt64 || |
9580 | 0 | eDT == GDT_Int64)) |
9581 | 0 | { |
9582 | 0 | CPLDebug("netCDF", "Selecting FORMAT=NC4 due to data type"); |
9583 | 0 | aosOptions.SetNameValue("FORMAT", "NC4"); |
9584 | 0 | } |
9585 | 0 | netCDFDataset *poDS = netCDFDataset::CreateLL(pszFilename, nXSize, nYSize, |
9586 | 0 | nBands, aosOptions.List()); |
9587 | 0 | if (!poDS) |
9588 | 0 | return nullptr; |
9589 | | |
9590 | | // Copy global metadata. |
9591 | | // Add Conventions, GDAL info and history. |
9592 | 0 | CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, NC_GLOBAL, nullptr); |
9593 | 0 | const bool bWriteGDALVersion = CPLTestBool( |
9594 | 0 | CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES")); |
9595 | 0 | const bool bWriteGDALHistory = CPLTestBool( |
9596 | 0 | CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_HISTORY", "YES")); |
9597 | 0 | NCDFAddGDALHistory( |
9598 | 0 | poDS->cdfid, pszFilename, bWriteGDALVersion, bWriteGDALHistory, |
9599 | 0 | poSrcDS->GetMetadataItem("NC_GLOBAL#history"), "CreateCopy", |
9600 | 0 | poSrcDS->GetMetadataItem("NC_GLOBAL#Conventions")); |
9601 | |
|
9602 | 0 | pfnProgress(0.1, nullptr, pProgressData); |
9603 | | |
9604 | | // Check for extra dimensions. |
9605 | 0 | int nDim = 2; |
9606 | 0 | char **papszExtraDimNames = |
9607 | 0 | NCDFTokenizeArray(poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", "")); |
9608 | 0 | char **papszExtraDimValues = nullptr; |
9609 | |
|
9610 | 0 | if (papszExtraDimNames != nullptr && CSLCount(papszExtraDimNames) > 0) |
9611 | 0 | { |
9612 | 0 | size_t nDimSizeTot = 1; |
9613 | | // first make sure dimensions lengths compatible with band count |
9614 | | // for( int i=0; i<CSLCount(papszExtraDimNames ); i++ ) { |
9615 | 0 | for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--) |
9616 | 0 | { |
9617 | 0 | char szTemp[NC_MAX_NAME + 32 + 1]; |
9618 | 0 | snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF", |
9619 | 0 | papszExtraDimNames[i]); |
9620 | 0 | papszExtraDimValues = |
9621 | 0 | NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, "")); |
9622 | 0 | const size_t nDimSize = atol(papszExtraDimValues[0]); |
9623 | 0 | CSLDestroy(papszExtraDimValues); |
9624 | 0 | nDimSizeTot *= nDimSize; |
9625 | 0 | } |
9626 | 0 | if (nDimSizeTot == (size_t)nBands) |
9627 | 0 | { |
9628 | 0 | nDim = 2 + CSLCount(papszExtraDimNames); |
9629 | 0 | } |
9630 | 0 | else |
9631 | 0 | { |
9632 | | // if nBands != #bands computed raise a warning |
9633 | | // just issue a debug message, because it was probably intentional |
9634 | 0 | CPLDebug("GDAL_netCDF", |
9635 | 0 | "Warning: Number of bands (%d) is not compatible with " |
9636 | 0 | "dimensions " |
9637 | 0 | "(total=%ld names=%s)", |
9638 | 0 | nBands, (long)nDimSizeTot, |
9639 | 0 | poSrcDS->GetMetadataItem("NETCDF_DIM_EXTRA", "")); |
9640 | 0 | CSLDestroy(papszExtraDimNames); |
9641 | 0 | papszExtraDimNames = nullptr; |
9642 | 0 | } |
9643 | 0 | } |
9644 | |
|
9645 | 0 | int *panDimIds = static_cast<int *>(CPLCalloc(nDim, sizeof(int))); |
9646 | 0 | int *panBandDimPos = static_cast<int *>(CPLCalloc(nDim, sizeof(int))); |
9647 | |
|
9648 | 0 | nc_type nVarType; |
9649 | 0 | int *panBandZLev = nullptr; |
9650 | 0 | int *panDimVarIds = nullptr; |
9651 | |
|
9652 | 0 | if (nDim > 2) |
9653 | 0 | { |
9654 | 0 | panBandZLev = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int))); |
9655 | 0 | panDimVarIds = static_cast<int *>(CPLCalloc(nDim - 2, sizeof(int))); |
9656 | | |
9657 | | // Define all dims. |
9658 | 0 | for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--) |
9659 | 0 | { |
9660 | 0 | poDS->papszDimName.AddString(papszExtraDimNames[i]); |
9661 | 0 | char szTemp[NC_MAX_NAME + 32 + 1]; |
9662 | 0 | snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_DEF", |
9663 | 0 | papszExtraDimNames[i]); |
9664 | 0 | papszExtraDimValues = |
9665 | 0 | NCDFTokenizeArray(poSrcDS->GetMetadataItem(szTemp, "")); |
9666 | 0 | const int nDimSize = papszExtraDimValues && papszExtraDimValues[0] |
9667 | 0 | ? atoi(papszExtraDimValues[0]) |
9668 | 0 | : 0; |
9669 | | // nc_type is an enum in netcdf-3, needs casting. |
9670 | 0 | nVarType = static_cast<nc_type>(papszExtraDimValues && |
9671 | 0 | papszExtraDimValues[0] && |
9672 | 0 | papszExtraDimValues[1] |
9673 | 0 | ? atol(papszExtraDimValues[1]) |
9674 | 0 | : 0); |
9675 | 0 | CSLDestroy(papszExtraDimValues); |
9676 | 0 | panBandZLev[i] = nDimSize; |
9677 | 0 | panBandDimPos[i + 2] = i; // Save Position of ZDim. |
9678 | | |
9679 | | // Define dim. |
9680 | 0 | int status = nc_def_dim(poDS->cdfid, papszExtraDimNames[i], |
9681 | 0 | nDimSize, &(panDimIds[i])); |
9682 | 0 | NCDF_ERR(status); |
9683 | | |
9684 | | // Define dim var. |
9685 | 0 | int anDim[1] = {panDimIds[i]}; |
9686 | 0 | status = nc_def_var(poDS->cdfid, papszExtraDimNames[i], nVarType, 1, |
9687 | 0 | anDim, &(panDimVarIds[i])); |
9688 | 0 | NCDF_ERR(status); |
9689 | | |
9690 | | // Add dim metadata, using global var# items. |
9691 | 0 | snprintf(szTemp, sizeof(szTemp), "%s#", papszExtraDimNames[i]); |
9692 | 0 | CopyMetadata(poSrcDS, nullptr, nullptr, poDS->cdfid, |
9693 | 0 | panDimVarIds[i], szTemp); |
9694 | 0 | } |
9695 | 0 | } |
9696 | | |
9697 | | // Copy GeoTransform and Projection. |
9698 | | |
9699 | | // Copy geolocation info. |
9700 | 0 | char **papszGeolocationInfo = poSrcDS->GetMetadata("GEOLOCATION"); |
9701 | 0 | if (papszGeolocationInfo != nullptr) |
9702 | 0 | poDS->GDALPamDataset::SetMetadata(papszGeolocationInfo, "GEOLOCATION"); |
9703 | | |
9704 | | // Copy geotransform. |
9705 | 0 | bool bGotGeoTransform = false; |
9706 | 0 | double adfGeoTransform[6]; |
9707 | 0 | CPLErr eErr = poSrcDS->GetGeoTransform(adfGeoTransform); |
9708 | 0 | if (eErr == CE_None) |
9709 | 0 | { |
9710 | 0 | poDS->SetGeoTransform(adfGeoTransform); |
9711 | | // Disable AddProjectionVars() from being called. |
9712 | 0 | bGotGeoTransform = true; |
9713 | 0 | poDS->m_bHasGeoTransform = false; |
9714 | 0 | } |
9715 | | |
9716 | | // Copy projection. |
9717 | 0 | void *pScaledProgress = nullptr; |
9718 | 0 | if (bGotGeoTransform || (pszWKT && pszWKT[0] != 0)) |
9719 | 0 | { |
9720 | 0 | poDS->SetProjection(pszWKT ? pszWKT : ""); |
9721 | | |
9722 | | // Now we can call AddProjectionVars() directly. |
9723 | 0 | poDS->m_bHasGeoTransform = bGotGeoTransform; |
9724 | 0 | poDS->AddProjectionVars(true, nullptr, nullptr); |
9725 | 0 | pScaledProgress = |
9726 | 0 | GDALCreateScaledProgress(0.1, 0.25, pfnProgress, pProgressData); |
9727 | 0 | poDS->AddProjectionVars(false, GDALScaledProgress, pScaledProgress); |
9728 | 0 | GDALDestroyScaledProgress(pScaledProgress); |
9729 | 0 | } |
9730 | 0 | else |
9731 | 0 | { |
9732 | 0 | poDS->bBottomUp = |
9733 | 0 | CPL_TO_BOOL(CSLFetchBoolean(papszOptions, "WRITE_BOTTOMUP", TRUE)); |
9734 | 0 | if (papszGeolocationInfo) |
9735 | 0 | { |
9736 | 0 | poDS->AddProjectionVars(true, nullptr, nullptr); |
9737 | 0 | poDS->AddProjectionVars(false, nullptr, nullptr); |
9738 | 0 | } |
9739 | 0 | } |
9740 | | |
9741 | | // Save X,Y dim positions. |
9742 | 0 | panDimIds[nDim - 1] = poDS->nXDimID; |
9743 | 0 | panBandDimPos[0] = nDim - 1; |
9744 | 0 | panDimIds[nDim - 2] = poDS->nYDimID; |
9745 | 0 | panBandDimPos[1] = nDim - 2; |
9746 | | |
9747 | | // Write extra dim values - after projection for optimization. |
9748 | 0 | if (nDim > 2) |
9749 | 0 | { |
9750 | | // Make sure we are in data mode. |
9751 | 0 | static_cast<netCDFDataset *>(poDS)->SetDefineMode(false); |
9752 | 0 | for (int i = CSLCount(papszExtraDimNames) - 1; i >= 0; i--) |
9753 | 0 | { |
9754 | 0 | char szTemp[NC_MAX_NAME + 32 + 1]; |
9755 | 0 | snprintf(szTemp, sizeof(szTemp), "NETCDF_DIM_%s_VALUES", |
9756 | 0 | papszExtraDimNames[i]); |
9757 | 0 | if (poSrcDS->GetMetadataItem(szTemp) != nullptr) |
9758 | 0 | { |
9759 | 0 | NCDFPut1DVar(poDS->cdfid, panDimVarIds[i], |
9760 | 0 | poSrcDS->GetMetadataItem(szTemp)); |
9761 | 0 | } |
9762 | 0 | } |
9763 | 0 | } |
9764 | |
|
9765 | 0 | pfnProgress(0.25, nullptr, pProgressData); |
9766 | | |
9767 | | // Define Bands. |
9768 | 0 | netCDFRasterBand *poBand = nullptr; |
9769 | 0 | int nBandID = -1; |
9770 | |
|
9771 | 0 | for (int iBand = 1; iBand <= nBands; iBand++) |
9772 | 0 | { |
9773 | 0 | CPLDebug("GDAL_netCDF", "creating band # %d/%d nDim = %d", iBand, |
9774 | 0 | nBands, nDim); |
9775 | |
|
9776 | 0 | poSrcBand = poSrcDS->GetRasterBand(iBand); |
9777 | 0 | eDT = poSrcBand->GetRasterDataType(); |
9778 | | |
9779 | | // Get var name from NETCDF_VARNAME. |
9780 | 0 | const char *pszNETCDF_VARNAME = |
9781 | 0 | poSrcBand->GetMetadataItem("NETCDF_VARNAME"); |
9782 | 0 | char szBandName[NC_MAX_NAME + 1]; |
9783 | 0 | if (!aosBandNames.empty()) |
9784 | 0 | { |
9785 | 0 | snprintf(szBandName, sizeof(szBandName), "%s", |
9786 | 0 | aosBandNames[iBand - 1]); |
9787 | 0 | } |
9788 | 0 | else if (pszNETCDF_VARNAME) |
9789 | 0 | { |
9790 | 0 | if (nBands > 1 && papszExtraDimNames == nullptr) |
9791 | 0 | snprintf(szBandName, sizeof(szBandName), "%s%d", |
9792 | 0 | pszNETCDF_VARNAME, iBand); |
9793 | 0 | else |
9794 | 0 | snprintf(szBandName, sizeof(szBandName), "%s", |
9795 | 0 | pszNETCDF_VARNAME); |
9796 | 0 | } |
9797 | 0 | else |
9798 | 0 | { |
9799 | 0 | szBandName[0] = '\0'; |
9800 | 0 | } |
9801 | | |
9802 | | // Get long_name from <var>#long_name. |
9803 | 0 | const char *pszLongName = ""; |
9804 | 0 | if (pszNETCDF_VARNAME) |
9805 | 0 | { |
9806 | 0 | pszLongName = |
9807 | 0 | poSrcDS->GetMetadataItem(std::string(pszNETCDF_VARNAME) |
9808 | 0 | .append("#") |
9809 | 0 | .append(CF_LNG_NAME) |
9810 | 0 | .c_str()); |
9811 | 0 | if (!pszLongName) |
9812 | 0 | pszLongName = ""; |
9813 | 0 | } |
9814 | |
|
9815 | 0 | constexpr bool bSignedData = false; |
9816 | |
|
9817 | 0 | if (nDim > 2) |
9818 | 0 | poBand = new netCDFRasterBand( |
9819 | 0 | netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand, |
9820 | 0 | bSignedData, szBandName, pszLongName, nBandID, nDim, iBand - 1, |
9821 | 0 | panBandZLev, panBandDimPos, panDimIds); |
9822 | 0 | else |
9823 | 0 | poBand = new netCDFRasterBand( |
9824 | 0 | netCDFRasterBand::CONSTRUCTOR_CREATE(), poDS, eDT, iBand, |
9825 | 0 | bSignedData, szBandName, pszLongName); |
9826 | |
|
9827 | 0 | poDS->SetBand(iBand, poBand); |
9828 | | |
9829 | | // Set nodata value, if any. |
9830 | 0 | GDALCopyNoDataValue(poBand, poSrcBand); |
9831 | | |
9832 | | // Copy Metadata for band. |
9833 | 0 | CopyMetadata(nullptr, poSrcDS->GetRasterBand(iBand), poBand, |
9834 | 0 | poDS->cdfid, poBand->nZId); |
9835 | | |
9836 | | // If more than 2D pass the first band's netcdf var ID to subsequent |
9837 | | // bands. |
9838 | 0 | if (nDim > 2) |
9839 | 0 | nBandID = poBand->nZId; |
9840 | 0 | } |
9841 | | |
9842 | | // Write projection variable to band variable. |
9843 | 0 | poDS->AddGridMappingRef(); |
9844 | |
|
9845 | 0 | pfnProgress(0.5, nullptr, pProgressData); |
9846 | | |
9847 | | // Write bands. |
9848 | | |
9849 | | // Make sure we are in data mode. |
9850 | 0 | poDS->SetDefineMode(false); |
9851 | |
|
9852 | 0 | double dfTemp = 0.5; |
9853 | |
|
9854 | 0 | eErr = CE_None; |
9855 | |
|
9856 | 0 | for (int iBand = 1; iBand <= nBands && eErr == CE_None; iBand++) |
9857 | 0 | { |
9858 | 0 | const double dfTemp2 = dfTemp + 0.4 / nBands; |
9859 | 0 | pScaledProgress = GDALCreateScaledProgress(dfTemp, dfTemp2, pfnProgress, |
9860 | 0 | pProgressData); |
9861 | 0 | dfTemp = dfTemp2; |
9862 | |
|
9863 | 0 | CPLDebug("GDAL_netCDF", "copying band data # %d/%d ", iBand, nBands); |
9864 | |
|
9865 | 0 | poSrcBand = poSrcDS->GetRasterBand(iBand); |
9866 | 0 | eDT = poSrcBand->GetRasterDataType(); |
9867 | |
|
9868 | 0 | GDALRasterBand *poDstBand = poDS->GetRasterBand(iBand); |
9869 | | |
9870 | | // Copy band data. |
9871 | 0 | if (eDT == GDT_Byte) |
9872 | 0 | { |
9873 | 0 | CPLDebug("GDAL_netCDF", "GByte Band#%d", iBand); |
9874 | 0 | eErr = NCDFCopyBand<GByte>(poSrcBand, poDstBand, nXSize, nYSize, |
9875 | 0 | GDALScaledProgress, pScaledProgress); |
9876 | 0 | } |
9877 | 0 | else if (eDT == GDT_Int8) |
9878 | 0 | { |
9879 | 0 | CPLDebug("GDAL_netCDF", "GInt8 Band#%d", iBand); |
9880 | 0 | eErr = NCDFCopyBand<GInt8>(poSrcBand, poDstBand, nXSize, nYSize, |
9881 | 0 | GDALScaledProgress, pScaledProgress); |
9882 | 0 | } |
9883 | 0 | else if (eDT == GDT_UInt16) |
9884 | 0 | { |
9885 | 0 | CPLDebug("GDAL_netCDF", "GUInt16 Band#%d", iBand); |
9886 | 0 | eErr = NCDFCopyBand<GInt16>(poSrcBand, poDstBand, nXSize, nYSize, |
9887 | 0 | GDALScaledProgress, pScaledProgress); |
9888 | 0 | } |
9889 | 0 | else if (eDT == GDT_Int16) |
9890 | 0 | { |
9891 | 0 | CPLDebug("GDAL_netCDF", "GInt16 Band#%d", iBand); |
9892 | 0 | eErr = NCDFCopyBand<GUInt16>(poSrcBand, poDstBand, nXSize, nYSize, |
9893 | 0 | GDALScaledProgress, pScaledProgress); |
9894 | 0 | } |
9895 | 0 | else if (eDT == GDT_UInt32) |
9896 | 0 | { |
9897 | 0 | CPLDebug("GDAL_netCDF", "GUInt32 Band#%d", iBand); |
9898 | 0 | eErr = NCDFCopyBand<GUInt32>(poSrcBand, poDstBand, nXSize, nYSize, |
9899 | 0 | GDALScaledProgress, pScaledProgress); |
9900 | 0 | } |
9901 | 0 | else if (eDT == GDT_Int32) |
9902 | 0 | { |
9903 | 0 | CPLDebug("GDAL_netCDF", "GInt32 Band#%d", iBand); |
9904 | 0 | eErr = NCDFCopyBand<GInt32>(poSrcBand, poDstBand, nXSize, nYSize, |
9905 | 0 | GDALScaledProgress, pScaledProgress); |
9906 | 0 | } |
9907 | 0 | else if (eDT == GDT_UInt64) |
9908 | 0 | { |
9909 | 0 | CPLDebug("GDAL_netCDF", "GUInt64 Band#%d", iBand); |
9910 | 0 | eErr = NCDFCopyBand<std::uint64_t>(poSrcBand, poDstBand, nXSize, |
9911 | 0 | nYSize, GDALScaledProgress, |
9912 | 0 | pScaledProgress); |
9913 | 0 | } |
9914 | 0 | else if (eDT == GDT_Int64) |
9915 | 0 | { |
9916 | 0 | CPLDebug("GDAL_netCDF", "GInt64 Band#%d", iBand); |
9917 | 0 | eErr = |
9918 | 0 | NCDFCopyBand<std::int64_t>(poSrcBand, poDstBand, nXSize, nYSize, |
9919 | 0 | GDALScaledProgress, pScaledProgress); |
9920 | 0 | } |
9921 | 0 | else if (eDT == GDT_Float32) |
9922 | 0 | { |
9923 | 0 | CPLDebug("GDAL_netCDF", "float Band#%d", iBand); |
9924 | 0 | eErr = NCDFCopyBand<float>(poSrcBand, poDstBand, nXSize, nYSize, |
9925 | 0 | GDALScaledProgress, pScaledProgress); |
9926 | 0 | } |
9927 | 0 | else if (eDT == GDT_Float64) |
9928 | 0 | { |
9929 | 0 | CPLDebug("GDAL_netCDF", "double Band#%d", iBand); |
9930 | 0 | eErr = NCDFCopyBand<double>(poSrcBand, poDstBand, nXSize, nYSize, |
9931 | 0 | GDALScaledProgress, pScaledProgress); |
9932 | 0 | } |
9933 | 0 | else |
9934 | 0 | { |
9935 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
9936 | 0 | "The NetCDF driver does not support GDAL data type %d", |
9937 | 0 | eDT); |
9938 | 0 | } |
9939 | |
|
9940 | 0 | GDALDestroyScaledProgress(pScaledProgress); |
9941 | 0 | } |
9942 | |
|
9943 | 0 | delete (poDS); |
9944 | |
|
9945 | 0 | CPLFree(panDimIds); |
9946 | 0 | CPLFree(panBandDimPos); |
9947 | 0 | CPLFree(panBandZLev); |
9948 | 0 | CPLFree(panDimVarIds); |
9949 | 0 | if (papszExtraDimNames) |
9950 | 0 | CSLDestroy(papszExtraDimNames); |
9951 | |
|
9952 | 0 | if (eErr != CE_None) |
9953 | 0 | return nullptr; |
9954 | | |
9955 | 0 | pfnProgress(0.95, nullptr, pProgressData); |
9956 | | |
9957 | | // Re-open dataset so we can return it. |
9958 | 0 | CPLStringList aosOpenOptions; |
9959 | 0 | aosOpenOptions.AddString("VARIABLES_AS_BANDS=YES"); |
9960 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_Update); |
9961 | 0 | oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_UPDATE; |
9962 | 0 | oOpenInfo.papszOpenOptions = aosOpenOptions.List(); |
9963 | 0 | auto poRetDS = Open(&oOpenInfo); |
9964 | | |
9965 | | // PAM cloning is disabled. See bug #4244. |
9966 | | // if( poDS ) |
9967 | | // poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT); |
9968 | |
|
9969 | 0 | pfnProgress(1.0, nullptr, pProgressData); |
9970 | |
|
9971 | 0 | return poRetDS; |
9972 | 0 | } |
9973 | | |
9974 | | // Note: some logic depends on bIsProjected and bIsGeoGraphic. |
9975 | | // May not be known when Create() is called, see AddProjectionVars(). |
9976 | | void netCDFDataset::ProcessCreationOptions() |
9977 | 0 | { |
9978 | 0 | const char *pszConfig = |
9979 | 0 | CSLFetchNameValue(papszCreationOptions, "CONFIG_FILE"); |
9980 | 0 | if (pszConfig != nullptr) |
9981 | 0 | { |
9982 | 0 | if (oWriterConfig.Parse(pszConfig)) |
9983 | 0 | { |
9984 | | // Override dataset creation options from the config file |
9985 | 0 | std::map<CPLString, CPLString>::iterator oIter; |
9986 | 0 | for (oIter = oWriterConfig.m_oDatasetCreationOptions.begin(); |
9987 | 0 | oIter != oWriterConfig.m_oDatasetCreationOptions.end(); |
9988 | 0 | ++oIter) |
9989 | 0 | { |
9990 | 0 | papszCreationOptions = CSLSetNameValue( |
9991 | 0 | papszCreationOptions, oIter->first, oIter->second); |
9992 | 0 | } |
9993 | 0 | } |
9994 | 0 | } |
9995 | | |
9996 | | // File format. |
9997 | 0 | eFormat = NCDF_FORMAT_NC; |
9998 | 0 | const char *pszValue = CSLFetchNameValue(papszCreationOptions, "FORMAT"); |
9999 | 0 | if (pszValue != nullptr) |
10000 | 0 | { |
10001 | 0 | if (EQUAL(pszValue, "NC")) |
10002 | 0 | { |
10003 | 0 | eFormat = NCDF_FORMAT_NC; |
10004 | 0 | } |
10005 | 0 | #ifdef NETCDF_HAS_NC2 |
10006 | 0 | else if (EQUAL(pszValue, "NC2")) |
10007 | 0 | { |
10008 | 0 | eFormat = NCDF_FORMAT_NC2; |
10009 | 0 | } |
10010 | 0 | #endif |
10011 | 0 | else if (EQUAL(pszValue, "NC4")) |
10012 | 0 | { |
10013 | 0 | eFormat = NCDF_FORMAT_NC4; |
10014 | 0 | } |
10015 | 0 | else if (EQUAL(pszValue, "NC4C")) |
10016 | 0 | { |
10017 | 0 | eFormat = NCDF_FORMAT_NC4C; |
10018 | 0 | } |
10019 | 0 | else |
10020 | 0 | { |
10021 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
10022 | 0 | "FORMAT=%s in not supported, using the default NC format.", |
10023 | 0 | pszValue); |
10024 | 0 | } |
10025 | 0 | } |
10026 | | |
10027 | | // COMPRESS option. |
10028 | 0 | pszValue = CSLFetchNameValue(papszCreationOptions, "COMPRESS"); |
10029 | 0 | if (pszValue != nullptr) |
10030 | 0 | { |
10031 | 0 | if (EQUAL(pszValue, "NONE")) |
10032 | 0 | { |
10033 | 0 | eCompress = NCDF_COMPRESS_NONE; |
10034 | 0 | } |
10035 | 0 | else if (EQUAL(pszValue, "DEFLATE")) |
10036 | 0 | { |
10037 | 0 | eCompress = NCDF_COMPRESS_DEFLATE; |
10038 | 0 | if (!((eFormat == NCDF_FORMAT_NC4) || |
10039 | 0 | (eFormat == NCDF_FORMAT_NC4C))) |
10040 | 0 | { |
10041 | 0 | CPLError(CE_Warning, CPLE_IllegalArg, |
10042 | 0 | "NOTICE: Format set to NC4C because compression is " |
10043 | 0 | "set to DEFLATE."); |
10044 | 0 | eFormat = NCDF_FORMAT_NC4C; |
10045 | 0 | } |
10046 | 0 | } |
10047 | 0 | else |
10048 | 0 | { |
10049 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
10050 | 0 | "COMPRESS=%s is not supported.", pszValue); |
10051 | 0 | } |
10052 | 0 | } |
10053 | | |
10054 | | // ZLEVEL option. |
10055 | 0 | pszValue = CSLFetchNameValue(papszCreationOptions, "ZLEVEL"); |
10056 | 0 | if (pszValue != nullptr) |
10057 | 0 | { |
10058 | 0 | nZLevel = atoi(pszValue); |
10059 | 0 | if (!(nZLevel >= 1 && nZLevel <= 9)) |
10060 | 0 | { |
10061 | 0 | CPLError(CE_Warning, CPLE_IllegalArg, |
10062 | 0 | "ZLEVEL=%s value not recognised, ignoring.", pszValue); |
10063 | 0 | nZLevel = NCDF_DEFLATE_LEVEL; |
10064 | 0 | } |
10065 | 0 | } |
10066 | | |
10067 | | // CHUNKING option. |
10068 | 0 | bChunking = |
10069 | 0 | CPL_TO_BOOL(CSLFetchBoolean(papszCreationOptions, "CHUNKING", TRUE)); |
10070 | | |
10071 | | // MULTIPLE_LAYERS option. |
10072 | 0 | const char *pszMultipleLayerBehavior = |
10073 | 0 | CSLFetchNameValueDef(papszCreationOptions, "MULTIPLE_LAYERS", "NO"); |
10074 | 0 | const char *pszGeometryEnc = CSLFetchNameValueDef( |
10075 | 0 | papszCreationOptions, "GEOMETRY_ENCODING", "CF_1.8"); |
10076 | 0 | if (EQUAL(pszMultipleLayerBehavior, "NO") || |
10077 | 0 | EQUAL(pszGeometryEnc, "CF_1.8")) |
10078 | 0 | { |
10079 | 0 | eMultipleLayerBehavior = SINGLE_LAYER; |
10080 | 0 | } |
10081 | 0 | else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_FILES")) |
10082 | 0 | { |
10083 | 0 | eMultipleLayerBehavior = SEPARATE_FILES; |
10084 | 0 | } |
10085 | 0 | else if (EQUAL(pszMultipleLayerBehavior, "SEPARATE_GROUPS")) |
10086 | 0 | { |
10087 | 0 | if (eFormat == NCDF_FORMAT_NC4) |
10088 | 0 | { |
10089 | 0 | eMultipleLayerBehavior = SEPARATE_GROUPS; |
10090 | 0 | } |
10091 | 0 | else |
10092 | 0 | { |
10093 | 0 | CPLError(CE_Warning, CPLE_IllegalArg, |
10094 | 0 | "MULTIPLE_LAYERS=%s is recognised only with FORMAT=NC4", |
10095 | 0 | pszMultipleLayerBehavior); |
10096 | 0 | } |
10097 | 0 | } |
10098 | 0 | else |
10099 | 0 | { |
10100 | 0 | CPLError(CE_Warning, CPLE_IllegalArg, |
10101 | 0 | "MULTIPLE_LAYERS=%s not recognised", pszMultipleLayerBehavior); |
10102 | 0 | } |
10103 | | |
10104 | | // Set nCreateMode based on eFormat. |
10105 | 0 | switch (eFormat) |
10106 | 0 | { |
10107 | 0 | #ifdef NETCDF_HAS_NC2 |
10108 | 0 | case NCDF_FORMAT_NC2: |
10109 | 0 | nCreateMode = NC_CLOBBER | NC_64BIT_OFFSET; |
10110 | 0 | break; |
10111 | 0 | #endif |
10112 | 0 | case NCDF_FORMAT_NC4: |
10113 | 0 | nCreateMode = NC_CLOBBER | NC_NETCDF4; |
10114 | 0 | break; |
10115 | 0 | case NCDF_FORMAT_NC4C: |
10116 | 0 | nCreateMode = NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL; |
10117 | 0 | break; |
10118 | 0 | case NCDF_FORMAT_NC: |
10119 | 0 | default: |
10120 | 0 | nCreateMode = NC_CLOBBER; |
10121 | 0 | break; |
10122 | 0 | } |
10123 | | |
10124 | 0 | CPLDebug("GDAL_netCDF", "file options: format=%d compress=%d zlevel=%d", |
10125 | 0 | eFormat, eCompress, nZLevel); |
10126 | 0 | } |
10127 | | |
10128 | | int netCDFDataset::DefVarDeflate(int nVarId, bool bChunkingArg) |
10129 | 0 | { |
10130 | 0 | if (eCompress == NCDF_COMPRESS_DEFLATE) |
10131 | 0 | { |
10132 | | // Must set chunk size to avoid huge performance hit (set |
10133 | | // bChunkingArg=TRUE) |
10134 | | // perhaps another solution it to change the chunk cache? |
10135 | | // http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.html#Chunk-Cache |
10136 | | // TODO: make sure this is okay. |
10137 | 0 | CPLDebug("GDAL_netCDF", "DefVarDeflate(%d, %d) nZlevel=%d", nVarId, |
10138 | 0 | static_cast<int>(bChunkingArg), nZLevel); |
10139 | |
|
10140 | 0 | int status = nc_def_var_deflate(cdfid, nVarId, 1, 1, nZLevel); |
10141 | 0 | NCDF_ERR(status); |
10142 | |
|
10143 | 0 | if (status == NC_NOERR && bChunkingArg && bChunking) |
10144 | 0 | { |
10145 | | // set chunking to be 1 for all dims, except X dim |
10146 | | // size_t chunksize[] = { 1, (size_t)nRasterXSize }; |
10147 | 0 | size_t chunksize[MAX_NC_DIMS]; |
10148 | 0 | int nd; |
10149 | 0 | nc_inq_varndims(cdfid, nVarId, &nd); |
10150 | 0 | chunksize[0] = (size_t)1; |
10151 | 0 | chunksize[1] = (size_t)1; |
10152 | 0 | for (int i = 2; i < nd; i++) |
10153 | 0 | chunksize[i] = (size_t)1; |
10154 | 0 | chunksize[nd - 1] = (size_t)nRasterXSize; |
10155 | | |
10156 | | // Config options just for testing purposes |
10157 | 0 | const char *pszBlockXSize = |
10158 | 0 | CPLGetConfigOption("BLOCKXSIZE", nullptr); |
10159 | 0 | if (pszBlockXSize) |
10160 | 0 | chunksize[nd - 1] = (size_t)atoi(pszBlockXSize); |
10161 | |
|
10162 | 0 | const char *pszBlockYSize = |
10163 | 0 | CPLGetConfigOption("BLOCKYSIZE", nullptr); |
10164 | 0 | if (nd >= 2 && pszBlockYSize) |
10165 | 0 | chunksize[nd - 2] = (size_t)atoi(pszBlockYSize); |
10166 | |
|
10167 | 0 | CPLDebug("GDAL_netCDF", |
10168 | 0 | "DefVarDeflate() chunksize={%ld, %ld} chunkX=%ld nd=%d", |
10169 | 0 | (long)chunksize[0], (long)chunksize[1], |
10170 | 0 | (long)chunksize[nd - 1], nd); |
10171 | | #ifdef NCDF_DEBUG |
10172 | | for (int i = 0; i < nd; i++) |
10173 | | CPLDebug("GDAL_netCDF", "DefVarDeflate() chunk[%d]=%ld", i, |
10174 | | chunksize[i]); |
10175 | | #endif |
10176 | |
|
10177 | 0 | status = nc_def_var_chunking(cdfid, nVarId, NC_CHUNKED, chunksize); |
10178 | 0 | NCDF_ERR(status); |
10179 | 0 | } |
10180 | 0 | else |
10181 | 0 | { |
10182 | 0 | CPLDebug("GDAL_netCDF", "chunksize not set"); |
10183 | 0 | } |
10184 | 0 | return status; |
10185 | 0 | } |
10186 | 0 | return NC_NOERR; |
10187 | 0 | } |
10188 | | |
10189 | | /************************************************************************/ |
10190 | | /* NCDFUnloadDriver() */ |
10191 | | /************************************************************************/ |
10192 | | |
10193 | | static void NCDFUnloadDriver(CPL_UNUSED GDALDriver *poDriver) |
10194 | 0 | { |
10195 | 0 | if (hNCMutex != nullptr) |
10196 | 0 | CPLDestroyMutex(hNCMutex); |
10197 | 0 | hNCMutex = nullptr; |
10198 | 0 | } |
10199 | | |
10200 | | /************************************************************************/ |
10201 | | /* GDALRegister_netCDF() */ |
10202 | | /************************************************************************/ |
10203 | | |
10204 | | class GDALnetCDFDriver final : public GDALDriver |
10205 | | { |
10206 | | public: |
10207 | 2 | GDALnetCDFDriver() = default; |
10208 | | |
10209 | | const char *GetMetadataItem(const char *pszName, |
10210 | | const char *pszDomain) override; |
10211 | | |
10212 | | char **GetMetadata(const char *pszDomain) override |
10213 | 0 | { |
10214 | 0 | std::lock_guard oLock(m_oMutex); |
10215 | 0 | InitializeDCAPVirtualIO(); |
10216 | 0 | return GDALDriver::GetMetadata(pszDomain); |
10217 | 0 | } |
10218 | | |
10219 | | private: |
10220 | | std::mutex m_oMutex{}; |
10221 | | bool m_bInitialized = false; |
10222 | | |
10223 | | void InitializeDCAPVirtualIO() |
10224 | 0 | { |
10225 | 0 | if (!m_bInitialized) |
10226 | 0 | { |
10227 | 0 | m_bInitialized = true; |
10228 | |
|
10229 | 0 | #ifdef ENABLE_UFFD |
10230 | 0 | if (CPLIsUserFaultMappingSupported()) |
10231 | 0 | { |
10232 | 0 | SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); |
10233 | 0 | } |
10234 | 0 | #endif |
10235 | 0 | } |
10236 | 0 | } |
10237 | | }; |
10238 | | |
10239 | | const char *GDALnetCDFDriver::GetMetadataItem(const char *pszName, |
10240 | | const char *pszDomain) |
10241 | 989k | { |
10242 | 989k | std::lock_guard oLock(m_oMutex); |
10243 | 989k | if (EQUAL(pszName, GDAL_DCAP_VIRTUALIO)) |
10244 | 0 | { |
10245 | 0 | InitializeDCAPVirtualIO(); |
10246 | 0 | } |
10247 | 989k | return GDALDriver::GetMetadataItem(pszName, pszDomain); |
10248 | 989k | } |
10249 | | |
10250 | | void GDALRegister_netCDF() |
10251 | | |
10252 | 2 | { |
10253 | 2 | if (!GDAL_CHECK_VERSION("netCDF driver")) |
10254 | 0 | return; |
10255 | | |
10256 | 2 | if (GDALGetDriverByName(DRIVER_NAME) != nullptr) |
10257 | 0 | return; |
10258 | | |
10259 | 2 | GDALDriver *poDriver = new GDALnetCDFDriver(); |
10260 | 2 | netCDFDriverSetCommonMetadata(poDriver); |
10261 | | |
10262 | 2 | poDriver->SetMetadataItem("NETCDF_CONVENTIONS", |
10263 | 2 | GDAL_DEFAULT_NCDF_CONVENTIONS); |
10264 | 2 | poDriver->SetMetadataItem("NETCDF_VERSION", nc_inq_libvers()); |
10265 | | |
10266 | | // Set pfns and register driver. |
10267 | 2 | poDriver->pfnOpen = netCDFDataset::Open; |
10268 | 2 | poDriver->pfnCreateCopy = netCDFDataset::CreateCopy; |
10269 | 2 | poDriver->pfnCreate = netCDFDataset::Create; |
10270 | 2 | poDriver->pfnCreateMultiDimensional = netCDFDataset::CreateMultiDimensional; |
10271 | 2 | poDriver->pfnUnloadDriver = NCDFUnloadDriver; |
10272 | | |
10273 | 2 | GetGDALDriverManager()->RegisterDriver(poDriver); |
10274 | 2 | } |
10275 | | |
10276 | | /************************************************************************/ |
10277 | | /* New functions */ |
10278 | | /************************************************************************/ |
10279 | | |
10280 | | /* Test for GDAL version string >= target */ |
10281 | | static bool NCDFIsGDALVersionGTE(const char *pszVersion, int nTarget) |
10282 | 0 | { |
10283 | | |
10284 | | // Valid strings are "GDAL 1.9dev, released 2011/01/18" and "GDAL 1.8.1 ". |
10285 | 0 | if (pszVersion == nullptr || EQUAL(pszVersion, "")) |
10286 | 0 | return false; |
10287 | 0 | else if (!STARTS_WITH_CI(pszVersion, "GDAL ")) |
10288 | 0 | return false; |
10289 | | // 2.0dev of 2011/12/29 has been later renamed as 1.10dev. |
10290 | 0 | else if (EQUAL("GDAL 2.0dev, released 2011/12/29", pszVersion)) |
10291 | 0 | return nTarget <= GDAL_COMPUTE_VERSION(1, 10, 0); |
10292 | 0 | else if (STARTS_WITH_CI(pszVersion, "GDAL 1.9dev")) |
10293 | 0 | return nTarget <= 1900; |
10294 | 0 | else if (STARTS_WITH_CI(pszVersion, "GDAL 1.8dev")) |
10295 | 0 | return nTarget <= 1800; |
10296 | | |
10297 | 0 | char **papszTokens = CSLTokenizeString2(pszVersion + 5, ".", 0); |
10298 | |
|
10299 | 0 | int nVersions[] = {0, 0, 0, 0}; |
10300 | 0 | for (int iToken = 0; papszTokens && iToken < 4 && papszTokens[iToken]; |
10301 | 0 | iToken++) |
10302 | 0 | { |
10303 | 0 | nVersions[iToken] = atoi(papszTokens[iToken]); |
10304 | 0 | if (nVersions[iToken] < 0) |
10305 | 0 | nVersions[iToken] = 0; |
10306 | 0 | else if (nVersions[iToken] > 99) |
10307 | 0 | nVersions[iToken] = 99; |
10308 | 0 | } |
10309 | |
|
10310 | 0 | int nVersion = 0; |
10311 | 0 | if (nVersions[0] > 1 || nVersions[1] >= 10) |
10312 | 0 | nVersion = |
10313 | 0 | GDAL_COMPUTE_VERSION(nVersions[0], nVersions[1], nVersions[2]); |
10314 | 0 | else |
10315 | 0 | nVersion = nVersions[0] * 1000 + nVersions[1] * 100 + |
10316 | 0 | nVersions[2] * 10 + nVersions[3]; |
10317 | |
|
10318 | 0 | CSLDestroy(papszTokens); |
10319 | 0 | return nTarget <= nVersion; |
10320 | 0 | } |
10321 | | |
10322 | | // Add Conventions, GDAL version and history. |
10323 | | static void NCDFAddGDALHistory(int fpImage, const char *pszFilename, |
10324 | | bool bWriteGDALVersion, bool bWriteGDALHistory, |
10325 | | const char *pszOldHist, |
10326 | | const char *pszFunctionName, |
10327 | | const char *pszCFVersion) |
10328 | 0 | { |
10329 | 0 | if (pszCFVersion == nullptr) |
10330 | 0 | { |
10331 | 0 | pszCFVersion = GDAL_DEFAULT_NCDF_CONVENTIONS; |
10332 | 0 | } |
10333 | 0 | int status = nc_put_att_text(fpImage, NC_GLOBAL, "Conventions", |
10334 | 0 | strlen(pszCFVersion), pszCFVersion); |
10335 | 0 | NCDF_ERR(status); |
10336 | |
|
10337 | 0 | if (bWriteGDALVersion) |
10338 | 0 | { |
10339 | 0 | const char *pszNCDF_GDAL = GDALVersionInfo("--version"); |
10340 | 0 | status = nc_put_att_text(fpImage, NC_GLOBAL, "GDAL", |
10341 | 0 | strlen(pszNCDF_GDAL), pszNCDF_GDAL); |
10342 | 0 | NCDF_ERR(status); |
10343 | 0 | } |
10344 | |
|
10345 | 0 | if (bWriteGDALHistory) |
10346 | 0 | { |
10347 | | // Add history. |
10348 | 0 | CPLString osTmp; |
10349 | | #ifdef GDAL_SET_CMD_LINE_DEFINED_TMP |
10350 | | if (!EQUAL(GDALGetCmdLine(), "")) |
10351 | | osTmp = GDALGetCmdLine(); |
10352 | | else |
10353 | | osTmp = |
10354 | | CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename); |
10355 | | #else |
10356 | 0 | osTmp = CPLSPrintf("GDAL %s( %s, ... )", pszFunctionName, pszFilename); |
10357 | 0 | #endif |
10358 | |
|
10359 | 0 | NCDFAddHistory(fpImage, osTmp.c_str(), pszOldHist); |
10360 | 0 | } |
10361 | 0 | else if (pszOldHist != nullptr) |
10362 | 0 | { |
10363 | 0 | status = nc_put_att_text(fpImage, NC_GLOBAL, "history", |
10364 | 0 | strlen(pszOldHist), pszOldHist); |
10365 | 0 | NCDF_ERR(status); |
10366 | 0 | } |
10367 | 0 | } |
10368 | | |
10369 | | // Code taken from cdo and libcdi, used for writing the history attribute. |
10370 | | |
10371 | | // void cdoDefHistory(int fileID, char *histstring) |
10372 | | static void NCDFAddHistory(int fpImage, const char *pszAddHist, |
10373 | | const char *pszOldHist) |
10374 | 0 | { |
10375 | | // Check pszOldHist - as if there was no previous history, it will be |
10376 | | // a null pointer - if so set as empty. |
10377 | 0 | if (nullptr == pszOldHist) |
10378 | 0 | { |
10379 | 0 | pszOldHist = ""; |
10380 | 0 | } |
10381 | |
|
10382 | 0 | char strtime[32]; |
10383 | 0 | strtime[0] = '\0'; |
10384 | |
|
10385 | 0 | time_t tp = time(nullptr); |
10386 | 0 | if (tp != -1) |
10387 | 0 | { |
10388 | 0 | struct tm ltime; |
10389 | 0 | VSILocalTime(&tp, <ime); |
10390 | 0 | (void)strftime(strtime, sizeof(strtime), |
10391 | 0 | "%a %b %d %H:%M:%S %Y: ", <ime); |
10392 | 0 | } |
10393 | | |
10394 | | // status = nc_get_att_text(fpImage, NC_GLOBAL, |
10395 | | // "history", pszOldHist); |
10396 | | // printf("status: %d pszOldHist: [%s]\n",status,pszOldHist); |
10397 | |
|
10398 | 0 | size_t nNewHistSize = |
10399 | 0 | strlen(pszOldHist) + strlen(strtime) + strlen(pszAddHist) + 1 + 1; |
10400 | 0 | char *pszNewHist = |
10401 | 0 | static_cast<char *>(CPLMalloc(nNewHistSize * sizeof(char))); |
10402 | |
|
10403 | 0 | strcpy(pszNewHist, strtime); |
10404 | 0 | strcat(pszNewHist, pszAddHist); |
10405 | | |
10406 | | // int disableHistory = FALSE; |
10407 | | // if( !disableHistory ) |
10408 | 0 | { |
10409 | 0 | if (!EQUAL(pszOldHist, "")) |
10410 | 0 | strcat(pszNewHist, "\n"); |
10411 | 0 | strcat(pszNewHist, pszOldHist); |
10412 | 0 | } |
10413 | |
|
10414 | 0 | const int status = nc_put_att_text(fpImage, NC_GLOBAL, "history", |
10415 | 0 | strlen(pszNewHist), pszNewHist); |
10416 | 0 | NCDF_ERR(status); |
10417 | |
|
10418 | 0 | CPLFree(pszNewHist); |
10419 | 0 | } |
10420 | | |
10421 | | static CPLErr NCDFSafeStrcat(char **ppszDest, const char *pszSrc, |
10422 | | size_t *nDestSize) |
10423 | 1.79k | { |
10424 | | /* Reallocate the data string until the content fits */ |
10425 | 1.79k | while (*nDestSize < (strlen(*ppszDest) + strlen(pszSrc) + 1)) |
10426 | 0 | { |
10427 | 0 | (*nDestSize) *= 2; |
10428 | 0 | *ppszDest = static_cast<char *>( |
10429 | 0 | CPLRealloc(reinterpret_cast<void *>(*ppszDest), *nDestSize)); |
10430 | | #ifdef NCDF_DEBUG |
10431 | | CPLDebug("GDAL_netCDF", "NCDFSafeStrcat() resized str from %ld to %ld", |
10432 | | (*nDestSize) / 2, *nDestSize); |
10433 | | #endif |
10434 | 0 | } |
10435 | 1.79k | strcat(*ppszDest, pszSrc); |
10436 | | |
10437 | 1.79k | return CE_None; |
10438 | 1.79k | } |
10439 | | |
10440 | | /* helper function for NCDFGetAttr() */ |
10441 | | /* if pdfValue != nullptr, sets *pdfValue to first value returned */ |
10442 | | /* if ppszValue != nullptr, sets *ppszValue with all attribute values */ |
10443 | | /* *ppszValue is the responsibility of the caller and must be freed */ |
10444 | | static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, |
10445 | | double *pdfValue, char **ppszValue) |
10446 | 17.1k | { |
10447 | 17.1k | nc_type nAttrType = NC_NAT; |
10448 | 17.1k | size_t nAttrLen = 0; |
10449 | | |
10450 | 17.1k | if (ppszValue) |
10451 | 17.1k | *ppszValue = nullptr; |
10452 | | |
10453 | 17.1k | int status = nc_inq_att(nCdfId, nVarId, pszAttrName, &nAttrType, &nAttrLen); |
10454 | 17.1k | if (status != NC_NOERR) |
10455 | 14.8k | return CE_Failure; |
10456 | | |
10457 | | #ifdef NCDF_DEBUG |
10458 | | CPLDebug("GDAL_netCDF", "NCDFGetAttr1(%s) len=%ld type=%d", pszAttrName, |
10459 | | nAttrLen, nAttrType); |
10460 | | #endif |
10461 | 2.30k | if (nAttrLen == 0 && nAttrType != NC_CHAR) |
10462 | 0 | return CE_Failure; |
10463 | | |
10464 | | /* Allocate guaranteed minimum size (use 10 or 20 if not a string) */ |
10465 | 2.30k | size_t nAttrValueSize = nAttrLen + 1; |
10466 | 2.30k | if (nAttrType != NC_CHAR && nAttrValueSize < 10) |
10467 | 1.79k | nAttrValueSize = 10; |
10468 | 2.30k | if (nAttrType == NC_DOUBLE && nAttrValueSize < 20) |
10469 | 138 | nAttrValueSize = 20; |
10470 | 2.30k | if (nAttrType == NC_INT64 && nAttrValueSize < 20) |
10471 | 0 | nAttrValueSize = 22; |
10472 | 2.30k | char *pszAttrValue = |
10473 | 2.30k | static_cast<char *>(CPLCalloc(nAttrValueSize, sizeof(char))); |
10474 | 2.30k | *pszAttrValue = '\0'; |
10475 | | |
10476 | 2.30k | if (nAttrLen > 1 && nAttrType != NC_CHAR) |
10477 | 0 | NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize); |
10478 | | |
10479 | 2.30k | double dfValue = 0.0; |
10480 | 2.30k | size_t m = 0; |
10481 | 2.30k | char szTemp[256]; |
10482 | 2.30k | bool bSetDoubleFromStr = false; |
10483 | | |
10484 | 2.30k | switch (nAttrType) |
10485 | 2.30k | { |
10486 | 509 | case NC_CHAR: |
10487 | 509 | CPL_IGNORE_RET_VAL( |
10488 | 509 | nc_get_att_text(nCdfId, nVarId, pszAttrName, pszAttrValue)); |
10489 | 509 | pszAttrValue[nAttrLen] = '\0'; |
10490 | 509 | bSetDoubleFromStr = true; |
10491 | 509 | dfValue = 0.0; |
10492 | 509 | break; |
10493 | 1 | case NC_BYTE: |
10494 | 1 | { |
10495 | 1 | signed char *pscTemp = static_cast<signed char *>( |
10496 | 1 | CPLCalloc(nAttrLen, sizeof(signed char))); |
10497 | 1 | nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp); |
10498 | 1 | dfValue = static_cast<double>(pscTemp[0]); |
10499 | 1 | if (nAttrLen > 1) |
10500 | 0 | { |
10501 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10502 | 0 | { |
10503 | 0 | snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]); |
10504 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10505 | 0 | } |
10506 | 0 | } |
10507 | 1 | snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]); |
10508 | 1 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10509 | 1 | CPLFree(pscTemp); |
10510 | 1 | break; |
10511 | 0 | } |
10512 | 0 | case NC_SHORT: |
10513 | 0 | { |
10514 | 0 | short *psTemp = |
10515 | 0 | static_cast<short *>(CPLCalloc(nAttrLen, sizeof(short))); |
10516 | 0 | nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp); |
10517 | 0 | dfValue = static_cast<double>(psTemp[0]); |
10518 | 0 | if (nAttrLen > 1) |
10519 | 0 | { |
10520 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10521 | 0 | { |
10522 | 0 | snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]); |
10523 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10524 | 0 | } |
10525 | 0 | } |
10526 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]); |
10527 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10528 | 0 | CPLFree(psTemp); |
10529 | 0 | break; |
10530 | 0 | } |
10531 | 1.65k | case NC_INT: |
10532 | 1.65k | { |
10533 | 1.65k | int *pnTemp = static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int))); |
10534 | 1.65k | nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp); |
10535 | 1.65k | dfValue = static_cast<double>(pnTemp[0]); |
10536 | 1.65k | if (nAttrLen > 1) |
10537 | 0 | { |
10538 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10539 | 0 | { |
10540 | 0 | snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]); |
10541 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10542 | 0 | } |
10543 | 0 | } |
10544 | 1.65k | snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]); |
10545 | 1.65k | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10546 | 1.65k | CPLFree(pnTemp); |
10547 | 1.65k | break; |
10548 | 0 | } |
10549 | 0 | case NC_FLOAT: |
10550 | 0 | { |
10551 | 0 | float *pfTemp = |
10552 | 0 | static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float))); |
10553 | 0 | nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp); |
10554 | 0 | dfValue = static_cast<double>(pfTemp[0]); |
10555 | 0 | if (nAttrLen > 1) |
10556 | 0 | { |
10557 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10558 | 0 | { |
10559 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]); |
10560 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10561 | 0 | } |
10562 | 0 | } |
10563 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]); |
10564 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10565 | 0 | CPLFree(pfTemp); |
10566 | 0 | break; |
10567 | 0 | } |
10568 | 138 | case NC_DOUBLE: |
10569 | 138 | { |
10570 | 138 | double *pdfTemp = |
10571 | 138 | static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double))); |
10572 | 138 | nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp); |
10573 | 138 | dfValue = pdfTemp[0]; |
10574 | 138 | if (nAttrLen > 1) |
10575 | 0 | { |
10576 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10577 | 0 | { |
10578 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]); |
10579 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10580 | 0 | } |
10581 | 0 | } |
10582 | 138 | CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]); |
10583 | 138 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10584 | 138 | CPLFree(pdfTemp); |
10585 | 138 | break; |
10586 | 0 | } |
10587 | 0 | case NC_STRING: |
10588 | 0 | { |
10589 | 0 | char **ppszTemp = |
10590 | 0 | static_cast<char **>(CPLCalloc(nAttrLen, sizeof(char *))); |
10591 | 0 | nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp); |
10592 | 0 | bSetDoubleFromStr = true; |
10593 | 0 | dfValue = 0.0; |
10594 | 0 | if (nAttrLen > 1) |
10595 | 0 | { |
10596 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10597 | 0 | { |
10598 | 0 | NCDFSafeStrcat(&pszAttrValue, |
10599 | 0 | ppszTemp[m] ? ppszTemp[m] : "{NULL}", |
10600 | 0 | &nAttrValueSize); |
10601 | 0 | NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize); |
10602 | 0 | } |
10603 | 0 | } |
10604 | 0 | NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}", |
10605 | 0 | &nAttrValueSize); |
10606 | 0 | nc_free_string(nAttrLen, ppszTemp); |
10607 | 0 | CPLFree(ppszTemp); |
10608 | 0 | break; |
10609 | 0 | } |
10610 | 0 | case NC_UBYTE: |
10611 | 0 | { |
10612 | 0 | unsigned char *pucTemp = static_cast<unsigned char *>( |
10613 | 0 | CPLCalloc(nAttrLen, sizeof(unsigned char))); |
10614 | 0 | nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp); |
10615 | 0 | dfValue = static_cast<double>(pucTemp[0]); |
10616 | 0 | if (nAttrLen > 1) |
10617 | 0 | { |
10618 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10619 | 0 | { |
10620 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]); |
10621 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10622 | 0 | } |
10623 | 0 | } |
10624 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]); |
10625 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10626 | 0 | CPLFree(pucTemp); |
10627 | 0 | break; |
10628 | 0 | } |
10629 | 0 | case NC_USHORT: |
10630 | 0 | { |
10631 | 0 | unsigned short *pusTemp; |
10632 | 0 | pusTemp = static_cast<unsigned short *>( |
10633 | 0 | CPLCalloc(nAttrLen, sizeof(unsigned short))); |
10634 | 0 | nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp); |
10635 | 0 | dfValue = static_cast<double>(pusTemp[0]); |
10636 | 0 | if (nAttrLen > 1) |
10637 | 0 | { |
10638 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10639 | 0 | { |
10640 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]); |
10641 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10642 | 0 | } |
10643 | 0 | } |
10644 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]); |
10645 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10646 | 0 | CPLFree(pusTemp); |
10647 | 0 | break; |
10648 | 0 | } |
10649 | 0 | case NC_UINT: |
10650 | 0 | { |
10651 | 0 | unsigned int *punTemp = |
10652 | 0 | static_cast<unsigned int *>(CPLCalloc(nAttrLen, sizeof(int))); |
10653 | 0 | nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp); |
10654 | 0 | dfValue = static_cast<double>(punTemp[0]); |
10655 | 0 | if (nAttrLen > 1) |
10656 | 0 | { |
10657 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10658 | 0 | { |
10659 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]); |
10660 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10661 | 0 | } |
10662 | 0 | } |
10663 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]); |
10664 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10665 | 0 | CPLFree(punTemp); |
10666 | 0 | break; |
10667 | 0 | } |
10668 | 0 | case NC_INT64: |
10669 | 0 | { |
10670 | 0 | GIntBig *panTemp = |
10671 | 0 | static_cast<GIntBig *>(CPLCalloc(nAttrLen, sizeof(GIntBig))); |
10672 | 0 | nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp); |
10673 | 0 | dfValue = static_cast<double>(panTemp[0]); |
10674 | 0 | if (nAttrLen > 1) |
10675 | 0 | { |
10676 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10677 | 0 | { |
10678 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", |
10679 | 0 | panTemp[m]); |
10680 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10681 | 0 | } |
10682 | 0 | } |
10683 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]); |
10684 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10685 | 0 | CPLFree(panTemp); |
10686 | 0 | break; |
10687 | 0 | } |
10688 | 0 | case NC_UINT64: |
10689 | 0 | { |
10690 | 0 | GUIntBig *panTemp = |
10691 | 0 | static_cast<GUIntBig *>(CPLCalloc(nAttrLen, sizeof(GUIntBig))); |
10692 | 0 | nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp); |
10693 | 0 | dfValue = static_cast<double>(panTemp[0]); |
10694 | 0 | if (nAttrLen > 1) |
10695 | 0 | { |
10696 | 0 | for (m = 0; m < nAttrLen - 1; m++) |
10697 | 0 | { |
10698 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", |
10699 | 0 | panTemp[m]); |
10700 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10701 | 0 | } |
10702 | 0 | } |
10703 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]); |
10704 | 0 | NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); |
10705 | 0 | CPLFree(panTemp); |
10706 | 0 | break; |
10707 | 0 | } |
10708 | 0 | default: |
10709 | 0 | CPLDebug("GDAL_netCDF", |
10710 | 0 | "NCDFGetAttr unsupported type %d for attribute %s", |
10711 | 0 | nAttrType, pszAttrName); |
10712 | 0 | break; |
10713 | 2.30k | } |
10714 | | |
10715 | 2.30k | if (nAttrLen > 1 && nAttrType != NC_CHAR) |
10716 | 0 | NCDFSafeStrcat(&pszAttrValue, "}", &nAttrValueSize); |
10717 | | |
10718 | 2.30k | if (bSetDoubleFromStr) |
10719 | 509 | { |
10720 | 509 | if (CPLGetValueType(pszAttrValue) == CPL_VALUE_STRING) |
10721 | 509 | { |
10722 | 509 | if (ppszValue == nullptr && pdfValue != nullptr) |
10723 | 0 | { |
10724 | 0 | CPLFree(pszAttrValue); |
10725 | 0 | return CE_Failure; |
10726 | 0 | } |
10727 | 509 | } |
10728 | 509 | dfValue = CPLAtof(pszAttrValue); |
10729 | 509 | } |
10730 | | |
10731 | | /* set return values */ |
10732 | 2.30k | if (ppszValue) |
10733 | 2.30k | *ppszValue = pszAttrValue; |
10734 | 0 | else |
10735 | 0 | CPLFree(pszAttrValue); |
10736 | | |
10737 | 2.30k | if (pdfValue) |
10738 | 0 | *pdfValue = dfValue; |
10739 | | |
10740 | 2.30k | return CE_None; |
10741 | 2.30k | } |
10742 | | |
10743 | | /* sets pdfValue to first value found */ |
10744 | | CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName, |
10745 | | double *pdfValue) |
10746 | 4 | { |
10747 | 4 | return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, pdfValue, nullptr); |
10748 | 4 | } |
10749 | | |
10750 | | /* pszValue is the responsibility of the caller and must be freed */ |
10751 | | CPLErr NCDFGetAttr(int nCdfId, int nVarId, const char *pszAttrName, |
10752 | | char **pszValue) |
10753 | 17.1k | { |
10754 | 17.1k | return NCDFGetAttr1(nCdfId, nVarId, pszAttrName, nullptr, pszValue); |
10755 | 17.1k | } |
10756 | | |
10757 | | /* By default write NC_CHAR, but detect for int/float/double and */ |
10758 | | /* NC4 string arrays */ |
10759 | | static CPLErr NCDFPutAttr(int nCdfId, int nVarId, const char *pszAttrName, |
10760 | | const char *pszValue) |
10761 | 0 | { |
10762 | 0 | int status = 0; |
10763 | 0 | char *pszTemp = nullptr; |
10764 | | |
10765 | | /* get the attribute values as tokens */ |
10766 | 0 | char **papszValues = NCDFTokenizeArray(pszValue); |
10767 | 0 | if (papszValues == nullptr) |
10768 | 0 | return CE_Failure; |
10769 | | |
10770 | 0 | size_t nAttrLen = CSLCount(papszValues); |
10771 | | |
10772 | | /* first detect type */ |
10773 | 0 | nc_type nAttrType = NC_CHAR; |
10774 | 0 | nc_type nTmpAttrType = NC_CHAR; |
10775 | 0 | for (size_t i = 0; i < nAttrLen; i++) |
10776 | 0 | { |
10777 | 0 | nTmpAttrType = NC_CHAR; |
10778 | 0 | bool bFoundType = false; |
10779 | 0 | errno = 0; |
10780 | 0 | int nValue = static_cast<int>(strtol(papszValues[i], &pszTemp, 10)); |
10781 | | /* test for int */ |
10782 | | /* TODO test for Byte and short - can this be done safely? */ |
10783 | 0 | if (errno == 0 && papszValues[i] != pszTemp && *pszTemp == 0) |
10784 | 0 | { |
10785 | 0 | char szTemp[256]; |
10786 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%d", nValue); |
10787 | 0 | if (EQUAL(szTemp, papszValues[i])) |
10788 | 0 | { |
10789 | 0 | bFoundType = true; |
10790 | 0 | nTmpAttrType = NC_INT; |
10791 | 0 | } |
10792 | 0 | else |
10793 | 0 | { |
10794 | 0 | unsigned int unValue = static_cast<unsigned int>( |
10795 | 0 | strtoul(papszValues[i], &pszTemp, 10)); |
10796 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", unValue); |
10797 | 0 | if (EQUAL(szTemp, papszValues[i])) |
10798 | 0 | { |
10799 | 0 | bFoundType = true; |
10800 | 0 | nTmpAttrType = NC_UINT; |
10801 | 0 | } |
10802 | 0 | } |
10803 | 0 | } |
10804 | 0 | if (!bFoundType) |
10805 | 0 | { |
10806 | | /* test for double */ |
10807 | 0 | errno = 0; |
10808 | 0 | double dfValue = CPLStrtod(papszValues[i], &pszTemp); |
10809 | 0 | if ((errno == 0) && (papszValues[i] != pszTemp) && (*pszTemp == 0)) |
10810 | 0 | { |
10811 | | // Test for float instead of double. |
10812 | | // strtof() is C89, which is not available in MSVC. |
10813 | | // See if we loose precision if we cast to float and write to |
10814 | | // char*. |
10815 | 0 | float fValue = float(dfValue); |
10816 | 0 | char szTemp[256]; |
10817 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", fValue); |
10818 | 0 | if (EQUAL(szTemp, papszValues[i])) |
10819 | 0 | nTmpAttrType = NC_FLOAT; |
10820 | 0 | else |
10821 | 0 | nTmpAttrType = NC_DOUBLE; |
10822 | 0 | } |
10823 | 0 | } |
10824 | 0 | if ((nTmpAttrType <= NC_DOUBLE && nAttrType <= NC_DOUBLE && |
10825 | 0 | nTmpAttrType > nAttrType) || |
10826 | 0 | (nTmpAttrType == NC_UINT && nAttrType < NC_FLOAT) || |
10827 | 0 | (nTmpAttrType >= NC_FLOAT && nAttrType == NC_UINT)) |
10828 | 0 | nAttrType = nTmpAttrType; |
10829 | 0 | } |
10830 | |
|
10831 | | #ifdef DEBUG |
10832 | | if (EQUAL(pszAttrName, "DEBUG_EMPTY_DOUBLE_ATTR")) |
10833 | | { |
10834 | | nAttrType = NC_DOUBLE; |
10835 | | nAttrLen = 0; |
10836 | | } |
10837 | | #endif |
10838 | | |
10839 | | /* now write the data */ |
10840 | 0 | if (nAttrType == NC_CHAR) |
10841 | 0 | { |
10842 | 0 | int nTmpFormat = 0; |
10843 | 0 | if (nAttrLen > 1) |
10844 | 0 | { |
10845 | 0 | status = nc_inq_format(nCdfId, &nTmpFormat); |
10846 | 0 | NCDF_ERR(status); |
10847 | 0 | } |
10848 | 0 | if (nAttrLen > 1 && nTmpFormat == NCDF_FORMAT_NC4) |
10849 | 0 | status = nc_put_att_string(nCdfId, nVarId, pszAttrName, nAttrLen, |
10850 | 0 | const_cast<const char **>(papszValues)); |
10851 | 0 | else |
10852 | 0 | status = nc_put_att_text(nCdfId, nVarId, pszAttrName, |
10853 | 0 | strlen(pszValue), pszValue); |
10854 | 0 | NCDF_ERR(status); |
10855 | 0 | } |
10856 | 0 | else |
10857 | 0 | { |
10858 | 0 | switch (nAttrType) |
10859 | 0 | { |
10860 | 0 | case NC_INT: |
10861 | 0 | { |
10862 | 0 | int *pnTemp = |
10863 | 0 | static_cast<int *>(CPLCalloc(nAttrLen, sizeof(int))); |
10864 | 0 | for (size_t i = 0; i < nAttrLen; i++) |
10865 | 0 | { |
10866 | 0 | pnTemp[i] = |
10867 | 0 | static_cast<int>(strtol(papszValues[i], &pszTemp, 10)); |
10868 | 0 | } |
10869 | 0 | status = nc_put_att_int(nCdfId, nVarId, pszAttrName, NC_INT, |
10870 | 0 | nAttrLen, pnTemp); |
10871 | 0 | NCDF_ERR(status); |
10872 | 0 | CPLFree(pnTemp); |
10873 | 0 | break; |
10874 | 0 | } |
10875 | 0 | case NC_UINT: |
10876 | 0 | { |
10877 | 0 | unsigned int *punTemp = static_cast<unsigned int *>( |
10878 | 0 | CPLCalloc(nAttrLen, sizeof(unsigned int))); |
10879 | 0 | for (size_t i = 0; i < nAttrLen; i++) |
10880 | 0 | { |
10881 | 0 | punTemp[i] = static_cast<unsigned int>( |
10882 | 0 | strtol(papszValues[i], &pszTemp, 10)); |
10883 | 0 | } |
10884 | 0 | status = nc_put_att_uint(nCdfId, nVarId, pszAttrName, NC_UINT, |
10885 | 0 | nAttrLen, punTemp); |
10886 | 0 | NCDF_ERR(status); |
10887 | 0 | CPLFree(punTemp); |
10888 | 0 | break; |
10889 | 0 | } |
10890 | 0 | case NC_FLOAT: |
10891 | 0 | { |
10892 | 0 | float *pfTemp = |
10893 | 0 | static_cast<float *>(CPLCalloc(nAttrLen, sizeof(float))); |
10894 | 0 | for (size_t i = 0; i < nAttrLen; i++) |
10895 | 0 | { |
10896 | 0 | pfTemp[i] = |
10897 | 0 | static_cast<float>(CPLStrtod(papszValues[i], &pszTemp)); |
10898 | 0 | } |
10899 | 0 | status = nc_put_att_float(nCdfId, nVarId, pszAttrName, NC_FLOAT, |
10900 | 0 | nAttrLen, pfTemp); |
10901 | 0 | NCDF_ERR(status); |
10902 | 0 | CPLFree(pfTemp); |
10903 | 0 | break; |
10904 | 0 | } |
10905 | 0 | case NC_DOUBLE: |
10906 | 0 | { |
10907 | 0 | double *pdfTemp = |
10908 | 0 | static_cast<double *>(CPLCalloc(nAttrLen, sizeof(double))); |
10909 | 0 | for (size_t i = 0; i < nAttrLen; i++) |
10910 | 0 | { |
10911 | 0 | pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp); |
10912 | 0 | } |
10913 | 0 | status = nc_put_att_double(nCdfId, nVarId, pszAttrName, |
10914 | 0 | NC_DOUBLE, nAttrLen, pdfTemp); |
10915 | 0 | NCDF_ERR(status); |
10916 | 0 | CPLFree(pdfTemp); |
10917 | 0 | break; |
10918 | 0 | } |
10919 | 0 | default: |
10920 | 0 | if (papszValues) |
10921 | 0 | CSLDestroy(papszValues); |
10922 | 0 | return CE_Failure; |
10923 | 0 | break; |
10924 | 0 | } |
10925 | 0 | } |
10926 | | |
10927 | 0 | if (papszValues) |
10928 | 0 | CSLDestroy(papszValues); |
10929 | |
|
10930 | 0 | return CE_None; |
10931 | 0 | } |
10932 | | |
10933 | | static CPLErr NCDFGet1DVar(int nCdfId, int nVarId, char **pszValue) |
10934 | 0 | { |
10935 | | /* get var information */ |
10936 | 0 | int nVarDimId = -1; |
10937 | 0 | int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId); |
10938 | 0 | if (status != NC_NOERR || nVarDimId != 1) |
10939 | 0 | return CE_Failure; |
10940 | | |
10941 | 0 | status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId); |
10942 | 0 | if (status != NC_NOERR) |
10943 | 0 | return CE_Failure; |
10944 | | |
10945 | 0 | nc_type nVarType = NC_NAT; |
10946 | 0 | status = nc_inq_vartype(nCdfId, nVarId, &nVarType); |
10947 | 0 | if (status != NC_NOERR) |
10948 | 0 | return CE_Failure; |
10949 | | |
10950 | 0 | size_t nVarLen = 0; |
10951 | 0 | status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen); |
10952 | 0 | if (status != NC_NOERR) |
10953 | 0 | return CE_Failure; |
10954 | | |
10955 | 0 | size_t start[1] = {0}; |
10956 | 0 | size_t count[1] = {nVarLen}; |
10957 | | |
10958 | | /* Allocate guaranteed minimum size */ |
10959 | 0 | size_t nVarValueSize = NCDF_MAX_STR_LEN; |
10960 | 0 | char *pszVarValue = |
10961 | 0 | static_cast<char *>(CPLCalloc(nVarValueSize, sizeof(char))); |
10962 | 0 | *pszVarValue = '\0'; |
10963 | |
|
10964 | 0 | if (nVarLen == 0) |
10965 | 0 | { |
10966 | | /* set return values */ |
10967 | 0 | *pszValue = pszVarValue; |
10968 | |
|
10969 | 0 | return CE_None; |
10970 | 0 | } |
10971 | | |
10972 | 0 | if (nVarLen > 1 && nVarType != NC_CHAR) |
10973 | 0 | NCDFSafeStrcat(&pszVarValue, "{", &nVarValueSize); |
10974 | |
|
10975 | 0 | switch (nVarType) |
10976 | 0 | { |
10977 | 0 | case NC_CHAR: |
10978 | 0 | nc_get_vara_text(nCdfId, nVarId, start, count, pszVarValue); |
10979 | 0 | pszVarValue[nVarLen] = '\0'; |
10980 | 0 | break; |
10981 | 0 | case NC_BYTE: |
10982 | 0 | { |
10983 | 0 | signed char *pscTemp = static_cast<signed char *>( |
10984 | 0 | CPLCalloc(nVarLen, sizeof(signed char))); |
10985 | 0 | nc_get_vara_schar(nCdfId, nVarId, start, count, pscTemp); |
10986 | 0 | char szTemp[256]; |
10987 | 0 | size_t m = 0; |
10988 | 0 | for (; m < nVarLen - 1; m++) |
10989 | 0 | { |
10990 | 0 | snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]); |
10991 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
10992 | 0 | } |
10993 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]); |
10994 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
10995 | 0 | CPLFree(pscTemp); |
10996 | 0 | break; |
10997 | 0 | } |
10998 | 0 | case NC_SHORT: |
10999 | 0 | { |
11000 | 0 | short *psTemp = |
11001 | 0 | static_cast<short *>(CPLCalloc(nVarLen, sizeof(short))); |
11002 | 0 | nc_get_vara_short(nCdfId, nVarId, start, count, psTemp); |
11003 | 0 | char szTemp[256]; |
11004 | 0 | size_t m = 0; |
11005 | 0 | for (; m < nVarLen - 1; m++) |
11006 | 0 | { |
11007 | 0 | snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]); |
11008 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11009 | 0 | } |
11010 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]); |
11011 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11012 | 0 | CPLFree(psTemp); |
11013 | 0 | break; |
11014 | 0 | } |
11015 | 0 | case NC_INT: |
11016 | 0 | { |
11017 | 0 | int *pnTemp = static_cast<int *>(CPLCalloc(nVarLen, sizeof(int))); |
11018 | 0 | nc_get_vara_int(nCdfId, nVarId, start, count, pnTemp); |
11019 | 0 | char szTemp[256]; |
11020 | 0 | size_t m = 0; |
11021 | 0 | for (; m < nVarLen - 1; m++) |
11022 | 0 | { |
11023 | 0 | snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]); |
11024 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11025 | 0 | } |
11026 | 0 | snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]); |
11027 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11028 | 0 | CPLFree(pnTemp); |
11029 | 0 | break; |
11030 | 0 | } |
11031 | 0 | case NC_FLOAT: |
11032 | 0 | { |
11033 | 0 | float *pfTemp = |
11034 | 0 | static_cast<float *>(CPLCalloc(nVarLen, sizeof(float))); |
11035 | 0 | nc_get_vara_float(nCdfId, nVarId, start, count, pfTemp); |
11036 | 0 | char szTemp[256]; |
11037 | 0 | size_t m = 0; |
11038 | 0 | for (; m < nVarLen - 1; m++) |
11039 | 0 | { |
11040 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]); |
11041 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11042 | 0 | } |
11043 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]); |
11044 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11045 | 0 | CPLFree(pfTemp); |
11046 | 0 | break; |
11047 | 0 | } |
11048 | 0 | case NC_DOUBLE: |
11049 | 0 | { |
11050 | 0 | double *pdfTemp = |
11051 | 0 | static_cast<double *>(CPLCalloc(nVarLen, sizeof(double))); |
11052 | 0 | nc_get_vara_double(nCdfId, nVarId, start, count, pdfTemp); |
11053 | 0 | char szTemp[256]; |
11054 | 0 | size_t m = 0; |
11055 | 0 | for (; m < nVarLen - 1; m++) |
11056 | 0 | { |
11057 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]); |
11058 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11059 | 0 | } |
11060 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]); |
11061 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11062 | 0 | CPLFree(pdfTemp); |
11063 | 0 | break; |
11064 | 0 | } |
11065 | 0 | case NC_STRING: |
11066 | 0 | { |
11067 | 0 | char **ppszTemp = |
11068 | 0 | static_cast<char **>(CPLCalloc(nVarLen, sizeof(char *))); |
11069 | 0 | nc_get_vara_string(nCdfId, nVarId, start, count, ppszTemp); |
11070 | 0 | size_t m = 0; |
11071 | 0 | for (; m < nVarLen - 1; m++) |
11072 | 0 | { |
11073 | 0 | NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize); |
11074 | 0 | NCDFSafeStrcat(&pszVarValue, ",", &nVarValueSize); |
11075 | 0 | } |
11076 | 0 | NCDFSafeStrcat(&pszVarValue, ppszTemp[m], &nVarValueSize); |
11077 | 0 | nc_free_string(nVarLen, ppszTemp); |
11078 | 0 | CPLFree(ppszTemp); |
11079 | 0 | break; |
11080 | 0 | } |
11081 | 0 | case NC_UBYTE: |
11082 | 0 | { |
11083 | 0 | unsigned char *pucTemp; |
11084 | 0 | pucTemp = static_cast<unsigned char *>( |
11085 | 0 | CPLCalloc(nVarLen, sizeof(unsigned char))); |
11086 | 0 | nc_get_vara_uchar(nCdfId, nVarId, start, count, pucTemp); |
11087 | 0 | char szTemp[256]; |
11088 | 0 | size_t m = 0; |
11089 | 0 | for (; m < nVarLen - 1; m++) |
11090 | 0 | { |
11091 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]); |
11092 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11093 | 0 | } |
11094 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]); |
11095 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11096 | 0 | CPLFree(pucTemp); |
11097 | 0 | break; |
11098 | 0 | } |
11099 | 0 | case NC_USHORT: |
11100 | 0 | { |
11101 | 0 | unsigned short *pusTemp; |
11102 | 0 | pusTemp = static_cast<unsigned short *>( |
11103 | 0 | CPLCalloc(nVarLen, sizeof(unsigned short))); |
11104 | 0 | nc_get_vara_ushort(nCdfId, nVarId, start, count, pusTemp); |
11105 | 0 | char szTemp[256]; |
11106 | 0 | size_t m = 0; |
11107 | 0 | for (; m < nVarLen - 1; m++) |
11108 | 0 | { |
11109 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]); |
11110 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11111 | 0 | } |
11112 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]); |
11113 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11114 | 0 | CPLFree(pusTemp); |
11115 | 0 | break; |
11116 | 0 | } |
11117 | 0 | case NC_UINT: |
11118 | 0 | { |
11119 | 0 | unsigned int *punTemp; |
11120 | 0 | punTemp = static_cast<unsigned int *>( |
11121 | 0 | CPLCalloc(nVarLen, sizeof(unsigned int))); |
11122 | 0 | nc_get_vara_uint(nCdfId, nVarId, start, count, punTemp); |
11123 | 0 | char szTemp[256]; |
11124 | 0 | size_t m = 0; |
11125 | 0 | for (; m < nVarLen - 1; m++) |
11126 | 0 | { |
11127 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]); |
11128 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11129 | 0 | } |
11130 | 0 | CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]); |
11131 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11132 | 0 | CPLFree(punTemp); |
11133 | 0 | break; |
11134 | 0 | } |
11135 | 0 | case NC_INT64: |
11136 | 0 | { |
11137 | 0 | long long *pnTemp = |
11138 | 0 | static_cast<long long *>(CPLCalloc(nVarLen, sizeof(long long))); |
11139 | 0 | nc_get_vara_longlong(nCdfId, nVarId, start, count, pnTemp); |
11140 | 0 | char szTemp[256]; |
11141 | 0 | size_t m = 0; |
11142 | 0 | for (; m < nVarLen - 1; m++) |
11143 | 0 | { |
11144 | 0 | snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", pnTemp[m]); |
11145 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11146 | 0 | } |
11147 | 0 | snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, pnTemp[m]); |
11148 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11149 | 0 | CPLFree(pnTemp); |
11150 | 0 | break; |
11151 | 0 | } |
11152 | 0 | case NC_UINT64: |
11153 | 0 | { |
11154 | 0 | unsigned long long *pnTemp = static_cast<unsigned long long *>( |
11155 | 0 | CPLCalloc(nVarLen, sizeof(unsigned long long))); |
11156 | 0 | nc_get_vara_ulonglong(nCdfId, nVarId, start, count, pnTemp); |
11157 | 0 | char szTemp[256]; |
11158 | 0 | size_t m = 0; |
11159 | 0 | for (; m < nVarLen - 1; m++) |
11160 | 0 | { |
11161 | 0 | snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", pnTemp[m]); |
11162 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11163 | 0 | } |
11164 | 0 | snprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, pnTemp[m]); |
11165 | 0 | NCDFSafeStrcat(&pszVarValue, szTemp, &nVarValueSize); |
11166 | 0 | CPLFree(pnTemp); |
11167 | 0 | break; |
11168 | 0 | } |
11169 | 0 | default: |
11170 | 0 | CPLDebug("GDAL_netCDF", "NCDFGetVar1D unsupported type %d", |
11171 | 0 | nVarType); |
11172 | 0 | CPLFree(pszVarValue); |
11173 | 0 | pszVarValue = nullptr; |
11174 | 0 | break; |
11175 | 0 | } |
11176 | | |
11177 | 0 | if (pszVarValue != nullptr && nVarLen > 1 && nVarType != NC_CHAR) |
11178 | 0 | NCDFSafeStrcat(&pszVarValue, "}", &nVarValueSize); |
11179 | | |
11180 | | /* set return values */ |
11181 | 0 | *pszValue = pszVarValue; |
11182 | |
|
11183 | 0 | return CE_None; |
11184 | 0 | } |
11185 | | |
11186 | | static CPLErr NCDFPut1DVar(int nCdfId, int nVarId, const char *pszValue) |
11187 | 0 | { |
11188 | 0 | if (EQUAL(pszValue, "")) |
11189 | 0 | return CE_Failure; |
11190 | | |
11191 | | /* get var information */ |
11192 | 0 | int nVarDimId = -1; |
11193 | 0 | int status = nc_inq_varndims(nCdfId, nVarId, &nVarDimId); |
11194 | 0 | if (status != NC_NOERR || nVarDimId != 1) |
11195 | 0 | return CE_Failure; |
11196 | | |
11197 | 0 | status = nc_inq_vardimid(nCdfId, nVarId, &nVarDimId); |
11198 | 0 | if (status != NC_NOERR) |
11199 | 0 | return CE_Failure; |
11200 | | |
11201 | 0 | nc_type nVarType = NC_CHAR; |
11202 | 0 | status = nc_inq_vartype(nCdfId, nVarId, &nVarType); |
11203 | 0 | if (status != NC_NOERR) |
11204 | 0 | return CE_Failure; |
11205 | | |
11206 | 0 | size_t nVarLen = 0; |
11207 | 0 | status = nc_inq_dimlen(nCdfId, nVarDimId, &nVarLen); |
11208 | 0 | if (status != NC_NOERR) |
11209 | 0 | return CE_Failure; |
11210 | | |
11211 | 0 | size_t start[1] = {0}; |
11212 | 0 | size_t count[1] = {nVarLen}; |
11213 | | |
11214 | | /* get the values as tokens */ |
11215 | 0 | char **papszValues = NCDFTokenizeArray(pszValue); |
11216 | 0 | if (papszValues == nullptr) |
11217 | 0 | return CE_Failure; |
11218 | | |
11219 | 0 | nVarLen = CSLCount(papszValues); |
11220 | | |
11221 | | /* now write the data */ |
11222 | 0 | if (nVarType == NC_CHAR) |
11223 | 0 | { |
11224 | 0 | status = nc_put_vara_text(nCdfId, nVarId, start, count, pszValue); |
11225 | 0 | NCDF_ERR(status); |
11226 | 0 | } |
11227 | 0 | else |
11228 | 0 | { |
11229 | 0 | switch (nVarType) |
11230 | 0 | { |
11231 | 0 | case NC_BYTE: |
11232 | 0 | { |
11233 | 0 | signed char *pscTemp = static_cast<signed char *>( |
11234 | 0 | CPLCalloc(nVarLen, sizeof(signed char))); |
11235 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11236 | 0 | { |
11237 | 0 | char *pszTemp = nullptr; |
11238 | 0 | pscTemp[i] = static_cast<signed char>( |
11239 | 0 | strtol(papszValues[i], &pszTemp, 10)); |
11240 | 0 | } |
11241 | 0 | status = |
11242 | 0 | nc_put_vara_schar(nCdfId, nVarId, start, count, pscTemp); |
11243 | 0 | NCDF_ERR(status); |
11244 | 0 | CPLFree(pscTemp); |
11245 | 0 | break; |
11246 | 0 | } |
11247 | 0 | case NC_SHORT: |
11248 | 0 | { |
11249 | 0 | short *psTemp = |
11250 | 0 | static_cast<short *>(CPLCalloc(nVarLen, sizeof(short))); |
11251 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11252 | 0 | { |
11253 | 0 | char *pszTemp = nullptr; |
11254 | 0 | psTemp[i] = static_cast<short>( |
11255 | 0 | strtol(papszValues[i], &pszTemp, 10)); |
11256 | 0 | } |
11257 | 0 | status = |
11258 | 0 | nc_put_vara_short(nCdfId, nVarId, start, count, psTemp); |
11259 | 0 | NCDF_ERR(status); |
11260 | 0 | CPLFree(psTemp); |
11261 | 0 | break; |
11262 | 0 | } |
11263 | 0 | case NC_INT: |
11264 | 0 | { |
11265 | 0 | int *pnTemp = |
11266 | 0 | static_cast<int *>(CPLCalloc(nVarLen, sizeof(int))); |
11267 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11268 | 0 | { |
11269 | 0 | char *pszTemp = nullptr; |
11270 | 0 | pnTemp[i] = |
11271 | 0 | static_cast<int>(strtol(papszValues[i], &pszTemp, 10)); |
11272 | 0 | } |
11273 | 0 | status = nc_put_vara_int(nCdfId, nVarId, start, count, pnTemp); |
11274 | 0 | NCDF_ERR(status); |
11275 | 0 | CPLFree(pnTemp); |
11276 | 0 | break; |
11277 | 0 | } |
11278 | 0 | case NC_FLOAT: |
11279 | 0 | { |
11280 | 0 | float *pfTemp = |
11281 | 0 | static_cast<float *>(CPLCalloc(nVarLen, sizeof(float))); |
11282 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11283 | 0 | { |
11284 | 0 | char *pszTemp = nullptr; |
11285 | 0 | pfTemp[i] = |
11286 | 0 | static_cast<float>(CPLStrtod(papszValues[i], &pszTemp)); |
11287 | 0 | } |
11288 | 0 | status = |
11289 | 0 | nc_put_vara_float(nCdfId, nVarId, start, count, pfTemp); |
11290 | 0 | NCDF_ERR(status); |
11291 | 0 | CPLFree(pfTemp); |
11292 | 0 | break; |
11293 | 0 | } |
11294 | 0 | case NC_DOUBLE: |
11295 | 0 | { |
11296 | 0 | double *pdfTemp = |
11297 | 0 | static_cast<double *>(CPLCalloc(nVarLen, sizeof(double))); |
11298 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11299 | 0 | { |
11300 | 0 | char *pszTemp = nullptr; |
11301 | 0 | pdfTemp[i] = CPLStrtod(papszValues[i], &pszTemp); |
11302 | 0 | } |
11303 | 0 | status = |
11304 | 0 | nc_put_vara_double(nCdfId, nVarId, start, count, pdfTemp); |
11305 | 0 | NCDF_ERR(status); |
11306 | 0 | CPLFree(pdfTemp); |
11307 | 0 | break; |
11308 | 0 | } |
11309 | 0 | default: |
11310 | 0 | { |
11311 | 0 | int nTmpFormat = 0; |
11312 | 0 | status = nc_inq_format(nCdfId, &nTmpFormat); |
11313 | 0 | NCDF_ERR(status); |
11314 | 0 | if (nTmpFormat == NCDF_FORMAT_NC4) |
11315 | 0 | { |
11316 | 0 | switch (nVarType) |
11317 | 0 | { |
11318 | 0 | case NC_STRING: |
11319 | 0 | { |
11320 | 0 | status = |
11321 | 0 | nc_put_vara_string(nCdfId, nVarId, start, count, |
11322 | 0 | (const char **)papszValues); |
11323 | 0 | NCDF_ERR(status); |
11324 | 0 | break; |
11325 | 0 | } |
11326 | 0 | case NC_UBYTE: |
11327 | 0 | { |
11328 | 0 | unsigned char *pucTemp = |
11329 | 0 | static_cast<unsigned char *>( |
11330 | 0 | CPLCalloc(nVarLen, sizeof(unsigned char))); |
11331 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11332 | 0 | { |
11333 | 0 | char *pszTemp = nullptr; |
11334 | 0 | pucTemp[i] = static_cast<unsigned char>( |
11335 | 0 | strtoul(papszValues[i], &pszTemp, 10)); |
11336 | 0 | } |
11337 | 0 | status = nc_put_vara_uchar(nCdfId, nVarId, start, |
11338 | 0 | count, pucTemp); |
11339 | 0 | NCDF_ERR(status); |
11340 | 0 | CPLFree(pucTemp); |
11341 | 0 | break; |
11342 | 0 | } |
11343 | 0 | case NC_USHORT: |
11344 | 0 | { |
11345 | 0 | unsigned short *pusTemp = |
11346 | 0 | static_cast<unsigned short *>( |
11347 | 0 | CPLCalloc(nVarLen, sizeof(unsigned short))); |
11348 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11349 | 0 | { |
11350 | 0 | char *pszTemp = nullptr; |
11351 | 0 | pusTemp[i] = static_cast<unsigned short>( |
11352 | 0 | strtoul(papszValues[i], &pszTemp, 10)); |
11353 | 0 | } |
11354 | 0 | status = nc_put_vara_ushort(nCdfId, nVarId, start, |
11355 | 0 | count, pusTemp); |
11356 | 0 | NCDF_ERR(status); |
11357 | 0 | CPLFree(pusTemp); |
11358 | 0 | break; |
11359 | 0 | } |
11360 | 0 | case NC_UINT: |
11361 | 0 | { |
11362 | 0 | unsigned int *punTemp = static_cast<unsigned int *>( |
11363 | 0 | CPLCalloc(nVarLen, sizeof(unsigned int))); |
11364 | 0 | for (size_t i = 0; i < nVarLen; i++) |
11365 | 0 | { |
11366 | 0 | char *pszTemp = nullptr; |
11367 | 0 | punTemp[i] = static_cast<unsigned int>( |
11368 | 0 | strtoul(papszValues[i], &pszTemp, 10)); |
11369 | 0 | } |
11370 | 0 | status = nc_put_vara_uint(nCdfId, nVarId, start, |
11371 | 0 | count, punTemp); |
11372 | 0 | NCDF_ERR(status); |
11373 | 0 | CPLFree(punTemp); |
11374 | 0 | break; |
11375 | 0 | } |
11376 | 0 | default: |
11377 | 0 | if (papszValues) |
11378 | 0 | CSLDestroy(papszValues); |
11379 | 0 | return CE_Failure; |
11380 | 0 | break; |
11381 | 0 | } |
11382 | 0 | } |
11383 | 0 | break; |
11384 | 0 | } |
11385 | 0 | } |
11386 | 0 | } |
11387 | | |
11388 | 0 | if (papszValues) |
11389 | 0 | CSLDestroy(papszValues); |
11390 | |
|
11391 | 0 | return CE_None; |
11392 | 0 | } |
11393 | | |
11394 | | /************************************************************************/ |
11395 | | /* GetDefaultNoDataValue() */ |
11396 | | /************************************************************************/ |
11397 | | |
11398 | | double NCDFGetDefaultNoDataValue(int nCdfId, int nVarId, int nVarType, |
11399 | | bool &bGotNoData) |
11400 | | |
11401 | 0 | { |
11402 | 0 | int nNoFill = 0; |
11403 | 0 | double dfNoData = 0.0; |
11404 | |
|
11405 | 0 | switch (nVarType) |
11406 | 0 | { |
11407 | 0 | case NC_CHAR: |
11408 | 0 | case NC_BYTE: |
11409 | 0 | case NC_UBYTE: |
11410 | | // Don't do default fill-values for bytes, too risky. |
11411 | | // This function should not be called in those cases. |
11412 | 0 | CPLAssert(false); |
11413 | 0 | break; |
11414 | 0 | case NC_SHORT: |
11415 | 0 | { |
11416 | 0 | short nFillVal = 0; |
11417 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == |
11418 | 0 | NC_NOERR) |
11419 | 0 | { |
11420 | 0 | if (!nNoFill) |
11421 | 0 | { |
11422 | 0 | bGotNoData = true; |
11423 | 0 | dfNoData = nFillVal; |
11424 | 0 | } |
11425 | 0 | } |
11426 | 0 | else |
11427 | 0 | dfNoData = NC_FILL_SHORT; |
11428 | 0 | break; |
11429 | 0 | } |
11430 | 0 | case NC_INT: |
11431 | 0 | { |
11432 | 0 | int nFillVal = 0; |
11433 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == |
11434 | 0 | NC_NOERR) |
11435 | 0 | { |
11436 | 0 | if (!nNoFill) |
11437 | 0 | { |
11438 | 0 | bGotNoData = true; |
11439 | 0 | dfNoData = nFillVal; |
11440 | 0 | } |
11441 | 0 | } |
11442 | 0 | else |
11443 | 0 | dfNoData = NC_FILL_INT; |
11444 | 0 | break; |
11445 | 0 | } |
11446 | 0 | case NC_FLOAT: |
11447 | 0 | { |
11448 | 0 | float fFillVal = 0; |
11449 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &fFillVal) == |
11450 | 0 | NC_NOERR) |
11451 | 0 | { |
11452 | 0 | if (!nNoFill) |
11453 | 0 | { |
11454 | 0 | bGotNoData = true; |
11455 | 0 | dfNoData = fFillVal; |
11456 | 0 | } |
11457 | 0 | } |
11458 | 0 | else |
11459 | 0 | dfNoData = NC_FILL_FLOAT; |
11460 | 0 | break; |
11461 | 0 | } |
11462 | 0 | case NC_DOUBLE: |
11463 | 0 | { |
11464 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &dfNoData) == |
11465 | 0 | NC_NOERR) |
11466 | 0 | { |
11467 | 0 | if (!nNoFill) |
11468 | 0 | { |
11469 | 0 | bGotNoData = true; |
11470 | 0 | } |
11471 | 0 | } |
11472 | 0 | else |
11473 | 0 | dfNoData = NC_FILL_DOUBLE; |
11474 | 0 | break; |
11475 | 0 | } |
11476 | 0 | case NC_USHORT: |
11477 | 0 | { |
11478 | 0 | unsigned short nFillVal = 0; |
11479 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == |
11480 | 0 | NC_NOERR) |
11481 | 0 | { |
11482 | 0 | if (!nNoFill) |
11483 | 0 | { |
11484 | 0 | bGotNoData = true; |
11485 | 0 | dfNoData = nFillVal; |
11486 | 0 | } |
11487 | 0 | } |
11488 | 0 | else |
11489 | 0 | dfNoData = NC_FILL_USHORT; |
11490 | 0 | break; |
11491 | 0 | } |
11492 | 0 | case NC_UINT: |
11493 | 0 | { |
11494 | 0 | unsigned int nFillVal = 0; |
11495 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == |
11496 | 0 | NC_NOERR) |
11497 | 0 | { |
11498 | 0 | if (!nNoFill) |
11499 | 0 | { |
11500 | 0 | bGotNoData = true; |
11501 | 0 | dfNoData = nFillVal; |
11502 | 0 | } |
11503 | 0 | } |
11504 | 0 | else |
11505 | 0 | dfNoData = NC_FILL_UINT; |
11506 | 0 | break; |
11507 | 0 | } |
11508 | 0 | default: |
11509 | 0 | dfNoData = 0.0; |
11510 | 0 | break; |
11511 | 0 | } |
11512 | | |
11513 | 0 | return dfNoData; |
11514 | 0 | } |
11515 | | |
11516 | | /************************************************************************/ |
11517 | | /* NCDFGetDefaultNoDataValueAsInt64() */ |
11518 | | /************************************************************************/ |
11519 | | |
11520 | | int64_t NCDFGetDefaultNoDataValueAsInt64(int nCdfId, int nVarId, |
11521 | | bool &bGotNoData) |
11522 | | |
11523 | 0 | { |
11524 | 0 | int nNoFill = 0; |
11525 | 0 | long long nFillVal = 0; |
11526 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR) |
11527 | 0 | { |
11528 | 0 | if (!nNoFill) |
11529 | 0 | { |
11530 | 0 | bGotNoData = true; |
11531 | 0 | return static_cast<int64_t>(nFillVal); |
11532 | 0 | } |
11533 | 0 | } |
11534 | 0 | else |
11535 | 0 | return static_cast<int64_t>(NC_FILL_INT64); |
11536 | 0 | return 0; |
11537 | 0 | } |
11538 | | |
11539 | | /************************************************************************/ |
11540 | | /* NCDFGetDefaultNoDataValueAsUInt64() */ |
11541 | | /************************************************************************/ |
11542 | | |
11543 | | uint64_t NCDFGetDefaultNoDataValueAsUInt64(int nCdfId, int nVarId, |
11544 | | bool &bGotNoData) |
11545 | | |
11546 | 0 | { |
11547 | 0 | int nNoFill = 0; |
11548 | 0 | unsigned long long nFillVal = 0; |
11549 | 0 | if (nc_inq_var_fill(nCdfId, nVarId, &nNoFill, &nFillVal) == NC_NOERR) |
11550 | 0 | { |
11551 | 0 | if (!nNoFill) |
11552 | 0 | { |
11553 | 0 | bGotNoData = true; |
11554 | 0 | return static_cast<uint64_t>(nFillVal); |
11555 | 0 | } |
11556 | 0 | } |
11557 | 0 | else |
11558 | 0 | return static_cast<uint64_t>(NC_FILL_UINT64); |
11559 | 0 | return 0; |
11560 | 0 | } |
11561 | | |
11562 | | static int NCDFDoesVarContainAttribVal(int nCdfId, |
11563 | | const char *const *papszAttribNames, |
11564 | | const char *const *papszAttribValues, |
11565 | | int nVarId, const char *pszVarName, |
11566 | | bool bStrict = true) |
11567 | 744 | { |
11568 | 744 | if (nVarId == -1 && pszVarName != nullptr) |
11569 | 744 | NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId); |
11570 | | |
11571 | 744 | if (nVarId == -1) |
11572 | 26 | return -1; |
11573 | | |
11574 | 718 | bool bFound = false; |
11575 | 3.44k | for (int i = 0; !bFound && papszAttribNames != nullptr && |
11576 | 3.44k | papszAttribNames[i] != nullptr; |
11577 | 2.73k | i++) |
11578 | 2.73k | { |
11579 | 2.73k | char *pszTemp = nullptr; |
11580 | 2.73k | if (NCDFGetAttr(nCdfId, nVarId, papszAttribNames[i], &pszTemp) == |
11581 | 2.73k | CE_None && |
11582 | 2.73k | pszTemp != nullptr) |
11583 | 4 | { |
11584 | 4 | if (bStrict) |
11585 | 4 | { |
11586 | 4 | if (EQUAL(pszTemp, papszAttribValues[i])) |
11587 | 0 | bFound = true; |
11588 | 4 | } |
11589 | 0 | else |
11590 | 0 | { |
11591 | 0 | if (EQUALN(pszTemp, papszAttribValues[i], |
11592 | 0 | strlen(papszAttribValues[i]))) |
11593 | 0 | bFound = true; |
11594 | 0 | } |
11595 | 4 | CPLFree(pszTemp); |
11596 | 4 | } |
11597 | 2.73k | } |
11598 | 718 | return bFound; |
11599 | 744 | } |
11600 | | |
11601 | | static int NCDFDoesVarContainAttribVal2(int nCdfId, const char *papszAttribName, |
11602 | | const char *const *papszAttribValues, |
11603 | | int nVarId, const char *pszVarName, |
11604 | | int bStrict = true) |
11605 | 279 | { |
11606 | 279 | if (nVarId == -1 && pszVarName != nullptr) |
11607 | 279 | NCDFResolveVar(nCdfId, pszVarName, &nCdfId, &nVarId); |
11608 | | |
11609 | 279 | if (nVarId == -1) |
11610 | 0 | return -1; |
11611 | | |
11612 | 279 | bool bFound = false; |
11613 | 279 | char *pszTemp = nullptr; |
11614 | 279 | if (NCDFGetAttr(nCdfId, nVarId, papszAttribName, &pszTemp) != CE_None || |
11615 | 279 | pszTemp == nullptr) |
11616 | 279 | return FALSE; |
11617 | | |
11618 | 0 | for (int i = 0; !bFound && i < CSLCount(papszAttribValues); i++) |
11619 | 0 | { |
11620 | 0 | if (bStrict) |
11621 | 0 | { |
11622 | 0 | if (EQUAL(pszTemp, papszAttribValues[i])) |
11623 | 0 | bFound = true; |
11624 | 0 | } |
11625 | 0 | else |
11626 | 0 | { |
11627 | 0 | if (EQUALN(pszTemp, papszAttribValues[i], |
11628 | 0 | strlen(papszAttribValues[i]))) |
11629 | 0 | bFound = true; |
11630 | 0 | } |
11631 | 0 | } |
11632 | |
|
11633 | 0 | CPLFree(pszTemp); |
11634 | |
|
11635 | 0 | return bFound; |
11636 | 279 | } |
11637 | | |
11638 | | static bool NCDFEqual(const char *papszName, const char *const *papszValues) |
11639 | 26 | { |
11640 | 26 | if (papszName == nullptr || EQUAL(papszName, "")) |
11641 | 0 | return false; |
11642 | | |
11643 | 78 | for (int i = 0; papszValues && papszValues[i]; ++i) |
11644 | 52 | { |
11645 | 52 | if (EQUAL(papszName, papszValues[i])) |
11646 | 0 | return true; |
11647 | 52 | } |
11648 | | |
11649 | 26 | return false; |
11650 | 26 | } |
11651 | | |
11652 | | // Test that a variable is longitude/latitude coordinate, |
11653 | | // following CF 4.1 and 4.2. |
11654 | | bool NCDFIsVarLongitude(int nCdfId, int nVarId, const char *pszVarName) |
11655 | 151 | { |
11656 | | // Check for matching attributes. |
11657 | 151 | int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLongitudeAttribNames, |
11658 | 151 | papszCFLongitudeAttribValues, nVarId, |
11659 | 151 | pszVarName); |
11660 | | // If not found using attributes then check using var name |
11661 | | // unless GDAL_NETCDF_VERIFY_DIMS=STRICT. |
11662 | 151 | if (bVal == -1) |
11663 | 6 | { |
11664 | 6 | if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), |
11665 | 6 | "STRICT")) |
11666 | 6 | bVal = NCDFEqual(pszVarName, papszCFLongitudeVarNames); |
11667 | 0 | else |
11668 | 0 | bVal = FALSE; |
11669 | 6 | } |
11670 | 145 | else if (bVal) |
11671 | 0 | { |
11672 | | // Check that the units is not 'm' or '1'. See #6759 |
11673 | 0 | char *pszTemp = nullptr; |
11674 | 0 | if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None && |
11675 | 0 | pszTemp != nullptr) |
11676 | 0 | { |
11677 | 0 | if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1")) |
11678 | 0 | bVal = false; |
11679 | 0 | CPLFree(pszTemp); |
11680 | 0 | } |
11681 | 0 | } |
11682 | | |
11683 | 151 | return CPL_TO_BOOL(bVal); |
11684 | 151 | } |
11685 | | |
11686 | | bool NCDFIsVarLatitude(int nCdfId, int nVarId, const char *pszVarName) |
11687 | 151 | { |
11688 | 151 | int bVal = NCDFDoesVarContainAttribVal(nCdfId, papszCFLatitudeAttribNames, |
11689 | 151 | papszCFLatitudeAttribValues, nVarId, |
11690 | 151 | pszVarName); |
11691 | 151 | if (bVal == -1) |
11692 | 7 | { |
11693 | 7 | if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), |
11694 | 7 | "STRICT")) |
11695 | 7 | bVal = NCDFEqual(pszVarName, papszCFLatitudeVarNames); |
11696 | 0 | else |
11697 | 0 | bVal = FALSE; |
11698 | 7 | } |
11699 | 144 | else if (bVal) |
11700 | 0 | { |
11701 | | // Check that the units is not 'm' or '1'. See #6759 |
11702 | 0 | char *pszTemp = nullptr; |
11703 | 0 | if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None && |
11704 | 0 | pszTemp != nullptr) |
11705 | 0 | { |
11706 | 0 | if (EQUAL(pszTemp, "m") || EQUAL(pszTemp, "1")) |
11707 | 0 | bVal = false; |
11708 | 0 | CPLFree(pszTemp); |
11709 | 0 | } |
11710 | 0 | } |
11711 | | |
11712 | 151 | return CPL_TO_BOOL(bVal); |
11713 | 151 | } |
11714 | | |
11715 | | bool NCDFIsVarProjectionX(int nCdfId, int nVarId, const char *pszVarName) |
11716 | 151 | { |
11717 | 151 | int bVal = NCDFDoesVarContainAttribVal( |
11718 | 151 | nCdfId, papszCFProjectionXAttribNames, papszCFProjectionXAttribValues, |
11719 | 151 | nVarId, pszVarName); |
11720 | 151 | if (bVal == -1) |
11721 | 6 | { |
11722 | 6 | if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), |
11723 | 6 | "STRICT")) |
11724 | 6 | bVal = NCDFEqual(pszVarName, papszCFProjectionXVarNames); |
11725 | 0 | else |
11726 | 0 | bVal = FALSE; |
11727 | 6 | } |
11728 | 145 | else if (bVal) |
11729 | 0 | { |
11730 | | // Check that the units is not '1' |
11731 | 0 | char *pszTemp = nullptr; |
11732 | 0 | if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None && |
11733 | 0 | pszTemp != nullptr) |
11734 | 0 | { |
11735 | 0 | if (EQUAL(pszTemp, "1")) |
11736 | 0 | bVal = false; |
11737 | 0 | CPLFree(pszTemp); |
11738 | 0 | } |
11739 | 0 | } |
11740 | | |
11741 | 151 | return CPL_TO_BOOL(bVal); |
11742 | 151 | } |
11743 | | |
11744 | | bool NCDFIsVarProjectionY(int nCdfId, int nVarId, const char *pszVarName) |
11745 | 151 | { |
11746 | 151 | int bVal = NCDFDoesVarContainAttribVal( |
11747 | 151 | nCdfId, papszCFProjectionYAttribNames, papszCFProjectionYAttribValues, |
11748 | 151 | nVarId, pszVarName); |
11749 | 151 | if (bVal == -1) |
11750 | 7 | { |
11751 | 7 | if (!EQUAL(CPLGetConfigOption("GDAL_NETCDF_VERIFY_DIMS", "YES"), |
11752 | 7 | "STRICT")) |
11753 | 7 | bVal = NCDFEqual(pszVarName, papszCFProjectionYVarNames); |
11754 | 0 | else |
11755 | 0 | bVal = FALSE; |
11756 | 7 | } |
11757 | 144 | else if (bVal) |
11758 | 0 | { |
11759 | | // Check that the units is not '1' |
11760 | 0 | char *pszTemp = nullptr; |
11761 | 0 | if (NCDFGetAttr(nCdfId, nVarId, "units", &pszTemp) == CE_None && |
11762 | 0 | pszTemp != nullptr) |
11763 | 0 | { |
11764 | 0 | if (EQUAL(pszTemp, "1")) |
11765 | 0 | bVal = false; |
11766 | 0 | CPLFree(pszTemp); |
11767 | 0 | } |
11768 | 0 | } |
11769 | | |
11770 | 151 | return CPL_TO_BOOL(bVal); |
11771 | 151 | } |
11772 | | |
11773 | | /* test that a variable is a vertical coordinate, following CF 4.3 */ |
11774 | | bool NCDFIsVarVerticalCoord(int nCdfId, int nVarId, const char *pszVarName) |
11775 | 139 | { |
11776 | | /* check for matching attributes */ |
11777 | 139 | if (NCDFDoesVarContainAttribVal(nCdfId, papszCFVerticalAttribNames, |
11778 | 139 | papszCFVerticalAttribValues, nVarId, |
11779 | 139 | pszVarName)) |
11780 | 0 | return true; |
11781 | | /* check for matching units */ |
11782 | 139 | else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS, |
11783 | 139 | papszCFVerticalUnitsValues, nVarId, |
11784 | 139 | pszVarName)) |
11785 | 0 | return true; |
11786 | | /* check for matching standard name */ |
11787 | 139 | else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_STD_NAME, |
11788 | 139 | papszCFVerticalStandardNameValues, |
11789 | 139 | nVarId, pszVarName)) |
11790 | 0 | return true; |
11791 | 139 | else |
11792 | 139 | return false; |
11793 | 139 | } |
11794 | | |
11795 | | /* test that a variable is a time coordinate, following CF 4.4 */ |
11796 | | bool NCDFIsVarTimeCoord(int nCdfId, int nVarId, const char *pszVarName) |
11797 | 1 | { |
11798 | | /* check for matching attributes */ |
11799 | 1 | if (NCDFDoesVarContainAttribVal(nCdfId, papszCFTimeAttribNames, |
11800 | 1 | papszCFTimeAttribValues, nVarId, |
11801 | 1 | pszVarName)) |
11802 | 0 | return true; |
11803 | | /* check for matching units */ |
11804 | 1 | else if (NCDFDoesVarContainAttribVal2(nCdfId, CF_UNITS, |
11805 | 1 | papszCFTimeUnitsValues, nVarId, |
11806 | 1 | pszVarName, false)) |
11807 | 0 | return true; |
11808 | 1 | else |
11809 | 1 | return false; |
11810 | 1 | } |
11811 | | |
11812 | | // Parse a string, and return as a string list. |
11813 | | // If it an array of the form {a,b}, then tokenize it. |
11814 | | // Otherwise, return a copy. |
11815 | | static char **NCDFTokenizeArray(const char *pszValue) |
11816 | 0 | { |
11817 | 0 | if (pszValue == nullptr || EQUAL(pszValue, "")) |
11818 | 0 | return nullptr; |
11819 | | |
11820 | 0 | char **papszValues = nullptr; |
11821 | 0 | const int nLen = static_cast<int>(strlen(pszValue)); |
11822 | |
|
11823 | 0 | if (pszValue[0] == '{' && nLen > 2 && pszValue[nLen - 1] == '}') |
11824 | 0 | { |
11825 | 0 | char *pszTemp = static_cast<char *>(CPLMalloc((nLen - 2) + 1)); |
11826 | 0 | strncpy(pszTemp, pszValue + 1, nLen - 2); |
11827 | 0 | pszTemp[nLen - 2] = '\0'; |
11828 | 0 | papszValues = CSLTokenizeString2(pszTemp, ",", CSLT_ALLOWEMPTYTOKENS); |
11829 | 0 | CPLFree(pszTemp); |
11830 | 0 | } |
11831 | 0 | else |
11832 | 0 | { |
11833 | 0 | papszValues = reinterpret_cast<char **>(CPLCalloc(2, sizeof(char *))); |
11834 | 0 | papszValues[0] = CPLStrdup(pszValue); |
11835 | 0 | papszValues[1] = nullptr; |
11836 | 0 | } |
11837 | |
|
11838 | 0 | return papszValues; |
11839 | 0 | } |
11840 | | |
11841 | | // Open a NetCDF subdataset from full path /group1/group2/.../groupn/var. |
11842 | | // Leading slash is optional. |
11843 | | static CPLErr NCDFOpenSubDataset(int nCdfId, const char *pszSubdatasetName, |
11844 | | int *pnGroupId, int *pnVarId) |
11845 | 11.1k | { |
11846 | 11.1k | *pnGroupId = -1; |
11847 | 11.1k | *pnVarId = -1; |
11848 | | |
11849 | | // Open group. |
11850 | 11.1k | char *pszGroupFullName = |
11851 | 11.1k | CPLStrdup(CPLGetPathSafe(pszSubdatasetName).c_str()); |
11852 | | // Add a leading slash if needed. |
11853 | 11.1k | if (pszGroupFullName[0] != '/') |
11854 | 0 | { |
11855 | 0 | char *old = pszGroupFullName; |
11856 | 0 | pszGroupFullName = CPLStrdup(CPLSPrintf("/%s", pszGroupFullName)); |
11857 | 0 | CPLFree(old); |
11858 | 0 | } |
11859 | | // Detect root group. |
11860 | 11.1k | if (EQUAL(pszGroupFullName, "/")) |
11861 | 9.21k | { |
11862 | 9.21k | *pnGroupId = nCdfId; |
11863 | 9.21k | CPLFree(pszGroupFullName); |
11864 | 9.21k | } |
11865 | 1.93k | else |
11866 | 1.93k | { |
11867 | 1.93k | int status = nc_inq_grp_full_ncid(nCdfId, pszGroupFullName, pnGroupId); |
11868 | 1.93k | CPLFree(pszGroupFullName); |
11869 | 1.93k | NCDF_ERR_RET(status); |
11870 | 1.93k | } |
11871 | | |
11872 | | // Open var. |
11873 | 9.21k | const char *pszVarName = CPLGetFilename(pszSubdatasetName); |
11874 | 9.21k | NCDF_ERR_RET(nc_inq_varid(*pnGroupId, pszVarName, pnVarId)); |
11875 | | |
11876 | 0 | return CE_None; |
11877 | 9.21k | } |
11878 | | |
11879 | | // Get all dimensions visible from a given NetCDF (or group) ID and any of |
11880 | | // its parents. |
11881 | | static CPLErr NCDFGetVisibleDims(int nGroupId, int *pnDims, int **ppanDimIds) |
11882 | 0 | { |
11883 | 0 | int nDims = 0; |
11884 | 0 | int *panDimIds = nullptr; |
11885 | 0 | NCDF_ERR_RET(nc_inq_dimids(nGroupId, &nDims, nullptr, true)); |
11886 | | |
11887 | 0 | panDimIds = static_cast<int *>(CPLMalloc(nDims * sizeof(int))); |
11888 | |
|
11889 | 0 | int status = nc_inq_dimids(nGroupId, nullptr, panDimIds, true); |
11890 | 0 | if (status != NC_NOERR) |
11891 | 0 | CPLFree(panDimIds); |
11892 | 0 | NCDF_ERR_RET(status); |
11893 | | |
11894 | 0 | *pnDims = nDims; |
11895 | 0 | *ppanDimIds = panDimIds; |
11896 | |
|
11897 | 0 | return CE_None; |
11898 | 0 | } |
11899 | | |
11900 | | // Get direct sub-groups IDs of a given NetCDF (or group) ID. |
11901 | | // Consider only direct children, does not get children of children. |
11902 | | static CPLErr NCDFGetSubGroups(int nGroupId, int *pnSubGroups, |
11903 | | int **ppanSubGroupIds) |
11904 | 99.0k | { |
11905 | 99.0k | *pnSubGroups = 0; |
11906 | 99.0k | *ppanSubGroupIds = nullptr; |
11907 | | |
11908 | 99.0k | int nSubGroups; |
11909 | 99.0k | NCDF_ERR_RET(nc_inq_grps(nGroupId, &nSubGroups, nullptr)); |
11910 | 99.0k | int *panSubGroupIds = |
11911 | 99.0k | static_cast<int *>(CPLMalloc(nSubGroups * sizeof(int))); |
11912 | 99.0k | NCDF_ERR_RET(nc_inq_grps(nGroupId, nullptr, panSubGroupIds)); |
11913 | 99.0k | *pnSubGroups = nSubGroups; |
11914 | 99.0k | *ppanSubGroupIds = panSubGroupIds; |
11915 | | |
11916 | 99.0k | return CE_None; |
11917 | 99.0k | } |
11918 | | |
11919 | | // Get the full name of a given NetCDF (or group) ID |
11920 | | // (e.g. /group1/group2/.../groupn). |
11921 | | // bNC3Compat remove the leading slash for top-level variables for |
11922 | | // backward compatibility (top-level variables are the ones in the root group). |
11923 | | static CPLErr NCDFGetGroupFullName(int nGroupId, char **ppszFullName, |
11924 | | bool bNC3Compat) |
11925 | 7.06k | { |
11926 | 7.06k | *ppszFullName = nullptr; |
11927 | | |
11928 | 7.06k | size_t nFullNameLen; |
11929 | 7.06k | NCDF_ERR_RET(nc_inq_grpname_len(nGroupId, &nFullNameLen)); |
11930 | 7.06k | *ppszFullName = |
11931 | 7.06k | static_cast<char *>(CPLMalloc((nFullNameLen + 1) * sizeof(char))); |
11932 | 7.06k | int status = nc_inq_grpname_full(nGroupId, &nFullNameLen, *ppszFullName); |
11933 | 7.06k | if (status != NC_NOERR) |
11934 | 0 | { |
11935 | 0 | CPLFree(*ppszFullName); |
11936 | 0 | *ppszFullName = nullptr; |
11937 | 0 | NCDF_ERR_RET(status); |
11938 | 0 | } |
11939 | | |
11940 | 7.06k | if (bNC3Compat && EQUAL(*ppszFullName, "/")) |
11941 | 7.06k | (*ppszFullName)[0] = '\0'; |
11942 | | |
11943 | 7.06k | return CE_None; |
11944 | 7.06k | } |
11945 | | |
11946 | | CPLString NCDFGetGroupFullName(int nGroupId) |
11947 | 0 | { |
11948 | 0 | char *pszFullname = nullptr; |
11949 | 0 | NCDFGetGroupFullName(nGroupId, &pszFullname, false); |
11950 | 0 | CPLString osRet(pszFullname ? pszFullname : ""); |
11951 | 0 | CPLFree(pszFullname); |
11952 | 0 | return osRet; |
11953 | 0 | } |
11954 | | |
11955 | | // Get the full name of a given NetCDF variable ID |
11956 | | // (e.g. /group1/group2/.../groupn/var). |
11957 | | // Handle also NC_GLOBAL as nVarId. |
11958 | | // bNC3Compat remove the leading slash for top-level variables for |
11959 | | // backward compatibility (top-level variables are the ones in the root group). |
11960 | | static CPLErr NCDFGetVarFullName(int nGroupId, int nVarId, char **ppszFullName, |
11961 | | bool bNC3Compat) |
11962 | 7.04k | { |
11963 | 7.04k | *ppszFullName = nullptr; |
11964 | 7.04k | char *pszGroupFullName = nullptr; |
11965 | 7.04k | ERR_RET(NCDFGetGroupFullName(nGroupId, &pszGroupFullName, bNC3Compat)); |
11966 | 7.04k | char szVarName[NC_MAX_NAME + 1]; |
11967 | 7.04k | if (nVarId == NC_GLOBAL) |
11968 | 399 | { |
11969 | 399 | strcpy(szVarName, "NC_GLOBAL"); |
11970 | 399 | } |
11971 | 6.64k | else |
11972 | 6.64k | { |
11973 | 6.64k | int status = nc_inq_varname(nGroupId, nVarId, szVarName); |
11974 | 6.64k | if (status != NC_NOERR) |
11975 | 0 | { |
11976 | 0 | CPLFree(pszGroupFullName); |
11977 | 0 | NCDF_ERR_RET(status); |
11978 | 0 | } |
11979 | 6.64k | } |
11980 | 7.04k | const char *pszSep = "/"; |
11981 | 7.04k | if (EQUAL(pszGroupFullName, "/") || EQUAL(pszGroupFullName, "")) |
11982 | 7.04k | pszSep = ""; |
11983 | 7.04k | *ppszFullName = |
11984 | 7.04k | CPLStrdup(CPLSPrintf("%s%s%s", pszGroupFullName, pszSep, szVarName)); |
11985 | 7.04k | CPLFree(pszGroupFullName); |
11986 | 7.04k | return CE_None; |
11987 | 7.04k | } |
11988 | | |
11989 | | // Get the NetCDF root group ID of a given group ID. |
11990 | | static CPLErr NCDFGetRootGroup(int nStartGroupId, int *pnRootGroupId) |
11991 | 11.1k | { |
11992 | 11.1k | *pnRootGroupId = -1; |
11993 | | // Recurse on parent group. |
11994 | 11.1k | int nParentGroupId; |
11995 | 11.1k | int status = nc_inq_grp_parent(nStartGroupId, &nParentGroupId); |
11996 | 11.1k | if (status == NC_NOERR) |
11997 | 0 | return NCDFGetRootGroup(nParentGroupId, pnRootGroupId); |
11998 | 11.1k | else if (status != NC_ENOGRP) |
11999 | 0 | NCDF_ERR_RET(status); |
12000 | 11.1k | else // No more parent group. |
12001 | 11.1k | { |
12002 | 11.1k | *pnRootGroupId = nStartGroupId; |
12003 | 11.1k | } |
12004 | | |
12005 | 11.1k | return CE_None; |
12006 | 11.1k | } |
12007 | | |
12008 | | // Implementation of NCDFResolveVar/Att. |
12009 | | static CPLErr NCDFResolveElem(int nStartGroupId, const char *pszVar, |
12010 | | const char *pszAtt, int *pnGroupId, int *pnId, |
12011 | | bool bMandatory) |
12012 | 99.6k | { |
12013 | 99.6k | if (!pszVar && !pszAtt) |
12014 | 0 | { |
12015 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
12016 | 0 | "pszVar and pszAtt NCDFResolveElem() args are both null."); |
12017 | 0 | return CE_Failure; |
12018 | 0 | } |
12019 | | |
12020 | 99.6k | enum |
12021 | 99.6k | { |
12022 | 99.6k | NCRM_PARENT, |
12023 | 99.6k | NCRM_WIDTH_WISE |
12024 | 99.6k | } eNCResolveMode = NCRM_PARENT; |
12025 | | |
12026 | 99.6k | std::queue<int> aoQueueGroupIdsToVisit; |
12027 | 99.6k | aoQueueGroupIdsToVisit.push(nStartGroupId); |
12028 | | |
12029 | 197k | while (!aoQueueGroupIdsToVisit.empty()) |
12030 | 99.6k | { |
12031 | | // Get the first group of the FIFO queue. |
12032 | 99.6k | *pnGroupId = aoQueueGroupIdsToVisit.front(); |
12033 | 99.6k | aoQueueGroupIdsToVisit.pop(); |
12034 | | |
12035 | | // Look if this group contains the searched element. |
12036 | 99.6k | int status; |
12037 | 99.6k | if (pszVar) |
12038 | 99.6k | status = nc_inq_varid(*pnGroupId, pszVar, pnId); |
12039 | 0 | else // pszAtt != nullptr. |
12040 | 0 | status = nc_inq_attid(*pnGroupId, NC_GLOBAL, pszAtt, pnId); |
12041 | | |
12042 | 99.6k | if (status == NC_NOERR) |
12043 | 1.75k | { |
12044 | 1.75k | return CE_None; |
12045 | 1.75k | } |
12046 | 97.8k | else if ((pszVar && status != NC_ENOTVAR) || |
12047 | 97.8k | (pszAtt && status != NC_ENOTATT)) |
12048 | 3.82k | { |
12049 | 3.82k | NCDF_ERR(status); |
12050 | 3.82k | } |
12051 | | // Element not found, in NC4 case we must search in other groups |
12052 | | // following the CF logic. |
12053 | | |
12054 | | // The first resolve mode consists to search on parent groups. |
12055 | 97.8k | if (eNCResolveMode == NCRM_PARENT) |
12056 | 97.8k | { |
12057 | 97.8k | int nParentGroupId = -1; |
12058 | 97.8k | int status2 = nc_inq_grp_parent(*pnGroupId, &nParentGroupId); |
12059 | 97.8k | if (status2 == NC_NOERR) |
12060 | 0 | aoQueueGroupIdsToVisit.push(nParentGroupId); |
12061 | 97.8k | else if (status2 != NC_ENOGRP) |
12062 | 0 | NCDF_ERR(status2); |
12063 | 97.8k | else if (pszVar) |
12064 | | // When resolving a variable, if there is no more |
12065 | | // parent group then we switch to width-wise search mode |
12066 | | // starting from the latest found parent group. |
12067 | 97.8k | eNCResolveMode = NCRM_WIDTH_WISE; |
12068 | 97.8k | } |
12069 | | |
12070 | | // The second resolve mode is a width-wise search. |
12071 | 97.8k | if (eNCResolveMode == NCRM_WIDTH_WISE) |
12072 | 97.8k | { |
12073 | | // Enqueue all direct sub-groups. |
12074 | 97.8k | int nSubGroups = 0; |
12075 | 97.8k | int *panSubGroupIds = nullptr; |
12076 | 97.8k | NCDFGetSubGroups(*pnGroupId, &nSubGroups, &panSubGroupIds); |
12077 | 97.8k | for (int i = 0; i < nSubGroups; i++) |
12078 | 0 | aoQueueGroupIdsToVisit.push(panSubGroupIds[i]); |
12079 | 97.8k | CPLFree(panSubGroupIds); |
12080 | 97.8k | } |
12081 | 97.8k | } |
12082 | | |
12083 | 97.8k | if (bMandatory) |
12084 | 0 | { |
12085 | 0 | char *pszStartGroupFullName = nullptr; |
12086 | 0 | NCDFGetGroupFullName(nStartGroupId, &pszStartGroupFullName); |
12087 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
12088 | 0 | "Cannot resolve mandatory %s %s from group %s", |
12089 | 0 | (pszVar ? pszVar : pszAtt), |
12090 | 0 | (pszVar ? "variable" : "attribute"), |
12091 | 0 | (pszStartGroupFullName ? pszStartGroupFullName : "")); |
12092 | 0 | CPLFree(pszStartGroupFullName); |
12093 | 0 | } |
12094 | | |
12095 | 97.8k | *pnGroupId = -1; |
12096 | 97.8k | *pnId = -1; |
12097 | 97.8k | return CE_Failure; |
12098 | 99.6k | } |
12099 | | |
12100 | | // Resolve a variable name from a given starting group following the CF logic: |
12101 | | // - if var name is an absolute path then directly open it |
12102 | | // - first search in the starting group and its parent groups |
12103 | | // - then if there is no more parent group we switch to a width-wise search |
12104 | | // mode starting from the latest found parent group. |
12105 | | // The full CF logic is described here: |
12106 | | // https://github.com/diwg/cf2/blob/master/group/cf2-group.adoc#scope |
12107 | | // If bMandatory then print an error if resolving fails. |
12108 | | // TODO: implement support of relative paths. |
12109 | | // TODO: to follow strictly the CF logic, when searching for a coordinate |
12110 | | // variable, we must stop the parent search mode once the corresponding |
12111 | | // dimension is found and start the width-wise search from this group. |
12112 | | // TODO: to follow strictly the CF logic, when searching in width-wise mode |
12113 | | // we should skip every groups already visited during the parent |
12114 | | // search mode (but revisiting them should have no impact so we could |
12115 | | // let as it is if it is simpler...) |
12116 | | // TODO: CF specifies that the width-wise search order is "left-to-right" so |
12117 | | // maybe we must sort sibling groups alphabetically? but maybe not |
12118 | | // necessary if nc_inq_grps() already sort them? |
12119 | | CPLErr NCDFResolveVar(int nStartGroupId, const char *pszVar, int *pnGroupId, |
12120 | | int *pnVarId, bool bMandatory) |
12121 | 110k | { |
12122 | 110k | *pnGroupId = -1; |
12123 | 110k | *pnVarId = -1; |
12124 | 110k | int nGroupId = nStartGroupId, nVarId; |
12125 | 110k | if (pszVar[0] == '/') |
12126 | 11.1k | { |
12127 | | // This is an absolute path: we can open the var directly. |
12128 | 11.1k | int nRootGroupId; |
12129 | 11.1k | ERR_RET(NCDFGetRootGroup(nStartGroupId, &nRootGroupId)); |
12130 | 11.1k | ERR_RET(NCDFOpenSubDataset(nRootGroupId, pszVar, &nGroupId, &nVarId)); |
12131 | 11.1k | } |
12132 | 99.6k | else |
12133 | 99.6k | { |
12134 | | // We have to search the variable following the CF logic. |
12135 | 99.6k | ERR_RET(NCDFResolveElem(nStartGroupId, pszVar, nullptr, &nGroupId, |
12136 | 99.6k | &nVarId, bMandatory)); |
12137 | 99.6k | } |
12138 | 1.75k | *pnGroupId = nGroupId; |
12139 | 1.75k | *pnVarId = nVarId; |
12140 | 1.75k | return CE_None; |
12141 | 110k | } |
12142 | | |
12143 | | // Like NCDFResolveVar but returns directly the var full name. |
12144 | | static CPLErr NCDFResolveVarFullName(int nStartGroupId, const char *pszVar, |
12145 | | char **ppszFullName, bool bMandatory) |
12146 | 109k | { |
12147 | 109k | *ppszFullName = nullptr; |
12148 | 109k | int nGroupId, nVarId; |
12149 | 109k | ERR_RET( |
12150 | 109k | NCDFResolveVar(nStartGroupId, pszVar, &nGroupId, &nVarId, bMandatory)); |
12151 | 753 | return NCDFGetVarFullName(nGroupId, nVarId, ppszFullName); |
12152 | 109k | } |
12153 | | |
12154 | | // Like NCDFResolveVar but resolves an attribute instead a variable and |
12155 | | // returns its integer value. |
12156 | | // Only GLOBAL attributes are supported for the moment. |
12157 | | static CPLErr NCDFResolveAttInt(int nStartGroupId, int nStartVarId, |
12158 | | const char *pszAtt, int *pnAtt, bool bMandatory) |
12159 | 0 | { |
12160 | 0 | int nGroupId = nStartGroupId, nAttId = nStartVarId; |
12161 | 0 | ERR_RET(NCDFResolveElem(nStartGroupId, nullptr, pszAtt, &nGroupId, &nAttId, |
12162 | 0 | bMandatory)); |
12163 | 0 | NCDF_ERR_RET(nc_get_att_int(nGroupId, NC_GLOBAL, pszAtt, pnAtt)); |
12164 | 0 | return CE_None; |
12165 | 0 | } |
12166 | | |
12167 | | // Filter variables to keep only valid 2+D raster bands and vector fields in |
12168 | | // a given a NetCDF (or group) ID and its sub-groups. |
12169 | | // Coordinate or boundary variables are ignored. |
12170 | | // It also creates corresponding vector layers. |
12171 | | CPLErr netCDFDataset::FilterVars( |
12172 | | int nCdfId, bool bKeepRasters, bool bKeepVectors, char **papszIgnoreVars, |
12173 | | int *pnRasterVars, int *pnGroupId, int *pnVarId, int *pnIgnoredVars, |
12174 | | std::map<std::array<int, 3>, std::vector<std::pair<int, int>>> |
12175 | | &oMap2DDimsToGroupAndVar) |
12176 | 399 | { |
12177 | 399 | int nVars = 0; |
12178 | 399 | int nRasterVars = 0; |
12179 | 399 | NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr)); |
12180 | | |
12181 | 399 | std::vector<int> anPotentialVectorVarID; |
12182 | | // oMapDimIdToCount[x] = number of times dim x is the first dimension of |
12183 | | // potential vector variables |
12184 | 399 | std::map<int, int> oMapDimIdToCount; |
12185 | 399 | int nVarXId = -1; |
12186 | 399 | int nVarYId = -1; |
12187 | 399 | int nVarZId = -1; |
12188 | 399 | int nVarTimeId = -1; |
12189 | 399 | int nVarTimeDimId = -1; |
12190 | 399 | bool bIsVectorOnly = true; |
12191 | 399 | int nProfileDimId = -1; |
12192 | 399 | int nParentIndexVarID = -1; |
12193 | | |
12194 | 6.19k | for (int v = 0; v < nVars; v++) |
12195 | 5.79k | { |
12196 | 5.79k | int nVarDims; |
12197 | 5.79k | NCDF_ERR_RET(nc_inq_varndims(nCdfId, v, &nVarDims)); |
12198 | | // Should we ignore this variable? |
12199 | 5.79k | char szTemp[NC_MAX_NAME + 1]; |
12200 | 5.79k | szTemp[0] = '\0'; |
12201 | 5.79k | NCDF_ERR_RET(nc_inq_varname(nCdfId, v, szTemp)); |
12202 | | |
12203 | 5.79k | if (strstr(szTemp, "_node_coordinates") || |
12204 | 5.79k | strstr(szTemp, "_node_count")) |
12205 | 0 | { |
12206 | | // Ignore CF-1.8 Simple Geometries helper variables |
12207 | 0 | continue; |
12208 | 0 | } |
12209 | | |
12210 | 5.79k | if (nVarDims == 1 && (NCDFIsVarLongitude(nCdfId, -1, szTemp) || |
12211 | 139 | NCDFIsVarProjectionX(nCdfId, -1, szTemp))) |
12212 | 0 | { |
12213 | 0 | nVarXId = v; |
12214 | 0 | } |
12215 | 5.79k | else if (nVarDims == 1 && (NCDFIsVarLatitude(nCdfId, -1, szTemp) || |
12216 | 139 | NCDFIsVarProjectionY(nCdfId, -1, szTemp))) |
12217 | 0 | { |
12218 | 0 | nVarYId = v; |
12219 | 0 | } |
12220 | 5.79k | else if (nVarDims == 1 && NCDFIsVarVerticalCoord(nCdfId, -1, szTemp)) |
12221 | 0 | { |
12222 | 0 | nVarZId = v; |
12223 | 0 | } |
12224 | 5.79k | else |
12225 | 5.79k | { |
12226 | 5.79k | char *pszVarFullName = nullptr; |
12227 | 5.79k | CPLErr eErr = NCDFGetVarFullName(nCdfId, v, &pszVarFullName); |
12228 | 5.79k | if (eErr != CE_None) |
12229 | 0 | { |
12230 | 0 | CPLFree(pszVarFullName); |
12231 | 0 | continue; |
12232 | 0 | } |
12233 | 5.79k | bool bIgnoreVar = |
12234 | 5.79k | (CSLFindString(papszIgnoreVars, pszVarFullName) != -1); |
12235 | 5.79k | CPLFree(pszVarFullName); |
12236 | 5.79k | if (bIgnoreVar) |
12237 | 74 | { |
12238 | 74 | if (nVarDims == 1 && NCDFIsVarTimeCoord(nCdfId, -1, szTemp)) |
12239 | 0 | { |
12240 | 0 | nVarTimeId = v; |
12241 | 0 | nc_inq_vardimid(nCdfId, v, &nVarTimeDimId); |
12242 | 0 | } |
12243 | 74 | else if (nVarDims > 1) |
12244 | 0 | { |
12245 | 0 | (*pnIgnoredVars)++; |
12246 | 0 | CPLDebug("GDAL_netCDF", "variable #%d [%s] was ignored", v, |
12247 | 0 | szTemp); |
12248 | 0 | } |
12249 | 74 | } |
12250 | | // Only accept 2+D vars. |
12251 | 5.72k | else if (nVarDims >= 2) |
12252 | 474 | { |
12253 | 474 | bool bRasterCandidate = true; |
12254 | | // Identify variables that might be vector variables |
12255 | 474 | if (nVarDims == 2) |
12256 | 209 | { |
12257 | 209 | int anDimIds[2] = {-1, -1}; |
12258 | 209 | nc_inq_vardimid(nCdfId, v, anDimIds); |
12259 | | |
12260 | 209 | nc_type vartype = NC_NAT; |
12261 | 209 | nc_inq_vartype(nCdfId, v, &vartype); |
12262 | | |
12263 | 209 | char szDimNameFirst[NC_MAX_NAME + 1]; |
12264 | 209 | char szDimNameSecond[NC_MAX_NAME + 1]; |
12265 | 209 | szDimNameFirst[0] = '\0'; |
12266 | 209 | szDimNameSecond[0] = '\0'; |
12267 | 209 | if (vartype == NC_CHAR && |
12268 | 209 | nc_inq_dimname(nCdfId, anDimIds[0], szDimNameFirst) == |
12269 | 12 | NC_NOERR && |
12270 | 209 | nc_inq_dimname(nCdfId, anDimIds[1], szDimNameSecond) == |
12271 | 12 | NC_NOERR && |
12272 | 209 | !NCDFIsVarLongitude(nCdfId, -1, szDimNameSecond) && |
12273 | 209 | !NCDFIsVarProjectionX(nCdfId, -1, szDimNameSecond) && |
12274 | 209 | !NCDFIsVarLatitude(nCdfId, -1, szDimNameFirst) && |
12275 | 209 | !NCDFIsVarProjectionY(nCdfId, -1, szDimNameFirst)) |
12276 | 12 | { |
12277 | 12 | anPotentialVectorVarID.push_back(v); |
12278 | 12 | oMapDimIdToCount[anDimIds[0]]++; |
12279 | 12 | if (strstr(szDimNameSecond, "_max_width")) |
12280 | 12 | { |
12281 | 12 | bRasterCandidate = false; |
12282 | 12 | } |
12283 | 0 | else |
12284 | 0 | { |
12285 | 0 | std::array<int, 3> oKey{anDimIds[0], anDimIds[1], |
12286 | 0 | vartype}; |
12287 | 0 | oMap2DDimsToGroupAndVar[oKey].emplace_back( |
12288 | 0 | std::pair(nCdfId, v)); |
12289 | 0 | } |
12290 | 12 | } |
12291 | 197 | else |
12292 | 197 | { |
12293 | 197 | std::array<int, 3> oKey{anDimIds[0], anDimIds[1], |
12294 | 197 | vartype}; |
12295 | 197 | oMap2DDimsToGroupAndVar[oKey].emplace_back( |
12296 | 197 | std::pair(nCdfId, v)); |
12297 | 197 | bIsVectorOnly = false; |
12298 | 197 | } |
12299 | 209 | } |
12300 | 265 | else |
12301 | 265 | { |
12302 | 265 | bIsVectorOnly = false; |
12303 | 265 | } |
12304 | 474 | if (bKeepRasters && bRasterCandidate) |
12305 | 0 | { |
12306 | 0 | *pnGroupId = nCdfId; |
12307 | 0 | *pnVarId = v; |
12308 | 0 | nRasterVars++; |
12309 | 0 | } |
12310 | 474 | } |
12311 | 5.25k | else if (nVarDims == 1) |
12312 | 138 | { |
12313 | 138 | nc_type atttype = NC_NAT; |
12314 | 138 | size_t attlen = 0; |
12315 | 138 | if (nc_inq_att(nCdfId, v, "instance_dimension", &atttype, |
12316 | 138 | &attlen) == NC_NOERR && |
12317 | 138 | atttype == NC_CHAR && attlen < NC_MAX_NAME) |
12318 | 0 | { |
12319 | 0 | char szInstanceDimension[NC_MAX_NAME + 1]; |
12320 | 0 | if (nc_get_att_text(nCdfId, v, "instance_dimension", |
12321 | 0 | szInstanceDimension) == NC_NOERR) |
12322 | 0 | { |
12323 | 0 | szInstanceDimension[attlen] = 0; |
12324 | 0 | int status = nc_inq_dimid(nCdfId, szInstanceDimension, |
12325 | 0 | &nProfileDimId); |
12326 | 0 | if (status == NC_NOERR) |
12327 | 0 | nParentIndexVarID = v; |
12328 | 0 | else |
12329 | 0 | nProfileDimId = -1; |
12330 | 0 | if (status == NC_EBADDIM) |
12331 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
12332 | 0 | "Attribute instance_dimension='%s' refers " |
12333 | 0 | "to a non existing dimension", |
12334 | 0 | szInstanceDimension); |
12335 | 0 | else |
12336 | 0 | NCDF_ERR(status); |
12337 | 0 | } |
12338 | 0 | } |
12339 | 138 | if (v != nParentIndexVarID) |
12340 | 138 | { |
12341 | 138 | anPotentialVectorVarID.push_back(v); |
12342 | 138 | int nDimId = -1; |
12343 | 138 | nc_inq_vardimid(nCdfId, v, &nDimId); |
12344 | 138 | oMapDimIdToCount[nDimId]++; |
12345 | 138 | } |
12346 | 138 | } |
12347 | 5.79k | } |
12348 | 5.79k | } |
12349 | | |
12350 | | // If we are opened in raster-only mode and that there are only 1D or 2D |
12351 | | // variables and that the 2D variables have no X/Y dim, and all |
12352 | | // variables refer to the same main dimension (or 2 dimensions for |
12353 | | // featureType=profile), then it is a pure vector dataset |
12354 | 399 | CPLString osFeatureType( |
12355 | 399 | CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#featureType", "")); |
12356 | 399 | if (bKeepRasters && !bKeepVectors && bIsVectorOnly && nRasterVars > 0 && |
12357 | 399 | !anPotentialVectorVarID.empty() && |
12358 | 399 | (oMapDimIdToCount.size() == 1 || |
12359 | 0 | (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2 && |
12360 | 0 | nProfileDimId >= 0))) |
12361 | 0 | { |
12362 | 0 | anPotentialVectorVarID.resize(0); |
12363 | 0 | } |
12364 | 399 | else |
12365 | 399 | { |
12366 | 399 | *pnRasterVars += nRasterVars; |
12367 | 399 | } |
12368 | | |
12369 | 399 | if (!anPotentialVectorVarID.empty() && bKeepVectors) |
12370 | 61 | { |
12371 | | // Take the dimension that is referenced the most times. |
12372 | 61 | if (!(oMapDimIdToCount.size() == 1 || |
12373 | 61 | (EQUAL(osFeatureType, "profile") && |
12374 | 38 | oMapDimIdToCount.size() == 2 && nProfileDimId >= 0))) |
12375 | 38 | { |
12376 | 38 | CPLError(CE_Warning, CPLE_AppDefined, |
12377 | 38 | "The dataset has several variables that could be " |
12378 | 38 | "identified as vector fields, but not all share the same " |
12379 | 38 | "primary dimension. Consequently they will be ignored."); |
12380 | 38 | } |
12381 | 23 | else |
12382 | 23 | { |
12383 | 23 | if (nVarTimeId >= 0 && |
12384 | 23 | oMapDimIdToCount.find(nVarTimeDimId) != oMapDimIdToCount.end()) |
12385 | 0 | { |
12386 | 0 | anPotentialVectorVarID.push_back(nVarTimeId); |
12387 | 0 | } |
12388 | 23 | CreateGrpVectorLayers(nCdfId, osFeatureType, anPotentialVectorVarID, |
12389 | 23 | oMapDimIdToCount, nVarXId, nVarYId, nVarZId, |
12390 | 23 | nProfileDimId, nParentIndexVarID, |
12391 | 23 | bKeepRasters); |
12392 | 23 | } |
12393 | 61 | } |
12394 | | |
12395 | | // Recurse on sub-groups. |
12396 | 399 | int nSubGroups = 0; |
12397 | 399 | int *panSubGroupIds = nullptr; |
12398 | 399 | NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds); |
12399 | 399 | for (int i = 0; i < nSubGroups; i++) |
12400 | 0 | { |
12401 | 0 | FilterVars(panSubGroupIds[i], bKeepRasters, bKeepVectors, |
12402 | 0 | papszIgnoreVars, pnRasterVars, pnGroupId, pnVarId, |
12403 | 0 | pnIgnoredVars, oMap2DDimsToGroupAndVar); |
12404 | 0 | } |
12405 | 399 | CPLFree(panSubGroupIds); |
12406 | | |
12407 | 399 | return CE_None; |
12408 | 399 | } |
12409 | | |
12410 | | // Create vector layers from given potentially identified vector variables |
12411 | | // resulting from the scanning of a NetCDF (or group) ID. |
12412 | | CPLErr netCDFDataset::CreateGrpVectorLayers( |
12413 | | int nCdfId, const CPLString &osFeatureType, |
12414 | | const std::vector<int> &anPotentialVectorVarID, |
12415 | | const std::map<int, int> &oMapDimIdToCount, int nVarXId, int nVarYId, |
12416 | | int nVarZId, int nProfileDimId, int nParentIndexVarID, bool bKeepRasters) |
12417 | 23 | { |
12418 | 23 | char *pszGroupName = nullptr; |
12419 | 23 | NCDFGetGroupFullName(nCdfId, &pszGroupName); |
12420 | 23 | if (pszGroupName == nullptr || pszGroupName[0] == '\0') |
12421 | 23 | { |
12422 | 23 | CPLFree(pszGroupName); |
12423 | 23 | pszGroupName = CPLStrdup(CPLGetBasenameSafe(osFilename).c_str()); |
12424 | 23 | } |
12425 | 23 | OGRwkbGeometryType eGType = wkbUnknown; |
12426 | 23 | CPLString osLayerName = CSLFetchNameValueDef( |
12427 | 23 | papszMetadata, "NC_GLOBAL#ogr_layer_name", pszGroupName); |
12428 | 23 | CPLFree(pszGroupName); |
12429 | 23 | papszMetadata = |
12430 | 23 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_name", nullptr); |
12431 | | |
12432 | 23 | if (EQUAL(osFeatureType, "point") || EQUAL(osFeatureType, "profile")) |
12433 | 0 | { |
12434 | 0 | papszMetadata = |
12435 | 0 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#featureType", nullptr); |
12436 | 0 | eGType = wkbPoint; |
12437 | 0 | } |
12438 | | |
12439 | 23 | const char *pszLayerType = |
12440 | 23 | CSLFetchNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type"); |
12441 | 23 | if (pszLayerType != nullptr) |
12442 | 0 | { |
12443 | 0 | eGType = OGRFromOGCGeomType(pszLayerType); |
12444 | 0 | papszMetadata = |
12445 | 0 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_layer_type", nullptr); |
12446 | 0 | } |
12447 | | |
12448 | 23 | CPLString osGeometryField = |
12449 | 23 | CSLFetchNameValueDef(papszMetadata, "NC_GLOBAL#ogr_geometry_field", ""); |
12450 | 23 | papszMetadata = |
12451 | 23 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#ogr_geometry_field", nullptr); |
12452 | | |
12453 | 23 | int nFirstVarId = -1; |
12454 | 23 | int nVectorDim = oMapDimIdToCount.rbegin()->first; |
12455 | 23 | if (EQUAL(osFeatureType, "profile") && oMapDimIdToCount.size() == 2) |
12456 | 0 | { |
12457 | 0 | if (nVectorDim == nProfileDimId) |
12458 | 0 | nVectorDim = oMapDimIdToCount.begin()->first; |
12459 | 0 | } |
12460 | 23 | else |
12461 | 23 | { |
12462 | 23 | nProfileDimId = -1; |
12463 | 23 | } |
12464 | 23 | for (size_t j = 0; j < anPotentialVectorVarID.size(); j++) |
12465 | 23 | { |
12466 | 23 | int anDimIds[2] = {-1, -1}; |
12467 | 23 | nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds); |
12468 | 23 | if (nVectorDim == anDimIds[0]) |
12469 | 23 | { |
12470 | 23 | nFirstVarId = anPotentialVectorVarID[j]; |
12471 | 23 | break; |
12472 | 23 | } |
12473 | 23 | } |
12474 | | |
12475 | | // In case where coordinates are explicitly specified for one of the |
12476 | | // field/variable, use them in priority over the ones that might have been |
12477 | | // identified above. |
12478 | 23 | char *pszCoordinates = nullptr; |
12479 | 23 | if (NCDFGetAttr(nCdfId, nFirstVarId, "coordinates", &pszCoordinates) == |
12480 | 23 | CE_None) |
12481 | 9 | { |
12482 | 9 | char **papszTokens = NCDFTokenizeCoordinatesAttribute(pszCFCoordinates); |
12483 | 9 | for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; |
12484 | 9 | i++) |
12485 | 0 | { |
12486 | 0 | if (NCDFIsVarLongitude(nCdfId, -1, papszTokens[i]) || |
12487 | 0 | NCDFIsVarProjectionX(nCdfId, -1, papszTokens[i])) |
12488 | 0 | { |
12489 | 0 | nVarXId = -1; |
12490 | 0 | CPL_IGNORE_RET_VAL( |
12491 | 0 | nc_inq_varid(nCdfId, papszTokens[i], &nVarXId)); |
12492 | 0 | } |
12493 | 0 | else if (NCDFIsVarLatitude(nCdfId, -1, papszTokens[i]) || |
12494 | 0 | NCDFIsVarProjectionY(nCdfId, -1, papszTokens[i])) |
12495 | 0 | { |
12496 | 0 | nVarYId = -1; |
12497 | 0 | CPL_IGNORE_RET_VAL( |
12498 | 0 | nc_inq_varid(nCdfId, papszTokens[i], &nVarYId)); |
12499 | 0 | } |
12500 | 0 | else if (NCDFIsVarVerticalCoord(nCdfId, -1, papszTokens[i])) |
12501 | 0 | { |
12502 | 0 | nVarZId = -1; |
12503 | 0 | CPL_IGNORE_RET_VAL( |
12504 | 0 | nc_inq_varid(nCdfId, papszTokens[i], &nVarZId)); |
12505 | 0 | } |
12506 | 0 | } |
12507 | 9 | CSLDestroy(papszTokens); |
12508 | 9 | } |
12509 | 23 | CPLFree(pszCoordinates); |
12510 | | |
12511 | | // Check that the X,Y,Z vars share 1D and share the same dimension as |
12512 | | // attribute variables. |
12513 | 23 | if (nVarXId >= 0 && nVarYId >= 0) |
12514 | 0 | { |
12515 | 0 | int nVarDimCount = -1; |
12516 | 0 | int nVarDimId = -1; |
12517 | 0 | if (nc_inq_varndims(nCdfId, nVarXId, &nVarDimCount) != NC_NOERR || |
12518 | 0 | nVarDimCount != 1 || |
12519 | 0 | nc_inq_vardimid(nCdfId, nVarXId, &nVarDimId) != NC_NOERR || |
12520 | 0 | nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim) || |
12521 | 0 | nc_inq_varndims(nCdfId, nVarYId, &nVarDimCount) != NC_NOERR || |
12522 | 0 | nVarDimCount != 1 || |
12523 | 0 | nc_inq_vardimid(nCdfId, nVarYId, &nVarDimId) != NC_NOERR || |
12524 | 0 | nVarDimId != ((nProfileDimId >= 0) ? nProfileDimId : nVectorDim)) |
12525 | 0 | { |
12526 | 0 | nVarXId = nVarYId = -1; |
12527 | 0 | } |
12528 | 0 | else if (nVarZId >= 0 && |
12529 | 0 | (nc_inq_varndims(nCdfId, nVarZId, &nVarDimCount) != NC_NOERR || |
12530 | 0 | nVarDimCount != 1 || |
12531 | 0 | nc_inq_vardimid(nCdfId, nVarZId, &nVarDimId) != NC_NOERR || |
12532 | 0 | nVarDimId != nVectorDim)) |
12533 | 0 | { |
12534 | 0 | nVarZId = -1; |
12535 | 0 | } |
12536 | 0 | } |
12537 | | |
12538 | 23 | if (eGType == wkbUnknown && nVarXId >= 0 && nVarYId >= 0) |
12539 | 0 | { |
12540 | 0 | eGType = wkbPoint; |
12541 | 0 | } |
12542 | 23 | if (eGType == wkbPoint && nVarXId >= 0 && nVarYId >= 0 && nVarZId >= 0) |
12543 | 0 | { |
12544 | 0 | eGType = wkbPoint25D; |
12545 | 0 | } |
12546 | 23 | if (eGType == wkbUnknown && osGeometryField.empty()) |
12547 | 23 | { |
12548 | 23 | eGType = wkbNone; |
12549 | 23 | } |
12550 | | |
12551 | | // Read projection info |
12552 | 23 | char **papszMetadataBackup = CSLDuplicate(papszMetadata); |
12553 | 23 | ReadAttributes(nCdfId, nFirstVarId); |
12554 | 23 | if (!this->bSGSupport) |
12555 | 23 | SetProjectionFromVar(nCdfId, nFirstVarId, true); |
12556 | 23 | const char *pszValue = FetchAttr(nCdfId, nFirstVarId, CF_GRD_MAPPING); |
12557 | 23 | char *pszGridMapping = (pszValue ? CPLStrdup(pszValue) : nullptr); |
12558 | 23 | CSLDestroy(papszMetadata); |
12559 | 23 | papszMetadata = papszMetadataBackup; |
12560 | | |
12561 | 23 | OGRSpatialReference *poSRS = nullptr; |
12562 | 23 | if (!m_oSRS.IsEmpty()) |
12563 | 0 | { |
12564 | 0 | poSRS = m_oSRS.Clone(); |
12565 | 0 | } |
12566 | | // Reset if there's a 2D raster |
12567 | 23 | m_bHasProjection = false; |
12568 | 23 | m_bHasGeoTransform = false; |
12569 | | |
12570 | 23 | if (!bKeepRasters) |
12571 | 23 | { |
12572 | | // Strip out uninteresting metadata. |
12573 | 23 | papszMetadata = |
12574 | 23 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#Conventions", nullptr); |
12575 | 23 | papszMetadata = |
12576 | 23 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#GDAL", nullptr); |
12577 | 23 | papszMetadata = |
12578 | 23 | CSLSetNameValue(papszMetadata, "NC_GLOBAL#history", nullptr); |
12579 | 23 | } |
12580 | | |
12581 | 23 | std::shared_ptr<netCDFLayer> poLayer( |
12582 | 23 | new netCDFLayer(this, nCdfId, osLayerName, eGType, poSRS)); |
12583 | 23 | if (poSRS != nullptr) |
12584 | 0 | poSRS->Release(); |
12585 | 23 | poLayer->SetRecordDimID(nVectorDim); |
12586 | 23 | if (wkbFlatten(eGType) == wkbPoint && nVarXId >= 0 && nVarYId >= 0) |
12587 | 0 | { |
12588 | 0 | poLayer->SetXYZVars(nVarXId, nVarYId, nVarZId); |
12589 | 0 | } |
12590 | 23 | else if (!osGeometryField.empty()) |
12591 | 0 | { |
12592 | 0 | poLayer->SetWKTGeometryField(osGeometryField); |
12593 | 0 | } |
12594 | 23 | if (pszGridMapping != nullptr) |
12595 | 0 | { |
12596 | 0 | poLayer->SetGridMapping(pszGridMapping); |
12597 | 0 | CPLFree(pszGridMapping); |
12598 | 0 | } |
12599 | 23 | poLayer->SetProfile(nProfileDimId, nParentIndexVarID); |
12600 | | |
12601 | 64 | for (size_t j = 0; j < anPotentialVectorVarID.size(); j++) |
12602 | 41 | { |
12603 | 41 | int anDimIds[2] = {-1, -1}; |
12604 | 41 | nc_inq_vardimid(nCdfId, anPotentialVectorVarID[j], anDimIds); |
12605 | 41 | if (anDimIds[0] == nVectorDim || |
12606 | 41 | (nProfileDimId >= 0 && anDimIds[0] == nProfileDimId)) |
12607 | 41 | { |
12608 | | #ifdef NCDF_DEBUG |
12609 | | char szTemp2[NC_MAX_NAME + 1] = {}; |
12610 | | CPL_IGNORE_RET_VAL( |
12611 | | nc_inq_varname(nCdfId, anPotentialVectorVarID[j], szTemp2)); |
12612 | | CPLDebug("GDAL_netCDF", "Variable %s is a vector field", szTemp2); |
12613 | | #endif |
12614 | 41 | poLayer->AddField(anPotentialVectorVarID[j]); |
12615 | 41 | } |
12616 | 41 | } |
12617 | | |
12618 | 23 | if (poLayer->GetLayerDefn()->GetFieldCount() != 0 || |
12619 | 23 | poLayer->GetGeomType() != wkbNone) |
12620 | 23 | { |
12621 | 23 | papoLayers.push_back(poLayer); |
12622 | 23 | } |
12623 | | |
12624 | 23 | return CE_None; |
12625 | 23 | } |
12626 | | |
12627 | | // Get all coordinate and boundary variables full names referenced in |
12628 | | // a given a NetCDF (or group) ID and its sub-groups. |
12629 | | // These variables are identified in other variable's |
12630 | | // "coordinates" and "bounds" attribute. |
12631 | | // Searching coordinate and boundary variables may need to explore |
12632 | | // parents groups (or other groups in case of reference given in form of an |
12633 | | // absolute path). |
12634 | | // See CF sections 5.2, 5.6 and 7.1 |
12635 | | static CPLErr NCDFGetCoordAndBoundVarFullNames(int nCdfId, char ***ppapszVars) |
12636 | 399 | { |
12637 | 399 | int nVars = 0; |
12638 | 399 | NCDF_ERR(nc_inq(nCdfId, nullptr, &nVars, nullptr, nullptr)); |
12639 | | |
12640 | 6.19k | for (int v = 0; v < nVars; v++) |
12641 | 5.79k | { |
12642 | 5.79k | char *pszTemp = nullptr; |
12643 | 5.79k | char **papszTokens = nullptr; |
12644 | 5.79k | if (NCDFGetAttr(nCdfId, v, "coordinates", &pszTemp) == CE_None) |
12645 | 92 | papszTokens = NCDFTokenizeCoordinatesAttribute(pszTemp); |
12646 | 5.79k | CPLFree(pszTemp); |
12647 | 5.79k | pszTemp = nullptr; |
12648 | 5.79k | if (NCDFGetAttr(nCdfId, v, "bounds", &pszTemp) == CE_None && |
12649 | 5.79k | pszTemp != nullptr && !EQUAL(pszTemp, "")) |
12650 | 0 | papszTokens = CSLAddString(papszTokens, pszTemp); |
12651 | 5.79k | CPLFree(pszTemp); |
12652 | 115k | for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; |
12653 | 109k | i++) |
12654 | 109k | { |
12655 | 109k | char *pszVarFullName = nullptr; |
12656 | 109k | if (NCDFResolveVarFullName(nCdfId, papszTokens[i], |
12657 | 109k | &pszVarFullName) == CE_None) |
12658 | 753 | *ppapszVars = CSLAddString(*ppapszVars, pszVarFullName); |
12659 | 109k | CPLFree(pszVarFullName); |
12660 | 109k | } |
12661 | 5.79k | CSLDestroy(papszTokens); |
12662 | 5.79k | } |
12663 | | |
12664 | | // Recurse on sub-groups. |
12665 | 399 | int nSubGroups; |
12666 | 399 | int *panSubGroupIds = nullptr; |
12667 | 399 | NCDFGetSubGroups(nCdfId, &nSubGroups, &panSubGroupIds); |
12668 | 399 | for (int i = 0; i < nSubGroups; i++) |
12669 | 0 | { |
12670 | 0 | NCDFGetCoordAndBoundVarFullNames(panSubGroupIds[i], ppapszVars); |
12671 | 0 | } |
12672 | 399 | CPLFree(panSubGroupIds); |
12673 | | |
12674 | 399 | return CE_None; |
12675 | 399 | } |
12676 | | |
12677 | | // Check if give type is user defined |
12678 | | bool NCDFIsUserDefinedType(int /*ncid*/, int type) |
12679 | 0 | { |
12680 | 0 | return type >= NC_FIRSTUSERTYPEID; |
12681 | 0 | } |
12682 | | |
12683 | | char **NCDFTokenizeCoordinatesAttribute(const char *pszCoordinates) |
12684 | 101 | { |
12685 | | // CF conventions use space as the separator for variable names in the |
12686 | | // coordinates attribute, but some products such as |
12687 | | // https://sentinel.esa.int/web/sentinel/technical-guides/sentinel-3-synergy/products-algorithms/level-2-aod-algorithms-and-products/level-2-aod-products-description |
12688 | | // use comma. |
12689 | 101 | return CSLTokenizeString2(pszCoordinates, ", ", 0); |
12690 | 101 | } |