Coverage Report

Created: 2026-08-14 09:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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