/src/gdal/frmts/netcdf/netcdfsg.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: Winor Chen <wchen329 at wisc.edu> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2019, Winor Chen <wchen329 at wisc.edu> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | #include <cstdio> |
13 | | #include <cstring> |
14 | | #include <set> |
15 | | #include <vector> |
16 | | #include "netcdf.h" |
17 | | #include "netcdfdataset.h" |
18 | | #include "netcdfsg.h" |
19 | | |
20 | | namespace nccfdriver |
21 | | { |
22 | | /* Attribute Fetch |
23 | | * - |
24 | | * A function which makes it a bit easier to fetch single text attribute values |
25 | | * ncid: as used in netcdf.h |
26 | | * varID: variable id in which to look for the attribute |
27 | | * attrName: name of attribute to fine |
28 | | * alloc: a reference to a string that will be filled with the attribute (i.e. |
29 | | * truncated and filled with the return value) Returns: a reference to the |
30 | | * string to fill (a.k.a. string pointed to by alloc reference) |
31 | | */ |
32 | | std::string &attrf(int ncid, int varId, const char *attrName, |
33 | | std::string &alloc) |
34 | 13.8k | { |
35 | 13.8k | size_t len = 0; |
36 | 13.8k | nc_inq_attlen(ncid, varId, attrName, &len); |
37 | | |
38 | 13.8k | if (len < 1) |
39 | 13.4k | { |
40 | 13.4k | alloc.clear(); |
41 | 13.4k | return alloc; |
42 | 13.4k | } |
43 | | |
44 | 405 | alloc.resize(len); |
45 | 405 | memset(&alloc[0], 0, len); |
46 | | |
47 | | // Now look through this variable for the attribute |
48 | 405 | nc_get_att_text(ncid, varId, attrName, &alloc[0]); |
49 | | |
50 | 405 | return alloc; |
51 | 13.8k | } |
52 | | |
53 | | /* SGeometry_Reader |
54 | | * (implementations) |
55 | | * |
56 | | */ |
57 | | SGeometry_Reader::SGeometry_Reader(int ncId, int geoVarId) |
58 | 0 | : gc_varId(geoVarId), touple_order(0) |
59 | 0 | { |
60 | |
|
61 | 0 | char container_name[NC_MAX_NAME + 1]; |
62 | 0 | memset(container_name, 0, NC_MAX_NAME + 1); |
63 | | |
64 | | // Get geometry container name |
65 | 0 | if (nc_inq_varname(ncId, geoVarId, container_name) != NC_NOERR) |
66 | 0 | { |
67 | 0 | throw SG_Exception_Existential("new geometry container", |
68 | 0 | "the variable of the given ID"); |
69 | 0 | } |
70 | | |
71 | | // Establish string version of container_name |
72 | 0 | container_name_s = std::string(container_name); |
73 | | |
74 | | // Find geometry type |
75 | 0 | this->type = nccfdriver::getGeometryType(ncId, geoVarId); |
76 | |
|
77 | 0 | if (this->type == NONE) |
78 | 0 | { |
79 | 0 | throw SG_Exception_Existential( |
80 | 0 | static_cast<const char *>(container_name), CF_SG_GEOMETRY_TYPE); |
81 | 0 | } |
82 | | |
83 | | // Get grid mapping variable, if it exists |
84 | 0 | this->gm_varId = INVALID_VAR_ID; |
85 | 0 | if (attrf(ncId, geoVarId, CF_GRD_MAPPING, gm_name_s) != "") |
86 | 0 | { |
87 | 0 | const char *gm_name = gm_name_s.c_str(); |
88 | 0 | int gmVID; |
89 | 0 | if (nc_inq_varid(ncId, gm_name, &gmVID) == NC_NOERR) |
90 | 0 | { |
91 | 0 | this->gm_varId = gmVID; |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | // Find a list of node counts and part node count |
96 | 0 | std::string nc_name_s; |
97 | 0 | std::string pnc_name_s; |
98 | 0 | std::string ir_name_s; |
99 | 0 | int pnc_vid = INVALID_VAR_ID; |
100 | 0 | int nc_vid = INVALID_VAR_ID; |
101 | 0 | int ir_vid = INVALID_VAR_ID; |
102 | 0 | int buf; |
103 | 0 | size_t bound = 0; |
104 | 0 | size_t total_node_count = 0; // used in error checks later |
105 | 0 | size_t total_part_node_count = 0; |
106 | 0 | if (attrf(ncId, geoVarId, CF_SG_NODE_COUNT, nc_name_s) != "") |
107 | 0 | { |
108 | 0 | const char *nc_name = nc_name_s.c_str(); |
109 | 0 | nc_inq_varid(ncId, nc_name, &nc_vid); |
110 | 0 | while (nc_get_var1_int(ncId, nc_vid, &bound, &buf) == NC_NOERR) |
111 | 0 | { |
112 | 0 | if (buf < 0) |
113 | 0 | { |
114 | 0 | throw SG_Exception_Value_Violation( |
115 | 0 | static_cast<const char *>(container_name), CF_SG_NODE_COUNT, |
116 | 0 | "negative"); |
117 | 0 | } |
118 | | |
119 | 0 | this->node_counts.push_back(buf); |
120 | 0 | total_node_count += buf; |
121 | 0 | bound++; |
122 | 0 | } |
123 | 0 | } |
124 | | |
125 | 0 | if (attrf(ncId, geoVarId, CF_SG_PART_NODE_COUNT, pnc_name_s) != "") |
126 | 0 | { |
127 | 0 | const char *pnc_name = pnc_name_s.c_str(); |
128 | 0 | bound = 0; |
129 | 0 | nc_inq_varid(ncId, pnc_name, &pnc_vid); |
130 | 0 | while (nc_get_var1_int(ncId, pnc_vid, &bound, &buf) == NC_NOERR) |
131 | 0 | { |
132 | 0 | if (buf < 0) |
133 | 0 | { |
134 | 0 | throw SG_Exception_Value_Violation( |
135 | 0 | static_cast<const char *>(container_name), |
136 | 0 | CF_SG_PART_NODE_COUNT, "negative"); |
137 | 0 | } |
138 | | |
139 | 0 | this->pnode_counts.push_back(buf); |
140 | 0 | total_part_node_count += buf; |
141 | 0 | bound++; |
142 | 0 | } |
143 | 0 | } |
144 | | |
145 | 0 | if (attrf(ncId, geoVarId, CF_SG_INTERIOR_RING, ir_name_s) != "") |
146 | 0 | { |
147 | 0 | const char *ir_name = ir_name_s.c_str(); |
148 | 0 | bound = 0; |
149 | 0 | nc_inq_varid(ncId, ir_name, &ir_vid); |
150 | 0 | while (nc_get_var1_int(ncId, ir_vid, &bound, &buf) == NC_NOERR) |
151 | 0 | { |
152 | 0 | bool store = buf == 0 ? false : true; |
153 | |
|
154 | 0 | if (buf != 0 && buf != 1) |
155 | 0 | { |
156 | 0 | throw SG_Exception_Value_Required( |
157 | 0 | static_cast<const char *>(container_name), |
158 | 0 | CF_SG_INTERIOR_RING, "0 or 1"); |
159 | 0 | } |
160 | | |
161 | 0 | this->int_rings.push_back(store); |
162 | 0 | bound++; |
163 | 0 | } |
164 | 0 | } |
165 | | |
166 | | /* Enforcement of well formed CF files |
167 | | * If these are not met then the dataset is malformed and will halt further |
168 | | * processing of simple geometries. |
169 | | */ |
170 | | |
171 | | // part node count exists only when node count exists |
172 | 0 | if (pnode_counts.size() > 0 && node_counts.size() == 0) |
173 | 0 | { |
174 | 0 | throw SG_Exception_Dep(static_cast<const char *>(container_name), |
175 | 0 | CF_SG_PART_NODE_COUNT, CF_SG_NODE_COUNT); |
176 | 0 | } |
177 | | |
178 | | // part node counts and node counts don't match up in count |
179 | 0 | if (pnc_vid != INVALID_VAR_ID && total_node_count != total_part_node_count) |
180 | 0 | { |
181 | 0 | throw SG_Exception_BadSum(static_cast<const char *>(container_name), |
182 | 0 | CF_SG_PART_NODE_COUNT, CF_SG_PART_NODE_COUNT); |
183 | 0 | } |
184 | | |
185 | | // interior rings only exist when part node counts exist |
186 | 0 | if (int_rings.size() > 0 && pnode_counts.size() == 0) |
187 | 0 | { |
188 | 0 | throw SG_Exception_Dep(static_cast<const char *>(container_name), |
189 | 0 | CF_SG_INTERIOR_RING, CF_SG_PART_NODE_COUNT); |
190 | 0 | } |
191 | | |
192 | | // cardinality of part_node_counts == cardinality of interior_ring (if |
193 | | // interior ring > 0) |
194 | 0 | if (int_rings.size() > 0) |
195 | 0 | { |
196 | 0 | if (int_rings.size() != pnode_counts.size()) |
197 | 0 | { |
198 | 0 | throw SG_Exception_Dim_MM(static_cast<const char *>(container_name), |
199 | 0 | CF_SG_INTERIOR_RING, |
200 | 0 | CF_SG_PART_NODE_COUNT); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | | // lines and polygons require node_counts, multipolygons are checked with |
205 | | // part_node_count |
206 | 0 | if (this->type == POLYGON || this->type == LINE) |
207 | 0 | { |
208 | 0 | if (node_counts.size() < 1) |
209 | 0 | { |
210 | 0 | throw SG_Exception_Existential( |
211 | 0 | static_cast<const char *>(container_name), CF_SG_NODE_COUNT); |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | /* Basic Safety checks End |
216 | | */ |
217 | | |
218 | | // Create bound list |
219 | 0 | size_t rc = 0; |
220 | 0 | bound_list.push_back(0); // start with 0 |
221 | |
|
222 | 0 | if (node_counts.size() > 0) |
223 | 0 | { |
224 | 0 | for (size_t i = 0; i < node_counts.size() - 1; i++) |
225 | 0 | { |
226 | 0 | rc = rc + node_counts[i]; |
227 | 0 | bound_list.push_back(rc); |
228 | 0 | } |
229 | 0 | } |
230 | |
|
231 | 0 | std::string cart_s; |
232 | | // Node Coordinates |
233 | 0 | if (attrf(ncId, geoVarId, CF_SG_NODE_COORDINATES, cart_s) == "") |
234 | 0 | { |
235 | 0 | throw SG_Exception_Existential(container_name, CF_SG_NODE_COORDINATES); |
236 | 0 | } |
237 | | |
238 | | // Create parts count list and an offset list for parts indexing |
239 | 0 | if (this->node_counts.size() > 0) |
240 | 0 | { |
241 | 0 | int ind = 0; |
242 | 0 | int parts = 0; |
243 | 0 | int prog = 0; |
244 | 0 | int c = 0; |
245 | |
|
246 | 0 | for (size_t pcnt = 0; pcnt < pnode_counts.size(); pcnt++) |
247 | 0 | { |
248 | 0 | if (prog == 0) |
249 | 0 | pnc_bl.push_back(pcnt); |
250 | |
|
251 | 0 | if (int_rings.size() > 0 && !int_rings[pcnt]) |
252 | 0 | c++; |
253 | |
|
254 | 0 | prog = prog + pnode_counts[pcnt]; |
255 | 0 | parts++; |
256 | |
|
257 | 0 | if (prog == node_counts[ind]) |
258 | 0 | { |
259 | 0 | ind++; |
260 | 0 | this->parts_count.push_back(parts); |
261 | 0 | if (int_rings.size() > 0) |
262 | 0 | this->poly_count.push_back(c); |
263 | 0 | c = 0; |
264 | 0 | prog = 0; |
265 | 0 | parts = 0; |
266 | 0 | } |
267 | 0 | else if (prog > node_counts[ind]) |
268 | 0 | { |
269 | 0 | throw SG_Exception_BadSum(container_name, CF_SG_PART_NODE_COUNT, |
270 | 0 | CF_SG_NODE_COUNT); |
271 | 0 | } |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | // (1) the tuple order for a single point |
276 | | // (2) the variable ids with the relevant coordinate values |
277 | 0 | int X = INVALID_VAR_ID; |
278 | 0 | int Y = INVALID_VAR_ID; |
279 | 0 | int Z = INVALID_VAR_ID; |
280 | |
|
281 | 0 | char cart[NC_MAX_NAME + 1]; |
282 | 0 | memset(cart, 0, NC_MAX_NAME + 1); |
283 | 0 | strncpy(cart, cart_s.c_str(), NC_MAX_NAME); |
284 | |
|
285 | 0 | char *dim = strtok(cart, " "); |
286 | 0 | int axis_id = 0; |
287 | |
|
288 | 0 | while (dim != nullptr) |
289 | 0 | { |
290 | 0 | if (nc_inq_varid(ncId, dim, &axis_id) == NC_NOERR) |
291 | 0 | { |
292 | | |
293 | | // Check axis signature |
294 | 0 | std::string a_sig; |
295 | 0 | attrf(ncId, axis_id, CF_AXIS, a_sig); |
296 | | |
297 | | // If valid signify axis correctly |
298 | 0 | if (a_sig == "X") |
299 | 0 | { |
300 | 0 | X = axis_id; |
301 | 0 | } |
302 | 0 | else if (a_sig == "Y") |
303 | 0 | { |
304 | 0 | Y = axis_id; |
305 | 0 | } |
306 | 0 | else if (a_sig == "Z") |
307 | 0 | { |
308 | 0 | Z = axis_id; |
309 | 0 | } |
310 | 0 | else |
311 | 0 | { |
312 | 0 | throw SG_Exception_Dep(container_name, |
313 | 0 | "A node_coordinates variable", CF_AXIS); |
314 | 0 | } |
315 | | |
316 | 0 | this->touple_order++; |
317 | 0 | } |
318 | 0 | else |
319 | 0 | { |
320 | 0 | throw SG_Exception_Existential(container_name, dim); |
321 | 0 | } |
322 | | |
323 | 0 | dim = strtok(nullptr, " "); |
324 | 0 | } |
325 | | |
326 | | // Write axis in X, Y, Z order |
327 | | |
328 | 0 | if (X != INVALID_VAR_ID) |
329 | 0 | this->nodec_varIds.push_back(X); |
330 | 0 | else |
331 | 0 | { |
332 | 0 | throw SG_Exception_Existential(container_name, |
333 | 0 | "node_coordinates: X-axis"); |
334 | 0 | } |
335 | 0 | if (Y != INVALID_VAR_ID) |
336 | 0 | this->nodec_varIds.push_back(Y); |
337 | 0 | else |
338 | 0 | { |
339 | 0 | throw SG_Exception_Existential(container_name, |
340 | 0 | "node_coordinates: Y-axis"); |
341 | 0 | } |
342 | 0 | if (Z != INVALID_VAR_ID) |
343 | 0 | this->nodec_varIds.push_back(Z); |
344 | | |
345 | | /* Final Checks for node coordinates |
346 | | * (1) Each axis has one and only one dimension, dim length of each node |
347 | | * coordinates are all equal (2) total node count == dim length of each node |
348 | | * coordinates (if node_counts not empty) (3) there are at least two node |
349 | | * coordinate variable ids |
350 | | */ |
351 | |
|
352 | 0 | int all_dim = INVALID_VAR_ID; |
353 | 0 | bool dim_set = false; |
354 | | //(1) one dimension check, each node_coordinates have same dimension |
355 | 0 | for (size_t nvitr = 0; nvitr < nodec_varIds.size(); nvitr++) |
356 | 0 | { |
357 | 0 | int dimC = 0; |
358 | 0 | nc_inq_varndims(ncId, nodec_varIds[nvitr], &dimC); |
359 | |
|
360 | 0 | if (dimC != 1) |
361 | 0 | { |
362 | 0 | throw SG_Exception_Not1D(); |
363 | 0 | } |
364 | | |
365 | | // check that all have same dimension |
366 | 0 | int inter_dim[1]; |
367 | 0 | if (nc_inq_vardimid(ncId, nodec_varIds[nvitr], inter_dim) != NC_NOERR) |
368 | 0 | { |
369 | 0 | throw SG_Exception_Existential( |
370 | 0 | container_name, "one or more node_coordinate dimensions"); |
371 | 0 | } |
372 | | |
373 | 0 | if (!dim_set) |
374 | 0 | { |
375 | 0 | all_dim = inter_dim[0]; |
376 | 0 | dim_set = true; |
377 | 0 | } |
378 | | |
379 | 0 | else |
380 | 0 | { |
381 | 0 | if (inter_dim[0] != all_dim) |
382 | 0 | throw SG_Exception_Dim_MM( |
383 | 0 | container_name, "X, Y", |
384 | 0 | "in general all node coordinate axes"); |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | | // (2) check equality one |
389 | 0 | if (node_counts.size() > 0) |
390 | 0 | { |
391 | 0 | size_t diml = 0; |
392 | 0 | nc_inq_dimlen(ncId, all_dim, &diml); |
393 | |
|
394 | 0 | if (diml != total_node_count) |
395 | 0 | throw SG_Exception_BadSum(container_name, "node_count", |
396 | 0 | "node coordinate dimension length"); |
397 | 0 | } |
398 | | |
399 | | // (3) check tuple order |
400 | 0 | if (this->touple_order < 2) |
401 | 0 | { |
402 | 0 | throw SG_Exception_Existential( |
403 | 0 | container_name, |
404 | 0 | "insufficient node coordinates must have at least two axis"); |
405 | 0 | } |
406 | | |
407 | | /* Investigate for instance dimension |
408 | | * The procedure is as follows |
409 | | * |
410 | | * (1) if there's node_count, use the dimension used to index node count |
411 | | * (2) otherwise it's point (singleton) data, in this case use the node |
412 | | * coordinate dimension |
413 | | */ |
414 | 0 | size_t instance_dim_len = 0; |
415 | |
|
416 | 0 | if (node_counts.size() >= 1) |
417 | 0 | { |
418 | 0 | int nc_dims = 0; |
419 | 0 | nc_inq_varndims(ncId, nc_vid, &nc_dims); |
420 | |
|
421 | 0 | if (nc_dims != 1) |
422 | 0 | throw SG_Exception_Not1D(); |
423 | | |
424 | 0 | int nc_dim_id[1]; |
425 | |
|
426 | 0 | if (nc_inq_vardimid(ncId, nc_vid, nc_dim_id) != NC_NOERR) |
427 | 0 | { |
428 | 0 | throw SG_Exception_Existential(container_name, |
429 | 0 | "node_count dimension"); |
430 | 0 | } |
431 | | |
432 | 0 | this->inst_dimId = nc_dim_id[0]; |
433 | 0 | } |
434 | | |
435 | 0 | else |
436 | 0 | { |
437 | 0 | this->inst_dimId = all_dim; |
438 | 0 | } |
439 | | |
440 | 0 | nc_inq_dimlen(ncId, this->inst_dimId, &instance_dim_len); |
441 | |
|
442 | 0 | if (instance_dim_len == 0) |
443 | 0 | throw SG_Exception_EmptyDim(); |
444 | | |
445 | | // Set values accordingly |
446 | 0 | this->inst_dimLen = instance_dim_len; |
447 | 0 | this->pt_buffer = std::make_unique<Point>(this->touple_order); |
448 | 0 | this->gc_varId = geoVarId; |
449 | 0 | this->ncid = ncId; |
450 | 0 | } |
451 | | |
452 | | Point &SGeometry_Reader::operator[](size_t index) |
453 | 0 | { |
454 | 0 | for (int order = 0; order < touple_order; order++) |
455 | 0 | { |
456 | 0 | Point &pt = *(this->pt_buffer); |
457 | 0 | double data; |
458 | 0 | size_t real_ind = index; |
459 | | |
460 | | // Read a single coord |
461 | 0 | int err = |
462 | 0 | nc_get_var1_double(ncid, nodec_varIds[order], &real_ind, &data); |
463 | |
|
464 | 0 | if (err != NC_NOERR) |
465 | 0 | { |
466 | 0 | throw SG_Exception_BadPoint(); |
467 | 0 | } |
468 | | |
469 | 0 | pt[order] = data; |
470 | 0 | } |
471 | | |
472 | 0 | return *(this->pt_buffer); |
473 | 0 | } |
474 | | |
475 | | size_t SGeometry_Reader::get_geometry_count() |
476 | 0 | { |
477 | 0 | if (type == POINT) |
478 | 0 | { |
479 | 0 | if (this->nodec_varIds.size() < 1) |
480 | 0 | return 0; |
481 | | |
482 | | // If more than one dim, then error. Otherwise inquire its length and |
483 | | // return that |
484 | 0 | int dims; |
485 | 0 | if (nc_inq_varndims(this->ncid, nodec_varIds[0], &dims) != NC_NOERR) |
486 | 0 | return 0; |
487 | 0 | if (dims != 1) |
488 | 0 | return 0; |
489 | | |
490 | | // Find which dimension is used for x |
491 | 0 | int index; |
492 | 0 | if (nc_inq_vardimid(this->ncid, nodec_varIds[0], &index) != NC_NOERR) |
493 | 0 | { |
494 | 0 | return 0; |
495 | 0 | } |
496 | | |
497 | | // Finally find the length |
498 | 0 | size_t len; |
499 | 0 | if (nc_inq_dimlen(this->ncid, index, &len) != NC_NOERR) |
500 | 0 | { |
501 | 0 | return 0; |
502 | 0 | } |
503 | 0 | return len; |
504 | 0 | } |
505 | | |
506 | 0 | else |
507 | 0 | return this->node_counts.size(); |
508 | 0 | } |
509 | | |
510 | | static void add_to_buffer(std::vector<unsigned char> &buffer, uint8_t v) |
511 | 0 | { |
512 | 0 | buffer.push_back(v); |
513 | 0 | } |
514 | | |
515 | | static void add_to_buffer(std::vector<unsigned char> &buffer, uint32_t v) |
516 | 0 | { |
517 | 0 | const size_t old_size = buffer.size(); |
518 | 0 | buffer.resize(old_size + sizeof(v)); |
519 | 0 | memcpy(&buffer[old_size], &v, sizeof(v)); |
520 | 0 | } |
521 | | |
522 | | static void add_to_buffer(std::vector<unsigned char> &, int) = delete; |
523 | | |
524 | | static void add_to_buffer(std::vector<unsigned char> &buffer, double v) |
525 | 0 | { |
526 | 0 | const size_t old_size = buffer.size(); |
527 | 0 | buffer.resize(old_size + sizeof(v)); |
528 | 0 | memcpy(&buffer[old_size], &v, sizeof(v)); |
529 | 0 | } |
530 | | |
531 | | /* serializeToWKB |
532 | | * Takes the geometry in SGeometry at a given index and converts it into WKB |
533 | | * format. Converting SGeometry into WKB automatically allocates the required |
534 | | * buffer space and returns a buffer that MUST be free'd |
535 | | */ |
536 | | std::vector<unsigned char> SGeometry_Reader::serializeToWKB(size_t featureInd) |
537 | 0 | { |
538 | 0 | std::vector<unsigned char> ret; |
539 | 0 | int nc = 0; |
540 | 0 | size_t sb = 0; |
541 | | |
542 | | // Points don't have node_count entry... only inspect and set node_counts if |
543 | | // not a point |
544 | 0 | if (this->getGeometryType() != POINT) |
545 | 0 | { |
546 | 0 | nc = node_counts[featureInd]; |
547 | 0 | sb = bound_list[featureInd]; |
548 | 0 | } |
549 | | |
550 | | // Serialization occurs differently depending on geometry |
551 | | // The memory requirements also differ between geometries |
552 | 0 | switch (this->getGeometryType()) |
553 | 0 | { |
554 | 0 | case POINT: |
555 | 0 | inPlaceSerialize_Point(this, featureInd, ret); |
556 | 0 | break; |
557 | | |
558 | 0 | case LINE: |
559 | 0 | inPlaceSerialize_LineString(this, nc, sb, ret); |
560 | 0 | break; |
561 | | |
562 | 0 | case POLYGON: |
563 | | // A polygon has: |
564 | | // 1 byte header |
565 | | // 4 byte Type |
566 | | // 4 byte ring count (1 (exterior)) [assume only exterior rings, |
567 | | // otherwise multipolygon] For each ring: 4 byte point count, 8 byte |
568 | | // double x point count x # dimension (This is equivalent to |
569 | | // requesting enough for all points and a point count header for |
570 | | // each point) (Or 8 byte double x node count x # dimension + 4 byte |
571 | | // point count x part_count) |
572 | | |
573 | | // if interior ring, then assume that it will be a multipolygon |
574 | | // (maybe future work?) |
575 | 0 | inPlaceSerialize_PolygonExtOnly(this, nc, sb, ret); |
576 | 0 | break; |
577 | | |
578 | 0 | case MULTIPOINT: |
579 | 0 | { |
580 | 0 | uint8_t header = PLATFORM_HEADER; |
581 | 0 | uint32_t t = this->touple_order == 2 ? wkbMultiPoint |
582 | 0 | : this->touple_order == 3 ? wkbMultiPoint25D |
583 | 0 | : wkbNone; |
584 | |
|
585 | 0 | if (t == wkbNone) |
586 | 0 | throw SG_Exception_BadFeature(); |
587 | | |
588 | | // Add metadata |
589 | 0 | add_to_buffer(ret, header); |
590 | 0 | add_to_buffer(ret, t); |
591 | 0 | add_to_buffer(ret, static_cast<uint32_t>(nc)); |
592 | | |
593 | | // Add points |
594 | 0 | for (int pts = 0; pts < nc; pts++) |
595 | 0 | { |
596 | 0 | inPlaceSerialize_Point(this, static_cast<size_t>(sb + pts), |
597 | 0 | ret); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | 0 | break; |
602 | | |
603 | 0 | case MULTILINE: |
604 | 0 | { |
605 | 0 | uint8_t header = PLATFORM_HEADER; |
606 | 0 | uint32_t t = this->touple_order == 2 ? wkbMultiLineString |
607 | 0 | : this->touple_order == 3 ? wkbMultiLineString25D |
608 | 0 | : wkbNone; |
609 | |
|
610 | 0 | if (t == wkbNone) |
611 | 0 | throw SG_Exception_BadFeature(); |
612 | 0 | int32_t lc = parts_count[featureInd]; |
613 | 0 | size_t seek_begin = sb; |
614 | 0 | size_t pc_begin = |
615 | 0 | pnc_bl[featureInd]; // initialize with first part count, list |
616 | | // of part counts is contiguous |
617 | 0 | std::vector<int> pnc; |
618 | | |
619 | | // Build sub vector for part_node_counts |
620 | | // + Calculate wkbSize |
621 | 0 | for (int itr = 0; itr < lc; itr++) |
622 | 0 | { |
623 | 0 | pnc.push_back(pnode_counts[pc_begin + itr]); |
624 | 0 | } |
625 | |
|
626 | 0 | size_t cur_point = seek_begin; |
627 | 0 | size_t pcount = pnc.size(); |
628 | | |
629 | | // Begin Writing |
630 | 0 | add_to_buffer(ret, header); |
631 | 0 | add_to_buffer(ret, t); |
632 | 0 | add_to_buffer(ret, static_cast<uint32_t>(pcount)); |
633 | |
|
634 | 0 | for (size_t itr = 0; itr < pcount; itr++) |
635 | 0 | { |
636 | 0 | inPlaceSerialize_LineString(this, pnc[itr], cur_point, ret); |
637 | 0 | cur_point = pnc[itr] + cur_point; |
638 | 0 | } |
639 | 0 | } |
640 | | |
641 | 0 | break; |
642 | | |
643 | 0 | case MULTIPOLYGON: |
644 | 0 | { |
645 | 0 | uint8_t header = PLATFORM_HEADER; |
646 | 0 | uint32_t t = this->touple_order == 2 ? wkbMultiPolygon |
647 | 0 | : this->touple_order == 3 ? wkbMultiPolygon25D |
648 | 0 | : wkbNone; |
649 | |
|
650 | 0 | if (t == wkbNone) |
651 | 0 | throw SG_Exception_BadFeature(); |
652 | 0 | bool noInteriors = this->int_rings.size() == 0 ? true : false; |
653 | 0 | int32_t rc = parts_count[featureInd]; |
654 | 0 | size_t seek_begin = sb; |
655 | 0 | size_t pc_begin = |
656 | 0 | pnc_bl[featureInd]; // initialize with first part count, list |
657 | | // of part counts is contiguous |
658 | 0 | std::vector<int> pnc; |
659 | | |
660 | | // Build sub vector for part_node_counts |
661 | 0 | for (int itr = 0; itr < rc; itr++) |
662 | 0 | { |
663 | 0 | pnc.push_back(pnode_counts[pc_begin + itr]); |
664 | 0 | } |
665 | | |
666 | | // Create Multipolygon headers |
667 | 0 | add_to_buffer(ret, header); |
668 | 0 | add_to_buffer(ret, t); |
669 | |
|
670 | 0 | if (noInteriors) |
671 | 0 | { |
672 | 0 | size_t cur_point = seek_begin; |
673 | 0 | size_t pcount = pnc.size(); |
674 | 0 | add_to_buffer(ret, static_cast<uint32_t>(pcount)); |
675 | |
|
676 | 0 | for (size_t itr = 0; itr < pcount; itr++) |
677 | 0 | { |
678 | 0 | inPlaceSerialize_PolygonExtOnly(this, pnc[itr], cur_point, |
679 | 0 | ret); |
680 | 0 | cur_point = pnc[itr] + cur_point; |
681 | 0 | } |
682 | 0 | } |
683 | | |
684 | 0 | else |
685 | 0 | { |
686 | 0 | int32_t polys = poly_count[featureInd]; |
687 | 0 | add_to_buffer(ret, static_cast<uint32_t>(polys)); |
688 | |
|
689 | 0 | size_t base = pnc_bl[featureInd]; // beginning of parts_count |
690 | | // for this multigeometry |
691 | 0 | size_t seek = seek_begin; // beginning of node range for this |
692 | | // multigeometry |
693 | 0 | size_t ir_base = base + 1; |
694 | | |
695 | | // has interior rings, |
696 | 0 | for (int32_t itr = 0; itr < polys; itr++) |
697 | 0 | { |
698 | 0 | int rc_m = 1; |
699 | | |
700 | | // count how many parts belong to each Polygon |
701 | 0 | while (ir_base < int_rings.size() && int_rings[ir_base]) |
702 | 0 | { |
703 | 0 | rc_m++; |
704 | 0 | ir_base++; |
705 | 0 | } |
706 | |
|
707 | 0 | if (rc_m == 1) |
708 | 0 | ir_base++; // single ring case |
709 | |
|
710 | 0 | std::vector<int> poly_parts; |
711 | | |
712 | | // Make part node count sub vector |
713 | 0 | for (int itr_2 = 0; itr_2 < rc_m; itr_2++) |
714 | 0 | { |
715 | 0 | poly_parts.push_back(pnode_counts[base + itr_2]); |
716 | 0 | } |
717 | |
|
718 | 0 | inPlaceSerialize_Polygon(this, poly_parts, rc_m, seek, ret); |
719 | | |
720 | | // Update seek position |
721 | 0 | for (size_t itr_3 = 0; itr_3 < poly_parts.size(); itr_3++) |
722 | 0 | { |
723 | 0 | seek += poly_parts[itr_3]; |
724 | 0 | } |
725 | |
|
726 | 0 | base += poly_parts.size(); |
727 | | // cppcheck-suppress redundantAssignment |
728 | 0 | ir_base = base + 1; |
729 | 0 | } |
730 | 0 | } |
731 | 0 | } |
732 | 0 | break; |
733 | | |
734 | 0 | default: |
735 | |
|
736 | 0 | throw SG_Exception_BadFeature(); |
737 | 0 | break; |
738 | 0 | } |
739 | | |
740 | 0 | return ret; |
741 | 0 | } |
742 | | |
743 | | void SGeometry_PropertyScanner::open(int container_id) |
744 | 0 | { |
745 | | // First check for container_id, if variable doesn't exist error out |
746 | 0 | if (nc_inq_var(this->nc, container_id, nullptr, nullptr, nullptr, nullptr, |
747 | 0 | nullptr) != NC_NOERR) |
748 | 0 | { |
749 | 0 | return; // change to exception |
750 | 0 | } |
751 | | |
752 | | // Now exists, see what variables refer to this one |
753 | | // First get name of this container |
754 | 0 | char contname[NC_MAX_NAME + 1]; |
755 | 0 | memset(contname, 0, NC_MAX_NAME + 1); |
756 | 0 | if (nc_inq_varname(this->nc, container_id, contname) != NC_NOERR) |
757 | 0 | { |
758 | 0 | return; |
759 | 0 | } |
760 | | |
761 | | // Then scan throughout the netcdfDataset if those variables |
762 | | // geometry_container attribute matches the container |
763 | 0 | int varCount = 0; |
764 | 0 | if (nc_inq_nvars(this->nc, &varCount) != NC_NOERR) |
765 | 0 | { |
766 | 0 | return; |
767 | 0 | } |
768 | | |
769 | 0 | for (int curr = 0; curr < varCount; curr++) |
770 | 0 | { |
771 | 0 | size_t contname2_len = 0; |
772 | | |
773 | | // First find container length, and make buf that size in chars |
774 | 0 | if (nc_inq_attlen(this->nc, curr, CF_SG_GEOMETRY, &contname2_len) != |
775 | 0 | NC_NOERR) |
776 | 0 | { |
777 | | // not a geometry variable, continue |
778 | 0 | continue; |
779 | 0 | } |
780 | | |
781 | | // Also if present but empty, go on |
782 | 0 | if (contname2_len == 0) |
783 | 0 | continue; |
784 | | |
785 | | // Otherwise, geometry: see what container it has |
786 | 0 | char buf[NC_MAX_CHAR + 1]; |
787 | 0 | memset(buf, 0, NC_MAX_CHAR + 1); |
788 | |
|
789 | 0 | if (nc_get_att_text(this->nc, curr, CF_SG_GEOMETRY, buf) != NC_NOERR) |
790 | 0 | { |
791 | 0 | continue; |
792 | 0 | } |
793 | | |
794 | | // If matches, then establish a reference by placing this variable's |
795 | | // data in both vectors |
796 | 0 | if (!strcmp(contname, buf)) |
797 | 0 | { |
798 | 0 | char property_name[NC_MAX_NAME + 1] = {0}; |
799 | | |
800 | | // look for special OGR original name field |
801 | 0 | if (nc_get_att_text(this->nc, curr, OGR_SG_ORIGINAL_LAYERNAME, |
802 | 0 | property_name) != NC_NOERR) |
803 | 0 | { |
804 | | // if doesn't exist, then just use the variable name |
805 | 0 | if (nc_inq_varname(this->nc, curr, property_name) != NC_NOERR) |
806 | 0 | { |
807 | 0 | throw SG_Exception_General_Malformed(contname); |
808 | 0 | } |
809 | 0 | } |
810 | | |
811 | 0 | v_ids.push_back(curr); |
812 | 0 | v_headers.push_back(property_name); |
813 | 0 | } |
814 | 0 | } |
815 | 0 | } |
816 | | |
817 | | // Exception Class Implementations |
818 | | SG_Exception_Dim_MM::SG_Exception_Dim_MM(const char *container_name, |
819 | | const char *field_1, |
820 | | const char *field_2) |
821 | 0 | { |
822 | 0 | std::string cn_s(container_name); |
823 | 0 | std::string field1_s(field_1); |
824 | 0 | std::string field2_s(field_2); |
825 | |
|
826 | 0 | this->err_msg = "[" + cn_s + "] One or more dimensions of " + field1_s + |
827 | 0 | " and " + field2_s + " do not match but must match."; |
828 | 0 | } |
829 | | |
830 | | SG_Exception_Existential::SG_Exception_Existential(const char *container_name, |
831 | | const char *missing_name) |
832 | 0 | { |
833 | 0 | std::string cn_s(container_name); |
834 | 0 | std::string mn_s(missing_name); |
835 | |
|
836 | 0 | this->err_msg = "[" + cn_s + |
837 | 0 | "] The property or the variable associated with " + mn_s + |
838 | 0 | " is missing."; |
839 | 0 | } |
840 | | |
841 | | SG_Exception_Dep::SG_Exception_Dep(const char *container_name, const char *arg1, |
842 | | const char *arg2) |
843 | 0 | { |
844 | 0 | std::string cn_s(container_name); |
845 | 0 | std::string arg1_s(arg1); |
846 | 0 | std::string arg2_s(arg2); |
847 | |
|
848 | 0 | this->err_msg = "[" + cn_s + "] The attribute " + arg1_s + |
849 | 0 | " may not exist without the attribute " + arg2_s + |
850 | 0 | " existing."; |
851 | 0 | } |
852 | | |
853 | | SG_Exception_BadSum::SG_Exception_BadSum(const char *container_name, |
854 | | const char *arg1, const char *arg2) |
855 | 0 | { |
856 | 0 | std::string cn_s(container_name); |
857 | 0 | std::string arg1_s(arg1); |
858 | 0 | std::string arg2_s(arg2); |
859 | |
|
860 | 0 | this->err_msg = "[" + cn_s + "]" + " The sum of all values in " + arg1_s + |
861 | 0 | " and " + arg2_s + " do not match."; |
862 | 0 | } |
863 | | |
864 | | SG_Exception_General_Malformed ::SG_Exception_General_Malformed(const char *arg) |
865 | 0 | { |
866 | 0 | std::string arg1_s(arg); |
867 | |
|
868 | 0 | this->err_msg = |
869 | 0 | "Corruption or malformed formatting has been detected in: " + arg1_s; |
870 | 0 | } |
871 | | |
872 | | // to get past linker |
873 | | SG_Exception::~SG_Exception() |
874 | 0 | { |
875 | 0 | } |
876 | | |
877 | | // Helpers |
878 | | // following is a short hand for a clean up and exit, since goto isn't allowed |
879 | | double getCFVersion(int ncid) |
880 | 13.8k | { |
881 | 13.8k | double ver = -1.0; |
882 | 13.8k | std::string attrVal; |
883 | | |
884 | | // Fetch the CF attribute |
885 | 13.8k | if (attrf(ncid, NC_GLOBAL, NCDF_CONVENTIONS, attrVal) == "") |
886 | 13.4k | { |
887 | 13.4k | return ver; |
888 | 13.4k | } |
889 | | |
890 | 405 | if (sscanf(attrVal.c_str(), "CF-%lf", &ver) != 1) |
891 | 12 | { |
892 | 12 | return -1.0; |
893 | 12 | } |
894 | | |
895 | 393 | return ver; |
896 | 405 | } |
897 | | |
898 | | geom_t getGeometryType(int ncid, int varid) |
899 | 0 | { |
900 | 0 | geom_t ret = UNSUPPORTED; |
901 | 0 | std::string gt_name_s; |
902 | 0 | const char *gt_name = |
903 | 0 | attrf(ncid, varid, CF_SG_GEOMETRY_TYPE, gt_name_s).c_str(); |
904 | |
|
905 | 0 | if (gt_name[0] == '\0') |
906 | 0 | { |
907 | 0 | return NONE; |
908 | 0 | } |
909 | | |
910 | | // Points |
911 | 0 | if (!strcmp(gt_name, CF_SG_TYPE_POINT)) |
912 | 0 | { |
913 | | // Node Count not present? Assume that it is a multipoint. |
914 | 0 | if (nc_inq_att(ncid, varid, CF_SG_NODE_COUNT, nullptr, nullptr) == |
915 | 0 | NC_ENOTATT) |
916 | 0 | { |
917 | 0 | ret = POINT; |
918 | 0 | } |
919 | 0 | else |
920 | 0 | ret = MULTIPOINT; |
921 | 0 | } |
922 | | |
923 | | // Lines |
924 | 0 | else if (!strcmp(gt_name, CF_SG_TYPE_LINE)) |
925 | 0 | { |
926 | | // Part Node Count present? Assume multiline |
927 | 0 | if (nc_inq_att(ncid, varid, CF_SG_PART_NODE_COUNT, nullptr, nullptr) == |
928 | 0 | NC_ENOTATT) |
929 | 0 | { |
930 | 0 | ret = LINE; |
931 | 0 | } |
932 | 0 | else |
933 | 0 | ret = MULTILINE; |
934 | 0 | } |
935 | | |
936 | | // Polygons |
937 | 0 | else if (!strcmp(gt_name, CF_SG_TYPE_POLY)) |
938 | 0 | { |
939 | | /* Polygons versus MultiPolygons, slightly ambiguous |
940 | | * Part Node Count & no Interior Ring - MultiPolygon |
941 | | * no Part Node Count & no Interior Ring - Polygon |
942 | | * Part Node Count & Interior Ring - assume that it is a MultiPolygon |
943 | | */ |
944 | 0 | int pnc_present = |
945 | 0 | nc_inq_att(ncid, varid, CF_SG_PART_NODE_COUNT, nullptr, nullptr); |
946 | 0 | int ir_present = |
947 | 0 | nc_inq_att(ncid, varid, CF_SG_INTERIOR_RING, nullptr, nullptr); |
948 | |
|
949 | 0 | if (pnc_present == NC_ENOTATT && ir_present == NC_ENOTATT) |
950 | 0 | { |
951 | 0 | ret = POLYGON; |
952 | 0 | } |
953 | 0 | else |
954 | 0 | ret = MULTIPOLYGON; |
955 | 0 | } |
956 | |
|
957 | 0 | return ret; |
958 | 0 | } |
959 | | |
960 | | void inPlaceSerialize_Point(SGeometry_Reader *ge, size_t seek_pos, |
961 | | std::vector<unsigned char> &buffer) |
962 | 0 | { |
963 | 0 | uint8_t order = PLATFORM_HEADER; |
964 | 0 | uint32_t t = ge->get_axisCount() == 2 ? wkbPoint |
965 | 0 | : ge->get_axisCount() == 3 ? wkbPoint25D |
966 | 0 | : wkbNone; |
967 | |
|
968 | 0 | if (t == wkbNone) |
969 | 0 | throw SG_Exception_BadFeature(); |
970 | | |
971 | 0 | add_to_buffer(buffer, order); |
972 | 0 | add_to_buffer(buffer, t); |
973 | | |
974 | | // Now get point data; |
975 | 0 | Point &p = (*ge)[seek_pos]; |
976 | 0 | add_to_buffer(buffer, p[0]); |
977 | 0 | add_to_buffer(buffer, p[1]); |
978 | |
|
979 | 0 | if (ge->get_axisCount() >= 3) |
980 | 0 | { |
981 | 0 | add_to_buffer(buffer, p[2]); |
982 | 0 | } |
983 | 0 | } |
984 | | |
985 | | void inPlaceSerialize_LineString(SGeometry_Reader *ge, int node_count, |
986 | | size_t seek_begin, |
987 | | std::vector<unsigned char> &buffer) |
988 | 0 | { |
989 | 0 | uint8_t order = PLATFORM_HEADER; |
990 | 0 | uint32_t t = ge->get_axisCount() == 2 ? wkbLineString |
991 | 0 | : ge->get_axisCount() == 3 ? wkbLineString25D |
992 | 0 | : wkbNone; |
993 | |
|
994 | 0 | if (t == wkbNone) |
995 | 0 | throw SG_Exception_BadFeature(); |
996 | 0 | uint32_t nc = (uint32_t)node_count; |
997 | |
|
998 | 0 | add_to_buffer(buffer, order); |
999 | 0 | add_to_buffer(buffer, t); |
1000 | 0 | add_to_buffer(buffer, nc); |
1001 | | |
1002 | | // Now serialize points |
1003 | 0 | for (int ind = 0; ind < node_count; ind++) |
1004 | 0 | { |
1005 | 0 | Point &p = (*ge)[seek_begin + ind]; |
1006 | 0 | add_to_buffer(buffer, p[0]); |
1007 | 0 | add_to_buffer(buffer, p[1]); |
1008 | |
|
1009 | 0 | if (ge->get_axisCount() >= 3) |
1010 | 0 | { |
1011 | 0 | add_to_buffer(buffer, p[2]); |
1012 | 0 | } |
1013 | 0 | } |
1014 | 0 | } |
1015 | | |
1016 | | void inPlaceSerialize_PolygonExtOnly(SGeometry_Reader *ge, int node_count, |
1017 | | size_t seek_begin, |
1018 | | std::vector<unsigned char> &buffer) |
1019 | 0 | { |
1020 | 0 | uint8_t order = PLATFORM_HEADER; |
1021 | 0 | uint32_t t = ge->get_axisCount() == 2 ? wkbPolygon |
1022 | 0 | : ge->get_axisCount() == 3 ? wkbPolygon25D |
1023 | 0 | : wkbNone; |
1024 | |
|
1025 | 0 | if (t == wkbNone) |
1026 | 0 | throw SG_Exception_BadFeature(); |
1027 | 0 | uint32_t rc = 1; |
1028 | |
|
1029 | 0 | add_to_buffer(buffer, order); |
1030 | 0 | add_to_buffer(buffer, t); |
1031 | 0 | add_to_buffer(buffer, rc); |
1032 | 0 | add_to_buffer(buffer, static_cast<uint32_t>(node_count)); |
1033 | 0 | for (int pind = 0; pind < node_count; pind++) |
1034 | 0 | { |
1035 | 0 | Point &pt = (*ge)[seek_begin + pind]; |
1036 | 0 | add_to_buffer(buffer, pt[0]); |
1037 | 0 | add_to_buffer(buffer, pt[1]); |
1038 | |
|
1039 | 0 | if (ge->get_axisCount() >= 3) |
1040 | 0 | { |
1041 | 0 | add_to_buffer(buffer, pt[2]); |
1042 | 0 | } |
1043 | 0 | } |
1044 | 0 | } |
1045 | | |
1046 | | void inPlaceSerialize_Polygon(SGeometry_Reader *ge, std::vector<int> &pnc, |
1047 | | int ring_count, size_t seek_begin, |
1048 | | std::vector<unsigned char> &buffer) |
1049 | 0 | { |
1050 | 0 | uint8_t order = PLATFORM_HEADER; |
1051 | 0 | uint32_t t = ge->get_axisCount() == 2 ? wkbPolygon |
1052 | 0 | : ge->get_axisCount() == 3 ? wkbPolygon25D |
1053 | 0 | : wkbNone; |
1054 | |
|
1055 | 0 | if (t == wkbNone) |
1056 | 0 | throw SG_Exception_BadFeature(); |
1057 | 0 | uint32_t rc = static_cast<uint32_t>(ring_count); |
1058 | |
|
1059 | 0 | add_to_buffer(buffer, order); |
1060 | 0 | add_to_buffer(buffer, t); |
1061 | 0 | add_to_buffer(buffer, rc); |
1062 | 0 | int cmoffset = 0; |
1063 | 0 | for (int ring_c = 0; ring_c < ring_count; ring_c++) |
1064 | 0 | { |
1065 | 0 | uint32_t node_count = pnc[ring_c]; |
1066 | 0 | add_to_buffer(buffer, node_count); |
1067 | |
|
1068 | 0 | int pind = 0; |
1069 | 0 | for (pind = 0; pind < pnc[ring_c]; pind++) |
1070 | 0 | { |
1071 | 0 | Point &pt = (*ge)[seek_begin + cmoffset + pind]; |
1072 | 0 | add_to_buffer(buffer, pt[0]); |
1073 | 0 | add_to_buffer(buffer, pt[1]); |
1074 | |
|
1075 | 0 | if (ge->get_axisCount() >= 3) |
1076 | 0 | { |
1077 | 0 | add_to_buffer(buffer, pt[2]); |
1078 | 0 | } |
1079 | 0 | } |
1080 | |
|
1081 | 0 | cmoffset += pind; |
1082 | 0 | } |
1083 | 0 | } |
1084 | | |
1085 | | int scanForGeometryContainers(int ncid, std::set<int> &r_ids) |
1086 | 37 | { |
1087 | 37 | int nvars; |
1088 | 37 | if (nc_inq_nvars(ncid, &nvars) != NC_NOERR) |
1089 | 0 | { |
1090 | 0 | return -1; |
1091 | 0 | } |
1092 | | |
1093 | 37 | r_ids.clear(); |
1094 | | |
1095 | | // For each variable check for geometry attribute |
1096 | | // If has geometry attribute, then check the associated variable ID |
1097 | | |
1098 | 496 | for (int itr = 0; itr < nvars; itr++) |
1099 | 459 | { |
1100 | 459 | char c[NC_MAX_CHAR]; |
1101 | 459 | memset(c, 0, NC_MAX_CHAR); |
1102 | 459 | if (nc_get_att_text(ncid, itr, CF_SG_GEOMETRY, c) != NC_NOERR) |
1103 | 459 | { |
1104 | 459 | continue; |
1105 | 459 | } |
1106 | | |
1107 | 0 | int varID; |
1108 | 0 | if (nc_inq_varid(ncid, c, &varID) != NC_NOERR) |
1109 | 0 | { |
1110 | 0 | continue; |
1111 | 0 | } |
1112 | | |
1113 | | // Now have variable ID. See if vector contains it, and if not |
1114 | | // insert |
1115 | 0 | r_ids.insert(varID); // It's a set. No big deal sets don't allow |
1116 | | // duplicates anyways |
1117 | 0 | } |
1118 | | |
1119 | 37 | return 0; |
1120 | 37 | } |
1121 | | } // namespace nccfdriver |