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