Coverage Report

Created: 2026-02-14 09:00

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
/* 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
14.1k
{
35
14.1k
    size_t len = 0;
36
14.1k
    nc_inq_attlen(ncid, varId, attrName, &len);
37
38
14.1k
    if (len < 1)
39
13.5k
    {
40
13.5k
        alloc.clear();
41
13.5k
        return alloc;
42
13.5k
    }
43
44
574
    alloc.resize(len);
45
574
    memset(&alloc[0], 0, len);
46
47
    // Now look through this variable for the attribute
48
574
    nc_get_att_text(ncid, varId, attrName, &alloc[0]);
49
50
574
    return alloc;
51
14.1k
}
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
    }
738
739
0
    return ret;
740
0
}
741
742
void SGeometry_PropertyScanner::open(int container_id)
743
0
{
744
    // First check for container_id, if variable doesn't exist error out
745
0
    if (nc_inq_var(this->nc, container_id, nullptr, nullptr, nullptr, nullptr,
746
0
                   nullptr) != NC_NOERR)
747
0
    {
748
0
        return;  // change to exception
749
0
    }
750
751
    // Now exists, see what variables refer to this one
752
    // First get name of this container
753
0
    char contname[NC_MAX_NAME + 1];
754
0
    memset(contname, 0, NC_MAX_NAME + 1);
755
0
    if (nc_inq_varname(this->nc, container_id, contname) != NC_NOERR)
756
0
    {
757
0
        return;
758
0
    }
759
760
    // Then scan throughout the netcdfDataset if those variables
761
    // geometry_container attribute matches the container
762
0
    int varCount = 0;
763
0
    if (nc_inq_nvars(this->nc, &varCount) != NC_NOERR)
764
0
    {
765
0
        return;
766
0
    }
767
768
0
    for (int curr = 0; curr < varCount; curr++)
769
0
    {
770
0
        size_t contname2_len = 0;
771
772
        // First find container length, and make buf that size in chars
773
0
        if (nc_inq_attlen(this->nc, curr, CF_SG_GEOMETRY, &contname2_len) !=
774
0
            NC_NOERR)
775
0
        {
776
            // not a geometry variable, continue
777
0
            continue;
778
0
        }
779
780
        // Also if present but empty, go on
781
0
        if (contname2_len == 0)
782
0
            continue;
783
784
        // Otherwise, geometry: see what container it has
785
0
        char buf[NC_MAX_CHAR + 1];
786
0
        memset(buf, 0, NC_MAX_CHAR + 1);
787
788
0
        if (nc_get_att_text(this->nc, curr, CF_SG_GEOMETRY, buf) != NC_NOERR)
789
0
        {
790
0
            continue;
791
0
        }
792
793
        // If matches, then establish a reference by placing this variable's
794
        // data in both vectors
795
0
        if (!strcmp(contname, buf))
796
0
        {
797
0
            char property_name[NC_MAX_NAME + 1] = {0};
798
799
            // look for special OGR original name field
800
0
            if (nc_get_att_text(this->nc, curr, OGR_SG_ORIGINAL_LAYERNAME,
801
0
                                property_name) != NC_NOERR)
802
0
            {
803
                // if doesn't exist, then just use the variable name
804
0
                if (nc_inq_varname(this->nc, curr, property_name) != NC_NOERR)
805
0
                {
806
0
                    throw SG_Exception_General_Malformed(contname);
807
0
                }
808
0
            }
809
810
0
            v_ids.push_back(curr);
811
0
            v_headers.push_back(property_name);
812
0
        }
813
0
    }
814
0
}
815
816
// Exception Class Implementations
817
SG_Exception_Dim_MM::SG_Exception_Dim_MM(const char *container_name,
818
                                         const char *field_1,
819
                                         const char *field_2)
820
0
{
821
0
    std::string cn_s(container_name);
822
0
    std::string field1_s(field_1);
823
0
    std::string field2_s(field_2);
824
825
0
    this->err_msg = "[" + cn_s + "] One or more dimensions of " + field1_s +
826
0
                    " and " + field2_s + " do not match but must match.";
827
0
}
828
829
SG_Exception_Existential::SG_Exception_Existential(const char *container_name,
830
                                                   const char *missing_name)
831
0
{
832
0
    std::string cn_s(container_name);
833
0
    std::string mn_s(missing_name);
834
835
0
    this->err_msg = "[" + cn_s +
836
0
                    "] The property or the variable associated with " + mn_s +
837
0
                    " is missing.";
838
0
}
839
840
SG_Exception_Dep::SG_Exception_Dep(const char *container_name, const char *arg1,
841
                                   const char *arg2)
842
0
{
843
0
    std::string cn_s(container_name);
844
0
    std::string arg1_s(arg1);
845
0
    std::string arg2_s(arg2);
846
847
0
    this->err_msg = "[" + cn_s + "] The attribute " + arg1_s +
848
0
                    " may not exist without the attribute " + arg2_s +
849
0
                    " existing.";
850
0
}
851
852
SG_Exception_BadSum::SG_Exception_BadSum(const char *container_name,
853
                                         const char *arg1, const char *arg2)
854
0
{
855
0
    std::string cn_s(container_name);
856
0
    std::string arg1_s(arg1);
857
0
    std::string arg2_s(arg2);
858
859
0
    this->err_msg = "[" + cn_s + "]" + " The sum of all values in " + arg1_s +
860
0
                    " and " + arg2_s + " do not match.";
861
0
}
862
863
SG_Exception_General_Malformed ::SG_Exception_General_Malformed(const char *arg)
864
0
{
865
0
    std::string arg1_s(arg);
866
867
0
    this->err_msg =
868
0
        "Corruption or malformed formatting has been detected in: " + arg1_s;
869
0
}
870
871
// to get past linker
872
SG_Exception::~SG_Exception()
873
0
{
874
0
}
875
876
// Helpers
877
// following is a short hand for a clean up and exit, since goto isn't allowed
878
double getCFVersion(int ncid)
879
14.1k
{
880
14.1k
    double ver = -1.0;
881
14.1k
    std::string attrVal;
882
883
    // Fetch the CF attribute
884
14.1k
    if (attrf(ncid, NC_GLOBAL, NCDF_CONVENTIONS, attrVal) == "")
885
13.5k
    {
886
13.5k
        return ver;
887
13.5k
    }
888
889
574
    if (sscanf(attrVal.c_str(), "CF-%lf", &ver) != 1)
890
27
    {
891
27
        return -1.0;
892
27
    }
893
894
547
    return ver;
895
574
}
896
897
geom_t getGeometryType(int ncid, int varid)
898
0
{
899
0
    geom_t ret = UNSUPPORTED;
900
0
    std::string gt_name_s;
901
0
    const char *gt_name =
902
0
        attrf(ncid, varid, CF_SG_GEOMETRY_TYPE, gt_name_s).c_str();
903
904
0
    if (gt_name[0] == '\0')
905
0
    {
906
0
        return NONE;
907
0
    }
908
909
    // Points
910
0
    if (!strcmp(gt_name, CF_SG_TYPE_POINT))
911
0
    {
912
        // Node Count not present? Assume that it is a multipoint.
913
0
        if (nc_inq_att(ncid, varid, CF_SG_NODE_COUNT, nullptr, nullptr) ==
914
0
            NC_ENOTATT)
915
0
        {
916
0
            ret = POINT;
917
0
        }
918
0
        else
919
0
            ret = MULTIPOINT;
920
0
    }
921
922
    // Lines
923
0
    else if (!strcmp(gt_name, CF_SG_TYPE_LINE))
924
0
    {
925
        // Part Node Count present? Assume multiline
926
0
        if (nc_inq_att(ncid, varid, CF_SG_PART_NODE_COUNT, nullptr, nullptr) ==
927
0
            NC_ENOTATT)
928
0
        {
929
0
            ret = LINE;
930
0
        }
931
0
        else
932
0
            ret = MULTILINE;
933
0
    }
934
935
    // Polygons
936
0
    else if (!strcmp(gt_name, CF_SG_TYPE_POLY))
937
0
    {
938
        /* Polygons versus MultiPolygons, slightly ambiguous
939
         * Part Node Count & no Interior Ring - MultiPolygon
940
         * no Part Node Count & no Interior Ring - Polygon
941
         * Part Node Count & Interior Ring - assume that it is a MultiPolygon
942
         */
