Coverage Report

Created: 2025-07-23 09:13

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