1 /******************************************************************************
2  *
3  * Project:  netCDF read/write Driver
4  * Purpose:  GDAL bindings over netCDF library.
5  * Author:   Winor Chen <wchen329 at wisc.edu>
6  *
7  ******************************************************************************
8  * Copyright (c) 2019, Winor Chen <wchen329 at wisc.edu>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 #ifndef __NETCDFSGWRITERUTIL_H__
29 #define __NETCDFSGWRITERUTIL_H__
30 #include <cstdio>
31 #include <queue>
32 #include <typeinfo>
33 #include <vector>
34 #include "cpl_vsi.h"
35 #include "ogr_core.h"
36 #include "ogrsf_frmts.h"
37 #include "netcdflayersg.h"
38 #include "netcdfsg.h"
39 #include "netcdfvirtual.h"
40 
41 namespace nccfdriver
42 {
43 
44     /* OGR_SGeometry_Feature
45      * Constructs over a OGRFeature
46      * gives some basic information about that SGeometry Feature such as
47      * Hold references... limited to scope of its references
48      * - what's its geometry type
49      * - how much total points it has
50      * - how many parts it has
51      * - a vector of counts of points for each part
52      */
53     class SGeometry_Feature
54     {
55         bool hasInteriorRing;
56         const OGRGeometry * geometry_ref;
57         geom_t type;
58         size_t total_point_count;
59         size_t total_part_count;
60         std::vector<size_t> ppart_node_count;
61         std::vector<bool> part_at_ind_interior; // for use with Multipolygons ONLY
62         mutable OGRPoint pt_buffer;
63 
64         public:
getType()65             geom_t getType() { return this->type; }
getTotalNodeCount()66             size_t getTotalNodeCount() { return this->total_point_count; }
getTotalPartCount()67             size_t getTotalPartCount() { return this->total_part_count;  }
getPerPartNodeCount()68             std::vector<size_t> & getPerPartNodeCount() { return this->ppart_node_count; }
69             const OGRPoint& getPoint(size_t part_no, int point_index) const;
70             explicit SGeometry_Feature(OGRFeature&);
getHasInteriorRing()71             bool getHasInteriorRing() { return this->hasInteriorRing; }
IsPartAtIndInteriorRing(size_t ind)72             bool IsPartAtIndInteriorRing(size_t ind) { return this->part_at_ind_interior[ind]; } // ONLY used for Multipolygon
73     };
74 
75     /* A memory buffer with a soft limit
76      * Has basic capability of over quota checking, and memory counting
77      */
78     class WBuffer
79     {
80         unsigned long long used_mem = 0;
81 
82         public:
83             /* addCount(...)
84              * Takes in a size, and directly adds that size to memory count
85              */
86              void addCount(unsigned long long memuse);
87 
88             /* subCount(...)
89              * Directly subtracts the specified size from used_mem
90              */
91              void subCount(unsigned long long memfree);
getUsage()92              unsigned long long& getUsage() { return used_mem; }
93 
reset()94              void reset() { this->used_mem = 0; }
95 
WBuffer()96              WBuffer() {}
97     };
98 
99 
100 
101     /* OGR_SGFS_Transaction
102      * Abstract class for a committable transaction
103      *
104      */
105     class OGR_SGFS_Transaction
106     {
107         int varId = INVALID_VAR_ID;
108 
109         public:
110             /* int commit(...);
111              * Arguments: int ncid, the dataset to write to
112              *            int write_loc, the index in which to write to
113              * Implementation: should write the transaction to netCDF file
114              *
115              */
116             virtual void commit(netCDFVID& n, size_t write_loc) = 0;
117 
118             /* unsigned long long count(...)
119              * Implementation: supposed to return an approximate count of memory usage
120              * Most classes will implement with sizeof(*this), except if otherwise uncounted for dynamic allocation is involved.
121              */
122             virtual unsigned long long count() = 0;
123 
124             /* appendToLog
125              * Implementation - given a file pointer, a transaction will be written to that log file in the format:
126              * -
127              * transactionVarId - sizeof(int) bytes
128              * NC_TYPE - sizeof(int) bytes
129              * (nc_char only) OP - 1 byte (0 if does not require COUNT or non-zero i.e. 1 if does)
130              * (nc_char only): SIZE of data - sizeof(size_t) bytes
131              * DATA - size depends on NC_TYPE
132              */
133             virtual void appendToLog(VSILFILE*) = 0;
134 
135             /* ~OGR_SGFS_Transaction()
136              * Empty. Simply here to stop the compiler from complaining...
137              */
~OGR_SGFS_Transaction()138             virtual ~OGR_SGFS_Transaction() {}
139 
140 
141             /* OGR_SGFS_Transaction()
142              * Empty. Simply here to stop one of the CI machines from complaining...
143              */
OGR_SGFS_Transaction()144             OGR_SGFS_Transaction() {}
145 
146             /* void getVarId(...);
147              * Gets the var in which to commit the transaction to.
148              */
getVarId()149             int getVarId() { return this->varId; }
150 
151             /* nc_type getType
152              * Returns the type of transaction being saved
153              */
154             virtual nc_type getType() = 0;
155 
156             /* void setVarId(...);
157              * Sets the var in which to commit the transaction to.
158              */
setVarId(int vId)159             void setVarId(int vId) { this->varId = vId; }
160 
161     };
162 
163     typedef std::map<int, void*> NCWMap;
164     typedef std::pair<int, void*> NCWEntry; // NC Writer Entry
165     typedef std::unique_ptr<OGR_SGFS_Transaction> MTPtr; // a.k.a Managed Transaction Ptr
166 
genericLogAppend(T_c_type r,int vId,VSILFILE * f)167     template<class T_c_type, nc_type T_nc_type> void genericLogAppend(T_c_type r, int vId, VSILFILE* f)
168     {
169         T_c_type rep = r;
170         int varId = vId;
171         int type = T_nc_type;
172         VSIFWriteL(&varId, sizeof(int), 1, f); // write varID data
173         VSIFWriteL(&type, sizeof(int), 1, f); // write NC type
174         VSIFWriteL(&rep, sizeof(T_c_type), 1, f); // write data
175     }
176 
genericLogDataRead(int varId,VSILFILE * f)177     template<class T_c_type, class T_r_type> MTPtr genericLogDataRead(int varId, VSILFILE* f)
178     {
179         T_r_type data;
180         if(!VSIFReadL(&data, sizeof(T_r_type), 1, f))
181         {
182              return MTPtr(nullptr); // invalid read case
183         }
184         return MTPtr(new T_c_type(varId, data));
185     }
186 
187     /* OGR_SGFS_NC_Char_Transaction
188      * Writes to an NC_CHAR variable
189      */
190     class OGR_SGFS_NC_Char_Transaction : public OGR_SGFS_Transaction
191     {
192         std::string char_rep;
193 
194         public:
commit(netCDFVID & n,size_t write_loc)195             void commit(netCDFVID& n, size_t write_loc) override { n.nc_put_vvar1_text(OGR_SGFS_Transaction::getVarId(), &write_loc, char_rep.c_str()); }
count()196             unsigned long long count() override { return char_rep.size() + sizeof(*this); } // account for actual character representation, this class
197             void appendToLog(VSILFILE* f) override;
getType()198             nc_type getType() override { return NC_CHAR; }
OGR_SGFS_NC_Char_Transaction(int i_varId,const char * pszVal)199             OGR_SGFS_NC_Char_Transaction(int i_varId, const char* pszVal) :
200                char_rep(pszVal)
201             {
202                 OGR_SGFS_Transaction::setVarId(i_varId);
203             }
204     };
205 
206     /* OGR_SGFS_NC_CharA_Transaction
207      * Writes to an NC_CHAR variable, using vara instead of var1
208      * Used to store 2D character array values, specifically
209      */
210     class OGR_SGFS_NC_CharA_Transaction : public OGR_SGFS_Transaction
211     {
212         std::string char_rep;
213         size_t counts[2];
214 
215         public:
commit(netCDFVID & n,size_t write_loc)216             void commit(netCDFVID& n, size_t write_loc) override { size_t ind[2] = {write_loc, 0}; n.nc_put_vvara_text(OGR_SGFS_Transaction::getVarId(), ind, counts, char_rep.c_str()); }
count()217             unsigned long long count() override { return char_rep.size() + sizeof(*this); } // account for actual character representation, this class
218             void appendToLog(VSILFILE* f) override;
getType()219             nc_type getType() override { return NC_CHAR; }
OGR_SGFS_NC_CharA_Transaction(int i_varId,const char * pszVal)220             OGR_SGFS_NC_CharA_Transaction(int i_varId, const char* pszVal) :
221                char_rep(pszVal),
222                counts{1, char_rep.length()}
223             {
224                 OGR_SGFS_Transaction::setVarId(i_varId);
225             }
226     };
227 
228     template <class VClass, nc_type ntype> class OGR_SGFS_NC_Transaction_Generic : public OGR_SGFS_Transaction
229     {
230         VClass rep;
231 
232         public:
commit(netCDFVID & n,size_t write_loc)233             void commit(netCDFVID& n, size_t write_loc) override
234             {
235                 n.nc_put_vvar_generic<VClass>(OGR_SGFS_Transaction::getVarId(), &write_loc, &rep);
236             }
237 
count()238             unsigned long long count() override { return sizeof(*this); }
appendToLog(VSILFILE * f)239             void appendToLog(VSILFILE* f) override
240             {
241                 genericLogAppend<VClass, ntype>(rep, OGR_SGFS_Transaction::getVarId(), f);
242             }
243 
OGR_SGFS_NC_Transaction_Generic(int i_varId,VClass in)244             OGR_SGFS_NC_Transaction_Generic(int i_varId, VClass in) :
245                rep(in)
246             {
247                 OGR_SGFS_Transaction::setVarId(i_varId);
248             }
249 
getData()250             VClass getData()
251             {
252                 return rep;
253             }
254 
getType()255             nc_type getType() override { return ntype; }
256     };
257 
258     typedef OGR_SGFS_NC_Transaction_Generic<signed char, NC_BYTE> OGR_SGFS_NC_Byte_Transaction;
259     typedef OGR_SGFS_NC_Transaction_Generic<short, NC_SHORT> OGR_SGFS_NC_Short_Transaction;
260     typedef OGR_SGFS_NC_Transaction_Generic<int, NC_INT> OGR_SGFS_NC_Int_Transaction;
261     typedef OGR_SGFS_NC_Transaction_Generic<float, NC_FLOAT> OGR_SGFS_NC_Float_Transaction;
262     typedef OGR_SGFS_NC_Transaction_Generic<double, NC_DOUBLE> OGR_SGFS_NC_Double_Transaction;
263 
264 #ifdef NETCDF_HAS_NC4
265     typedef OGR_SGFS_NC_Transaction_Generic<unsigned, NC_UINT> OGR_SGFS_NC_UInt_Transaction;
266     typedef OGR_SGFS_NC_Transaction_Generic<unsigned long long, NC_UINT64> OGR_SGFS_NC_UInt64_Transaction;
267     typedef OGR_SGFS_NC_Transaction_Generic<long long, NC_INT64> OGR_SGFS_NC_Int64_Transaction;
268     typedef OGR_SGFS_NC_Transaction_Generic<unsigned char, NC_UBYTE> OGR_SGFS_NC_UByte_Transaction;
269     typedef OGR_SGFS_NC_Transaction_Generic<unsigned short, NC_USHORT> OGR_SGFS_NC_UShort_Transaction;
270 
271     /* OGR_SGFS_NC_String_Transaction
272      * Writes to an NC_STRING variable, in a similar manner as NC_Char
273      */
274     class OGR_SGFS_NC_String_Transaction : public OGR_SGFS_Transaction
275     {
276         std::string char_rep;
277 
278         public:
commit(netCDFVID & n,size_t write_loc)279             void commit(netCDFVID& n, size_t write_loc) override
280             {
281                 const char * writable = char_rep.c_str();
282                 n.nc_put_vvar1_string(OGR_SGFS_Transaction::getVarId(), &write_loc, &(writable));
283             }
284 
count()285             unsigned long long count() override { return char_rep.size() + sizeof(*this); } // account for actual character representation, this class
286 
getType()287             nc_type getType() override { return NC_STRING; }
288 
289             void appendToLog(VSILFILE* f) override;
290 
OGR_SGFS_NC_String_Transaction(int i_varId,const char * pszVal)291             OGR_SGFS_NC_String_Transaction(int i_varId, const char* pszVal) :
292                 char_rep(pszVal)
293             {
294                 OGR_SGFS_Transaction::setVarId(i_varId);
295             }
296     };
297 
298 #endif
299 
300     /* WTransactionLog
301      * -
302      * A temporary file which contains transactions to be written to a netCDF file.
303      * Once the transaction log is created it is set on write mode, it can only be read to after startRead() is called
304      */
305     class WTransactionLog
306     {
307         bool readMode = false;
308         std::string wlogName; // name of the temporary file, should be unique
309         VSILFILE* log = nullptr;
310 
311 
312         WTransactionLog(WTransactionLog&); // avoid possible undefined behavior
313         WTransactionLog operator=(const WTransactionLog&);
314 
315         public:
logIsNull()316             bool logIsNull() { return log == nullptr; }
317             void startLog(); // always call this first to open the file
318             void startRead(); // then call this before reading it
319             void push(MTPtr);
320 
321             // read mode
322             MTPtr pop();  // to test for EOF, test to see if pointer returned is null ptr
323 
324             // construction, destruction
325             explicit WTransactionLog(const std::string& logName);
326             ~WTransactionLog();
327     };
328 
329     /* OGR_NCScribe
330      * Buffers several netCDF transactions in memory or in a log.
331      * General scribe class
332      */
333     class OGR_NCScribe
334     {
335         netCDFVID & ncvd;
336         WBuffer buf;
337         WTransactionLog wl;
338         bool singleDatumMode = false;
339 
340         std::queue<MTPtr> transactionQueue;
341         std::map<int, size_t> varWriteInds;
342         std::map<int, size_t> varMaxInds;
343 
344         public:
345            /* size_t getWriteCount()
346             * Return the total write count (happened + pending) of certain variable
347             */
getWriteCount(int varId)348            size_t getWriteCount(int varId) { return this->varMaxInds.at(varId); }
349 
350             /* void commit_transaction()
351              * Replays all transactions to disk (according to fs stipulations)
352              */
353            void commit_transaction();
354 
355            /* MTPtr pop()
356             * Get the next transaction, if it exists.
357             * If not, it will just return a shared_ptr with nullptr inside
358             */
359            MTPtr pop();
360 
361            /* void log_transacion()
362             * Saves the current queued transactions to a log.
363             */
364            void log_transaction();
365 
366            /* void enqueue_transaction()
367             * Add a transaction to perform
368             * Once a transaction is enqueued, it will only be dequeued on commit
369             */
370            void enqueue_transaction(MTPtr transactionAdd);
371 
getMemBuffer()372            WBuffer& getMemBuffer() { return buf; }
373 
374            /* OGR_SGeometry_Field_Scribe()
375             * Constructs a Field Scribe over a dataset
376             */
OGR_NCScribe(netCDFVID & ncd,const std::string & name)377            OGR_NCScribe(netCDFVID& ncd, const std::string& name) :
378                ncvd(ncd),
379                wl(name)
380            {}
381 
382            /* setSingleDatumMode(...)
383             * Enables or disables single datum mode
384             * DO NOT use this when a commit is taking place, otherwise
385             * corruption may occur...
386             */
setSingleDatumMode(bool sdm)387            void setSingleDatumMode(bool sdm) { this->singleDatumMode = sdm; }
388 
389     };
390 
391     class ncLayer_SG_Metadata
392     {
393         int & ncID; // ncid REF. which tracks ncID changes that may be made upstream
394 
395         netCDFVID& vDataset;
396         OGR_NCScribe & ncb;
397         geom_t writableType = NONE;
398         std::string containerVarName;
399         int containerVar_realID = INVALID_VAR_ID;
400         bool interiorRingDetected = false; // flips on when an interior ring polygon has been detected
401         std::vector<int> node_coordinates_varIDs;// ids in X, Y (and then possibly Z) order
402         int node_coordinates_dimID = INVALID_DIM_ID; // dim of all node_coordinates
403         int node_count_dimID = INVALID_DIM_ID; // node count dim
404         int node_count_varID = INVALID_DIM_ID;
405         int pnc_dimID = INVALID_DIM_ID; // part node count dim AND interior ring dim
406         int pnc_varID = INVALID_VAR_ID;
407         int intring_varID = INVALID_VAR_ID;
408         size_t next_write_pos_node_coord = 0;
409         size_t next_write_pos_node_count = 0;
410         size_t next_write_pos_pnc = 0;
411 
412         public:
getWritableType()413             geom_t getWritableType() { return this->writableType; }
414             void writeSGeometryFeature(SGeometry_Feature& ft);
get_containerRealID()415             int get_containerRealID() { return this->containerVar_realID; }
get_containerName()416             std::string get_containerName() { return this->containerVarName; }
get_node_count_dimID()417             int get_node_count_dimID() { return this->node_count_dimID; }
get_node_coord_dimID()418             int get_node_coord_dimID() { return this->node_coordinates_dimID; }
get_pnc_dimID()419             int get_pnc_dimID() { return this->pnc_dimID; }
get_pnc_varID()420             int get_pnc_varID() { return this->pnc_varID; }
get_intring_varID()421             int get_intring_varID() { return this->intring_varID; }
get_nodeCoordVarIDs()422             std::vector<int>& get_nodeCoordVarIDs() { return this->node_coordinates_varIDs; }
get_next_write_pos_node_coord()423             size_t get_next_write_pos_node_coord() { return this->next_write_pos_node_coord; }
get_next_write_pos_node_count()424             size_t get_next_write_pos_node_count() { return this->next_write_pos_node_count; }
get_next_write_pos_pnc()425             size_t get_next_write_pos_pnc() { return this->next_write_pos_pnc; }
getInteriorRingDetected()426             bool getInteriorRingDetected() { return this->interiorRingDetected; }
427             void initializeNewContainer(int containerVID);
428             ncLayer_SG_Metadata(int & i_ncID, geom_t geo, netCDFVID& ncdf, OGR_NCScribe& scribe);
429     };
430 
431     /* WBufferManager
432      * -
433      * Simply takes a collection of buffers in and a quota limit and sums all the usages up
434      * to establish if buffers are over the soft limit (collectively)
435      *
436      * The buffers added, however, are not memory managed by WBufferManager
437      */
438     class WBufferManager
439     {
440         unsigned long long buffer_soft_limit = 0;
441         std::vector<WBuffer*> bufs;
442 
443         public:
444             bool isOverQuota();
adjustLimit(unsigned long long lim)445             void adjustLimit(unsigned long long lim) { this->buffer_soft_limit = lim; }
addBuffer(WBuffer * b)446             void addBuffer(WBuffer* b) { this->bufs.push_back(b); }
WBufferManager(unsigned long long lim)447             explicit WBufferManager(unsigned long long lim) : buffer_soft_limit(lim){ }
448     };
449 
450 
451     // Exception Classes
452     class SGWriter_Exception : public SG_Exception
453     {
454         public:
get_err_msg()455             const char * get_err_msg() override { return "A general error occurred when writing a netCDF dataset"; }
456     };
457 
458     class SGWriter_Exception_NCWriteFailure : public SGWriter_Exception
459     {
460         std::string msg;
461 
462         public:
get_err_msg()463             const char * get_err_msg() override { return this->msg.c_str(); }
464             SGWriter_Exception_NCWriteFailure(const char * layer_name, const char * failure_name,
465                 const char * failure_type);
466     };
467 
468     class SGWriter_Exception_NCInqFailure : public SGWriter_Exception
469     {
470         std::string msg;
471 
472         public:
get_err_msg()473             const char * get_err_msg() override { return this->msg.c_str(); }
474             SGWriter_Exception_NCInqFailure(const char * layer_name, const char * failure_name,
475                 const char * failure_type);
476     };
477 
478     class SGWriter_Exception_NCDefFailure : public SGWriter_Exception
479     {
480         std::string msg;
481 
482         public:
get_err_msg()483             const char * get_err_msg() override { return this->msg.c_str(); }
484             SGWriter_Exception_NCDefFailure(const char * layer_name, const char * failure_name,
485                 const char * failure_type);
486     };
487 
488     class SGWriter_Exception_EmptyGeometry : public SGWriter_Exception
489     {
490         std::string msg;
491 
492         public:
get_err_msg()493             const char * get_err_msg() override { return this->msg.c_str(); }
SGWriter_Exception_EmptyGeometry()494             SGWriter_Exception_EmptyGeometry() : msg("An empty geometry was detected when writing a netCDF file. Empty geometries are not allowed.") {}
495     };
496 
497     class SGWriter_Exception_RingOOB: public SGWriter_Exception
498     {
499         std::string msg;
500 
501         public:
get_err_msg()502             const char * get_err_msg() override { return this->msg.c_str(); }
SGWriter_Exception_RingOOB()503             SGWriter_Exception_RingOOB() : msg("An attempt was made to read a polygon ring that does not exist.") {}
504     };
505 
506     class SGWriter_Exception_NCDelFailure : public SGWriter_Exception
507     {
508         std::string msg;
509 
510         public:
get_err_msg()511             const char * get_err_msg() override { return this->msg.c_str(); }
SGWriter_Exception_NCDelFailure(const char * layer,const char * what)512             SGWriter_Exception_NCDelFailure(const char* layer, const char* what)
513                 : msg("[" + std::string(layer) + "] Failed to delete: " + std::string(what))
514             {}
515     };
516 
517     // Helper Functions that for writing
518 
519     /* std::vector<..> writeGeometryContainer(...)
520      * Writes a geometry container of a given geometry type given the following arguments:
521      * int ncID - ncid as used in netcdf.h, group or file id
522      * std::string name - what to name this container
523      * geom_t geometry_type - the geometry type of the container
524      * std::vector<..> node_coordinate_names - variable names corresponding to each axis
525      * Only writes attributes that are for sure required. i.e. does NOT required interior ring for anything or part node count for Polygons
526      *
527      * Returns: geometry container variable ID
528      */
529     int write_Geometry_Container
530         (int ncID, const std::string& name, geom_t geometry_type, const std::vector<std::string> & node_coordinate_names);
531 
NCWMapAllocIfNeeded(int varid,NCWMap & mapAdd,size_t numEntries,std::vector<int> & v)532     template<class W_type> inline void NCWMapAllocIfNeeded(int varid, NCWMap& mapAdd, size_t numEntries, std::vector<int> & v)
533     {
534         if(mapAdd.count(varid) < 1)
535         {
536             mapAdd.insert(NCWEntry(varid,  CPLMalloc(sizeof(W_type) * numEntries)));
537             v.push_back(varid);
538         }
539     }
540 
NCWMapWriteAndCommit(int varid,NCWMap & mapAdd,size_t currentEntry,size_t numEntries,W_type data,netCDFVID & vcdf)541     template<class W_type> inline void NCWMapWriteAndCommit(int varid, NCWMap& mapAdd, size_t currentEntry, size_t numEntries, W_type data, netCDFVID& vcdf)
542     {
543         W_type* ptr = static_cast<W_type*>(mapAdd.at(varid));
544         ptr[currentEntry] = data;
545         static const size_t BEGIN = 0;
546 
547         // If all items are ready, write the array, and free it, delete the pointer
548         if (currentEntry == (numEntries - 1))
549         {
550             try
551             {
552                 // Write the whole array at once
553                 vcdf.nc_put_vvara_generic<W_type>(varid, &BEGIN, &numEntries, ptr);
554             }
555             catch (SG_Exception_VWrite_Failure& e)
556             {
557                 CPLError(CE_Warning, CPLE_FileIO, "%s", e.get_err_msg());
558             }
559 
560             CPLFree(mapAdd.at(varid));
561             mapAdd.erase(varid);
562         }
563     }
564 }
565 
566 #endif
567