943
0
        int pnc_present =
944
0
            nc_inq_att(ncid, varid, CF_SG_PART_NODE_COUNT, nullptr, nullptr);
945
0
        int ir_present =
946
0
            nc_inq_att(ncid, varid, CF_SG_INTERIOR_RING, nullptr, nullptr);
947
948
0
        if (pnc_present == NC_ENOTATT && ir_present == NC_ENOTATT)
949
0
        {
950
0
            ret = POLYGON;
951
0
        }
952
0
        else
953
0
            ret = MULTIPOLYGON;
954
0
    }
955
956
0
    return ret;
957
0
}
958
959
void inPlaceSerialize_Point(SGeometry_Reader *ge, size_t seek_pos,
960
                            std::vector<unsigned char> &buffer)
961
0
{
962
0
    uint8_t order = PLATFORM_HEADER;
963
0
    uint32_t t = ge->get_axisCount() == 2   ? wkbPoint
964
0
                 : ge->get_axisCount() == 3 ? wkbPoint25D
965
0
                                            : wkbNone;
966
967
0
    if (t == wkbNone)
968
0
        throw SG_Exception_BadFeature();
969
970
0
    add_to_buffer(buffer, order);
971
0
    add_to_buffer(buffer, t);
972
973
    // Now get point data;
974
0
    Point &p = (*ge)[seek_pos];
975
0
    add_to_buffer(buffer, p[0]);
976
0
    add_to_buffer(buffer, p[1]);
977
978
0
    if (ge->get_axisCount() >= 3)
979
0
    {
980
0
        add_to_buffer(buffer, p[2]);
981
0
    }
982
0
}
983
984
void inPlaceSerialize_LineString(SGeometry_Reader *ge, int node_count,
985
                                 size_t seek_begin,
986
                                 std::vector<unsigned char> &buffer)
987
0
{
988
0
    uint8_t order = PLATFORM_HEADER;
989
0
    uint32_t t = ge->get_axisCount() == 2   ? wkbLineString
990
0
                 : ge->get_axisCount() == 3 ? wkbLineString25D
991
0
                                            : wkbNone;
992
993
0
    if (t == wkbNone)
994
0
        throw SG_Exception_BadFeature();
995
0
    uint32_t nc = (uint32_t)node_count;
996
997
0
    add_to_buffer(buffer, order);
998
0
    add_to_buffer(buffer, t);
999
0
    add_to_buffer(buffer, nc);
1000
1001
    // Now serialize points
1002
0
    for (int ind = 0; ind < node_count; ind++)
1003
0
    {
1004
0
        Point &p = (*ge)[seek_begin + ind];
1005
0
        add_to_buffer(buffer, p[0]);
1006
0
        add_to_buffer(buffer, p[1]);
1007
1008
0
        if (ge->get_axisCount() >= 3)
1009
0
        {
1010
0
            add_to_buffer(buffer, p[2]);
1011
0
        }
1012
0
    }
1013
0
}
1014
1015
void inPlaceSerialize_PolygonExtOnly(SGeometry_Reader *ge, int node_count,
1016
                                     size_t seek_begin,
1017
                                     std::vector<unsigned char> &buffer)
1018
0
{
1019
0
    uint8_t order = PLATFORM_HEADER;
1020
0
    uint32_t t = ge->get_axisCount() == 2   ? wkbPolygon
1021
0
                 : ge->get_axisCount() == 3 ? wkbPolygon25D
1022
0
                                            : wkbNone;
1023
1024
0
    if (t == wkbNone)
1025
0
        throw SG_Exception_BadFeature();
1026
0
    uint32_t rc = 1;
1027
1028
0
    add_to_buffer(buffer, order);
1029
0
    add_to_buffer(buffer, t);
1030
0
    add_to_buffer(buffer, rc);
1031
0
    add_to_buffer(buffer, static_cast<uint32_t>(node_count));
1032
0
    for (int pind = 0; pind < node_count; pind++)
1033
0
    {
1034
0
        Point &pt = (*ge)[seek_begin + pind];
1035
0
        add_to_buffer(buffer, pt[0]);
1036
0
        add_to_buffer(buffer, pt[1]);
1037
1038
0
        if (ge->get_axisCount() >= 3)
1039
0
        {
1040
0
            add_to_buffer(buffer, pt[2]);
1041
0
        }
1042
0
    }
1043
0
}
1044
1045
void inPlaceSerialize_Polygon(SGeometry_Reader *ge, std::vector<int> &pnc,
1046
                              int ring_count, size_t seek_begin,
1047
                              std::vector<unsigned char> &buffer)
1048
0
{
1049
0
    uint8_t order = PLATFORM_HEADER;
1050
0
    uint32_t t = ge->get_axisCount() == 2   ? wkbPolygon
1051
0
                 : ge->get_axisCount() == 3 ? wkbPolygon25D
1052
0
                                            : wkbNone;
1053
1054
0
    if (t == wkbNone)
1055
0
        throw SG_Exception_BadFeature();
1056
0
    uint32_t rc = static_cast<uint32_t>(ring_count);
1057
1058
0
    add_to_buffer(buffer, order);
1059
0
    add_to_buffer(buffer, t);
1060
0
    add_to_buffer(buffer, rc);
1061
0
    int cmoffset = 0;
1062
0
    for (int ring_c = 0; ring_c < ring_count; ring_c++)
1063
0
    {
1064
0
        uint32_t node_count = pnc[ring_c];
1065
0
        add_to_buffer(buffer, node_count);
1066
1067
0
        int pind = 0;
1068
0
        for (pind = 0; pind < pnc[ring_c]; pind++)
1069
0
        {
1070
0
            Point &pt = (*ge)[seek_begin + cmoffset + pind];
1071
0
            add_to_buffer(buffer, pt[0]);
1072
0
            add_to_buffer(buffer, pt[1]);
1073
1074
0
            if (ge->get_axisCount() >= 3)
1075
0
            {
1076
0
                add_to_buffer(buffer, pt[2]);
1077
0
            }
1078
0
        }
1079
1080
0
        cmoffset += pind;
1081
0
    }
1082
0
}
1083
1084
int scanForGeometryContainers(int ncid, std::set<int> &r_ids)
1085
129
{
1086
129
    int nvars;
1087
129
    if (nc_inq_nvars(ncid, &nvars) != NC_NOERR)
1088
0
    {
1089
0
        return -1;
1090
0
    }
1091
1092
129
    r_ids.clear();
1093
1094
    // For each variable check for geometry attribute
1095
    // If has geometry attribute, then check the associated variable ID
1096
1097
1.53k
    for (int itr = 0; itr < nvars; itr++)
1098
1.40k
    {
1099
1.40k
        char c[NC_MAX_CHAR];
1100
1.40k
        memset(c, 0, NC_MAX_CHAR);
1101
1.40k
        if (nc_get_att_text(ncid, itr, CF_SG_GEOMETRY, c) != NC_NOERR)
1102
1.40k
        {
1103
1.40k
            continue;
1104
1.40k
        }
1105
1106
0
        int varID;
1107
0
        if (nc_inq_varid(ncid, c, &varID) != NC_NOERR)
1108
0
        {
1109
0
            continue;
1110
0
        }
1111
1112
        // Now have variable ID. See if vector contains it, and if not
1113
        // insert
1114
0
        r_ids.insert(varID);  // It's a set. No big deal sets don't allow
1115
                              // duplicates anyways
1116
0
    }
1117
1118
129
    return 0;
1119
129
}
1120
}  // namespace nccfdriver