• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.39
/frmts/wcs/wcsdataset201.cpp
1
/******************************************************************************
2
 *
3
 * Project:  WCS Client Driver
4
 * Purpose:  Implementation of Dataset class for WCS 2.0.
5
 * Author:   Ari Jolma <ari dot jolma at gmail dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2006, Frank Warmerdam
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 * Copyright (c) 2017, Ari Jolma
11
 * Copyright (c) 2017, Finnish Environment Institute
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15

16
#include "cpl_string.h"
17
#include "cpl_minixml.h"
18
#include "cpl_http.h"
19
#include "cpl_conv.h"
20
#include "gmlutils.h"
21
#include "gdal_frmts.h"
22
#include "gdal_pam.h"
23
#include "ogr_spatialref.h"
24
#include "gmlcoverage.h"
25

26
#include <algorithm>
27

28
#include "wcsdataset.h"
29
#include "wcsutils.h"
30

31
using namespace WCSUtils;
32

33
/************************************************************************/
34
/*                         CoverageSubtype()                            */
35
/*                                                                      */
36
/************************************************************************/
37

38
static std::string CoverageSubtype(CPLXMLNode *coverage)
21✔
39
{
40
    std::string subtype =
41
        CPLGetXMLValue(coverage, "ServiceParameters.CoverageSubtype", "");
21✔
42
    size_t pos = subtype.find("Coverage");
21✔
43
    if (pos != std::string::npos)
21✔
44
    {
45
        subtype.erase(pos);
21✔
46
    }
47
    return subtype;
21✔
48
}
49

50
/************************************************************************/
51
/*                         GetGridNode()                                */
52
/*                                                                      */
53
/************************************************************************/
54

55
static CPLXMLNode *GetGridNode(CPLXMLNode *coverage, const std::string &subtype)
21✔
56
{
57
    CPLXMLNode *grid = nullptr;
21✔
58
    // Construct the name of the node that we look under domainSet.
59
    // For now we can handle RectifiedGrid and ReferenceableGridByVectors.
60
    // Note that if this is called at GetCoverage stage, the grid should not be
61
    // NULL.
62
    std::string path = "domainSet";
21✔
63
    if (subtype == "RectifiedGrid")
21✔
64
    {
65
        grid = CPLGetXMLNode(coverage, (path + "." + subtype).c_str());
18✔
66
    }
67
    else if (subtype == "ReferenceableGrid")
3✔
68
    {
69
        grid = CPLGetXMLNode(coverage,
3✔
70
                             (path + "." + subtype + "ByVectors").c_str());
6✔
71
    }
72
    if (!grid)
21✔
73
    {
74
        CPLError(CE_Failure, CPLE_AppDefined,
×
75
                 "Can't handle coverages of type '%s'.", subtype.c_str());
76
    }
77
    return grid;
42✔
78
}
79

80
/************************************************************************/
81
/*                         ParseParameters()                            */
82
/*                                                                      */
83
/************************************************************************/
84

85
static void ParseParameters(CPLXMLNode *service,
42✔
86
                            std::vector<std::string> &dimensions,
87
                            std::string &range,
88
                            std::vector<std::vector<std::string>> &others)
89
{
90
    std::vector<std::string> parameters =
91
        Split(CPLGetXMLValue(service, "Parameters", ""), "&");
84✔
92
    for (unsigned int i = 0; i < parameters.size(); ++i)
84✔
93
    {
94
        std::vector<std::string> kv = Split(parameters[i].c_str(), "=");
42✔
95
        if (kv.size() < 2)
42✔
96
        {
97
            continue;
×
98
        }
99
        kv[0] = CPLString(kv[0]).toupper();
42✔
100
        if (kv[0] == "RANGESUBSET")
42✔
101
        {
102
            range = kv[1];
×
103
        }
104
        else if (kv[0] == "SUBSET")
42✔
105
        {
106
            dimensions = Split(kv[1].c_str(), ";");
×
107
        }
108
        else
109
        {
110
            others.push_back(std::vector<std::string>{kv[0], kv[1]});
126✔
111
        }
112
    }
113
    // fallback to service values, if any
114
    if (range == "")
42✔
115
    {
116
        range = CPLGetXMLValue(service, "RangeSubset", "");
42✔
117
    }
118
    if (dimensions.size() == 0)
42✔
119
    {
120
        dimensions = Split(CPLGetXMLValue(service, "Subset", ""), ";");
42✔
121
    }
122
}
42✔
123

124
/************************************************************************/
125
/*                         GetNativeExtent()                            */
126
/*                                                                      */
127
/************************************************************************/
128

129
std::vector<double> WCSDataset201::GetNativeExtent(int nXOff, int nYOff,
21✔
130
                                                   int nXSize, int nYSize,
131
                                                   CPL_UNUSED int nBufXSize,
132
                                                   CPL_UNUSED int nBufYSize)
133
{
134
    std::vector<double> extent;
21✔
135
    // WCS 2.0 extents are the outer edges of outer pixels.
136
    extent.push_back(m_gt[0] + (nXOff)*m_gt[1]);
21✔
137
    extent.push_back(m_gt[3] + (nYOff + nYSize) * m_gt[5]);
21✔
138
    extent.push_back(m_gt[0] + (nXOff + nXSize) * m_gt[1]);
21✔
139
    extent.push_back(m_gt[3] + (nYOff)*m_gt[5]);
21✔
140
    return extent;
21✔
141
}
142

143
/************************************************************************/
144
/*                        GetCoverageRequest()                          */
145
/*                                                                      */
146
/************************************************************************/
147

148
std::string
149
WCSDataset201::GetCoverageRequest(bool scaled, int nBufXSize, int nBufYSize,
21✔
150
                                  const std::vector<double> &extent,
151
                                  const std::string & /*osBandList*/)
152
{
153
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
21✔
154
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
21✔
155
    request += "&REQUEST=GetCoverage";
21✔
156
    request +=
157
        "&VERSION=" + std::string(CPLGetXMLValue(psService, "Version", ""));
21✔
158
    request += "&COVERAGEID=" +
42✔
159
               URLEncode(CPLGetXMLValue(psService, "CoverageName", ""));
63✔
160

161
    // note: native_crs is not really supported
162
    if (!native_crs)
21✔
163
    {
164
        std::string crs = URLEncode(CPLGetXMLValue(psService, "SRS", ""));
×
165
        request += "&OUTPUTCRS=" + crs;
×
166
        request += "&SUBSETTINGCRS=" + crs;
×
167
    }
168

169
    std::vector<std::string> domain =
170
        Split(CPLGetXMLValue(psService, "Domain", ""), ",");
42✔
171
    if (domain.size() < 2)
21✔
172
    {
173
        // eek!
174
        domain.push_back("E");
×
175
        domain.push_back("N");
×
176
    }
177
    const char *x = domain[0].c_str();
21✔
178
    const char *y = domain[1].c_str();
21✔
179
    if (CPLGetXMLBoolean(psService, "SubsetAxisSwap"))
21✔
180
    {
181
        const char *tmp = x;
6✔
182
        x = y;
6✔
183
        y = tmp;
6✔
184
    }
185

186
    std::vector<std::string> low =
187
        Split(CPLGetXMLValue(psService, "Low", ""), ",");
42✔
188
    std::vector<std::string> high =
189
        Split(CPLGetXMLValue(psService, "High", ""), ",");
42✔
190
    std::string a = CPLString().Printf("%.17g", extent[0]);
42✔
191
    if (low.size() > 1 && CPLAtof(low[0].c_str()) > extent[0])
21✔
192
    {
193
        a = low[0];
×
194
    }
195
    std::string b = CPLString().Printf("%.17g", extent[2]);
42✔
196
    if (high.size() > 1 && CPLAtof(high[0].c_str()) < extent[2])
21✔
197
    {
198
        b = high[0];
1✔
199
    }
200
    /*
201
    std::string a = CPLString().Printf(
202
        "%.17g", MAX(m_gt[0], extent[0]));
203
    std::string b = CPLString().Printf(
204
        "%.17g", MIN(m_gt[0] + nRasterXSize * m_gt[1],
205
    extent[2]));
206
    */
207

208
    // 09-147 KVP Protocol: subset keys must be unique
209
    // GeoServer: seems to require plain SUBSET for x and y
210

211
    request +=
212
        CPLString().Printf("&SUBSET=%s%%28%s,%s%%29", x, a.c_str(), b.c_str());
21✔
213

214
    a = CPLString().Printf("%.17g", extent[1]);
21✔
215
    if (low.size() > 1 && CPLAtof(low[1].c_str()) > extent[1])
21✔
216
    {
217
        a = low[1];
×
218
    }
219
    b = CPLString().Printf("%.17g", extent[3]);
21✔
220
    if (high.size() > 1 && CPLAtof(high[1].c_str()) < extent[3])
21✔
221
    {
222
        b = high[1];
×
223
    }
224
    /*
225
    a = CPLString().Printf(
226
        "%.17g", MAX(m_gt[3] + nRasterYSize * m_gt[5],
227
    extent[1])); b = CPLString().Printf(
228
        "%.17g", MIN(m_gt[3], extent[3]));
229
    */
230

231
    request +=
232
        CPLString().Printf("&SUBSET=%s%%28%s,%s%%29", y, a.c_str(), b.c_str());
21✔
233

234
    // Dimension and range parameters:
235
    std::vector<std::string> dimensions;
42✔
236
    std::string range;
42✔
237
    std::vector<std::vector<std::string>> others;
42✔
238
    ParseParameters(psService, dimensions, range, others);
21✔
239

240
    // set subsets for axis other than x/y
241
    for (unsigned int i = 0; i < dimensions.size(); ++i)
24✔
242
    {
243
        size_t pos = dimensions[i].find("(");
3✔
244
        std::string dim = dimensions[i].substr(0, pos);
3✔
245
        if (IndexOf(dim, domain) != -1)
3✔
246
        {
247
            continue;
×
248
        }
249
        std::vector<std::string> params =
250
            Split(FromParenthesis(dimensions[i]).c_str(), ",");
6✔
251
        request +=
252
            "&SUBSET" + CPLString().Printf("%i", i) + "=" + dim + "%28";  // (
3✔
253
        for (unsigned int j = 0; j < params.size(); ++j)
6✔
254
        {
255
            // todo: %22 (") should be used only for non-numbers
256
            request += "%22" + params[j] + "%22";
3✔
257
        }
258
        request += "%29";  // )
3✔
259
    }
260

261
    if (scaled)
21✔
262
    {
263
        CPLString tmp;
14✔
264
        // scaling is expressed in grid axes
265
        if (CPLGetXMLBoolean(psService, "UseScaleFactor"))
7✔
266
        {
267
            double fx = fabs((extent[2] - extent[0]) / m_gt[1] /
1✔
268
                             ((double)nBufXSize + 0.5));
1✔
269
            double fy = fabs((extent[3] - extent[1]) / m_gt[5] /
1✔
270
                             ((double)nBufYSize + 0.5));
1✔
271
            tmp.Printf("&SCALEFACTOR=%.15g", MIN(fx, fy));
1✔
272
        }
273
        else
274
        {
275
            std::vector<std::string> grid_axes =
276
                Split(CPLGetXMLValue(psService, "GridAxes", ""), ",");
12✔
277
            if (grid_axes.size() < 2)
6✔
278
            {
279
                // eek!
280
                grid_axes.push_back("E");
×
281
                grid_axes.push_back("N");
×
282
            }
283
            tmp.Printf("&SCALESIZE=%s%%28%i%%29,%s%%28%i%%29",
284
                       grid_axes[0].c_str(), nBufXSize, grid_axes[1].c_str(),
12✔
285
                       nBufYSize);
12✔
286
        }
287
        request += tmp;
7✔
288
    }
289

290
    if (range != "" && range != "*")
21✔
291
    {
292
        request += "&RANGESUBSET=" + range;
×
293
    }
294

295
    // other parameters may come from
296
    // 1) URL (others)
297
    // 2) Service file
298
    const char *keys[] = {WCS_URL_PARAMETERS};
21✔
299
    for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
231✔
300
    {
301
        std::string value;
420✔
302
        int ix = IndexOf(CPLString(keys[i]).toupper(), others);
210✔
303
        if (ix >= 0)
210✔
304
        {
305
            value = others[ix][1];
×
306
        }
307
        else
308
        {
309
            value = CPLGetXMLValue(psService, keys[i], "");
210✔
310
        }
311
        if (value != "")
210✔
312
        {
313
            request = CPLURLAddKVP(request.c_str(), keys[i], value.c_str());
21✔
314
        }
315
    }
316
    // add extra parameters
317
    std::string extra = CPLGetXMLValue(psService, "Parameters", "");
42✔
318
    if (extra != "")
21✔
319
    {
320
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
42✔
321
        for (unsigned int i = 0; i < pairs.size(); ++i)
42✔
322
        {
323
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
21✔
324
            request =
325
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
21✔
326
        }
327
    }
328
    std::vector<std::string> pairs =
329
        Split(CPLGetXMLValue(psService, "GetCoverageExtra", ""), "&");
42✔
330
    for (unsigned int i = 0; i < pairs.size(); ++i)
42✔
331
    {
332
        std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
42✔
333
        if (pair.size() > 1)
21✔
334
        {
335
            request =
336
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
21✔
337
        }
338
    }
339

340
    CPLDebug("WCS", "Requesting %s", request.c_str());
21✔
341
    return request;
42✔
342
}
343

344
/************************************************************************/
345
/*                        DescribeCoverageRequest()                     */
346
/*                                                                      */
347
/************************************************************************/
348

349
std::string WCSDataset201::DescribeCoverageRequest()
7✔
350
{
351
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
7✔
352
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
7✔
353
    request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
7✔
354
    request = CPLURLAddKVP(request.c_str(), "VERSION",
14✔
355
                           CPLGetXMLValue(psService, "Version", "2.0.1"));
14✔
356
    request = CPLURLAddKVP(request.c_str(), "COVERAGEID",
14✔
357
                           CPLGetXMLValue(psService, "CoverageName", ""));
14✔
358
    std::string extra = CPLGetXMLValue(psService, "Parameters", "");
14✔
359
    if (extra != "")
7✔
360
    {
361
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
14✔
362
        for (unsigned int i = 0; i < pairs.size(); ++i)
14✔
363
        {
364
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
7✔
365
            request =
366
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
7✔
367
        }
368
    }
369
    extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
7✔
370
    if (extra != "")
7✔
371
    {
372
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
×
373
        for (unsigned int i = 0; i < pairs.size(); ++i)
×
374
        {
375
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
×
376
            request =
377
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
×
378
        }
379
    }
380
    CPLDebug("WCS", "Requesting %s", request.c_str());
7✔
381
    return request;
14✔
382
}
383

384
/************************************************************************/
385
/*                             GridOffsets()                            */
386
/*                                                                      */
387
/************************************************************************/
388

389
bool WCSDataset201::GridOffsets(CPLXMLNode *grid, const std::string &subtype,
21✔
390
                                bool swap_grid_axis,
391
                                std::vector<double> &origin,
392
                                std::vector<std::vector<double>> &offset,
393
                                std::vector<std::string> axes, char ***metadata)
394
{
395
    // todo: use domain_index
396

397
    // origin position, center of cell
398
    CPLXMLNode *point = CPLGetXMLNode(grid, "origin.Point.pos");
21✔
399
    origin = Flist(
42✔
400
        Split(CPLGetXMLValue(point, nullptr, ""), " ", axis_order_swap), 0, 2);
63✔
401

402
    // offsets = coefficients of affine transformation from cell coords to
403
    // CRS coords, (1,2) and (4,5)
404

405
    if (subtype == "RectifiedGrid")
21✔
406
    {
407

408
        // for rectified grid the geo transform is from origin and offsetVectors
409
        int i = 0;
18✔
410
        for (CPLXMLNode *node = grid->psChild; node != nullptr;
126✔
411
             node = node->psNext)
108✔
412
        {
413
            if (node->eType != CXT_Element ||
126✔
414
                !EQUAL(node->pszValue, "offsetVector"))
90✔
415
            {
416
                continue;
90✔
417
            }
418
            offset.push_back(Flist(
36✔
419
                Split(CPLGetXMLValue(node, nullptr, ""), " ", axis_order_swap),
72✔
420
                0, 2));
421
            if (i == 1)
36✔
422
            {
423
                break;
18✔
424
            }
425
            i++;
18✔
426
        }
427
        if (offset.size() < 2)
18✔
428
        {
429
            // error or not?
430
            offset.push_back(std::vector<double>{1, 0});  // x
×
431
            offset.push_back(std::vector<double>{0, 1});  // y
×
432
        }
433
        // if axis_order_swap
434
        // the offset order should be swapped
435
        // Rasdaman does it
436
        // MapServer and GeoServer not
437
        if (swap_grid_axis)
18✔
438
        {
439
            std::swap(offset[0], offset[1]);
3✔
440
        }
441
    }
442
    else
443
    {  // if (coverage_type == "ReferenceableGrid"(ByVector)) {
444

445
        // for vector referenceable grid the geo transform is from
446
        // offsetVector, coefficients, gridAxesSpanned, sequenceRule
447
        // in generalGridAxis.GeneralGridAxis
448
        for (CPLXMLNode *node = grid->psChild; node != nullptr;
33✔
449
             node = node->psNext)
30✔
450
        {
451
            CPLXMLNode *axis = CPLGetXMLNode(node, "GeneralGridAxis");
30✔
452
            if (!axis)
30✔
453
            {
454
                continue;
21✔
455
            }
456
            std::string spanned = CPLGetXMLValue(axis, "gridAxesSpanned", "");
9✔
457
            int index = IndexOf(spanned, axes);
9✔
458
            if (index == -1)
9✔
459
            {
460
                CPLError(CE_Failure, CPLE_AppDefined,
×
461
                         "This is not a rectilinear grid(?).");
462
                return false;
×
463
            }
464
            std::string coeffs = CPLGetXMLValue(axis, "coefficients", "");
9✔
465
            if (coeffs != "")
9✔
466
            {
467
                *metadata = CSLSetNameValue(
6✔
468
                    *metadata,
469
                    CPLString().Printf("DIMENSION_%i_COEFFS", index).c_str(),
6✔
470
                    coeffs.c_str());
471
            }
472
            std::string order =
473
                CPLGetXMLValue(axis, "sequenceRule.axisOrder", "");
9✔
474
            std::string rule = CPLGetXMLValue(axis, "sequenceRule", "");
9✔
475
            if (!(order == "+1" && rule == "Linear"))
9✔
476
            {
477
                CPLError(CE_Failure, CPLE_AppDefined,
×
478
                         "Grids with sequence rule '%s' and axis order '%s' "
479
                         "are not supported.",
480
                         rule.c_str(), order.c_str());
481
                return false;
×
482
            }
483
            CPLXMLNode *offset_node = CPLGetXMLNode(axis, "offsetVector");
9✔
484
            if (offset_node)
9✔
485
            {
486
                offset.push_back(
9✔
487
                    Flist(Split(CPLGetXMLValue(offset_node, nullptr, ""), " ",
18✔
488
                                axis_order_swap),
9✔
489
                          0, 2));
490
            }
491
            else
492
            {
493
                CPLError(CE_Failure, CPLE_AppDefined,
×
494
                         "Missing offset vector in grid axis.");
495
                return false;
×
496
            }
497
        }
498
        // todo: make sure offset order is the same as the axes order but see
499
        // above
500
    }
501
    if (origin.size() < 2 || offset.size() < 2)
21✔
502
    {
503
        CPLError(CE_Failure, CPLE_AppDefined,
×
504
                 "Could not parse origin or offset vectors from grid.");
505
        return false;
×
506
    }
507
    return true;
21✔
508
}
509

510
/************************************************************************/
511
/*                             GetSubdataset()                          */
512
/*                                                                      */
513
/************************************************************************/
514

515
std::string WCSDataset201::GetSubdataset(const std::string &coverage)
×
516
{
517
    char **metadata = GDALPamDataset::GetMetadata("SUBDATASETS");
×
518
    std::string subdataset;
×
519
    if (metadata != nullptr)
×
520
    {
521
        for (int i = 0; metadata[i] != nullptr; ++i)
×
522
        {
523
            char *key;
524
            std::string url = CPLParseNameValue(metadata[i], &key);
×
525
            if (key != nullptr && strstr(key, "SUBDATASET_") &&
×
526
                strstr(key, "_NAME"))
×
527
            {
528
                if (coverage == CPLURLGetValue(url.c_str(), "coverageId"))
×
529
                {
530
                    subdataset = key;
×
531
                    subdataset.erase(subdataset.find("_NAME"), 5);
×
532
                    CPLFree(key);
×
533
                    break;
×
534
                }
535
            }
536
            CPLFree(key);
×
537
        }
538
    }
539
    return subdataset;
×
540
}
541

542
/************************************************************************/
543
/*                             SetFormat()                              */
544
/*                                                                      */
545
/************************************************************************/
546

547
bool WCSDataset201::SetFormat(CPLXMLNode *coverage)
21✔
548
{
549
    // set the Format value in service,
550
    // unless it is set by the user
551
    std::string format = CPLGetXMLValue(psService, "Format", "");
42✔
552

553
    // todo: check the value against list of supported formats?
554
    if (format != "")
21✔
555
    {
556
        return true;
14✔
557
    }
558

559
    /*      We will prefer anything that sounds like TIFF, otherwise        */
560
    /*      falling back to the first supported format.  Should we          */
561
    /*      consider preferring the nativeFormat if available?              */
562

563
    char **metadata = GDALPamDataset::GetMetadata(nullptr);
7✔
564
    const char *value =
565
        CSLFetchNameValue(metadata, "WCS_GLOBAL#formatSupported");
7✔
566
    if (value == nullptr)
7✔
567
    {
568
        format = CPLGetXMLValue(coverage, "ServiceParameters.nativeFormat", "");
×
569
    }
570
    else
571
    {
572
        std::vector<std::string> format_list = Split(value, ",");
14✔
573
        for (unsigned j = 0; j < format_list.size(); ++j)
32✔
574
        {
575
            if (CPLString(format_list[j]).ifind("tiff") != std::string::npos)
32✔
576
            {
577
                format = format_list[j];
7✔
578
                break;
7✔
579
            }
580
        }
581
        if (format == "" && format_list.size() > 0)
7✔
582
        {
583
            format = format_list[0];
×
584
        }
585
    }
586
    if (format != "")
7✔
587
    {
588
        CPLSetXMLValue(psService, "Format", format.c_str());
7✔
589
        bServiceDirty = true;
7✔
590
        return true;
7✔
591
    }
592
    else
593
    {
594
        return false;
×
595
    }
596
}
597

598
/************************************************************************/
599
/*                         ParseGridFunction()                          */
600
/*                                                                      */
601
/************************************************************************/
602

603
bool WCSDataset201::ParseGridFunction(CPLXMLNode *coverage,
21✔
604
                                      std::vector<int> &axisOrder)
605
{
606
    CPLXMLNode *function =
607
        CPLGetXMLNode(coverage, "coverageFunction.GridFunction");
21✔
608
    if (function)
21✔
609
    {
610
        std::string path = "sequenceRule";
15✔
611
        std::string sequenceRule = CPLGetXMLValue(function, path.c_str(), "");
15✔
612
        path += ".axisOrder";
15✔
613
        axisOrder =
614
            Ilist(Split(CPLGetXMLValue(function, path.c_str(), ""), " "));
15✔
615
        // for now require simple
616
        if (sequenceRule != "Linear")
15✔
617
        {
618
            CPLError(CE_Failure, CPLE_AppDefined,
×
619
                     "Can't handle '%s' coverages.", sequenceRule.c_str());
620
            return false;
×
621
        }
622
    }
623
    return true;
21✔
624
}
625

626
/************************************************************************/
627
/*                             ParseRange()                             */
628
/*                                                                      */
629
/************************************************************************/
630

631
int WCSDataset201::ParseRange(CPLXMLNode *coverage,
21✔
632
                              const std::string &range_subset, char ***metadata)
633
{
634
    int fields = 0;
21✔
635
    // Default is to include all (types permitting?)
636
    // Can also be controlled with Range parameter
637

638
    // The contents of a rangeType is a swe:DataRecord
639
    const char *path = "rangeType.DataRecord";
21✔
640
    CPLXMLNode *record = CPLGetXMLNode(coverage, path);
21✔
641
    if (!record)
21✔
642
    {
643
        CPLError(CE_Failure, CPLE_AppDefined,
×
644
                 "Attributes are not defined in a DataRecord, giving up.");
645
        return 0;
×
646
    }
647

648
    // mapserver does not like field names, it wants indexes
649
    // so we should be able to give those
650

651
    // if Range is set remove those not in it
652
    std::vector<std::string> range = Split(range_subset.c_str(), ",");
42✔
653
    // todo: add check for range subsetting profile existence in server metadata
654
    // here
655
    unsigned int range_index = 0;  // index for reading from range
21✔
656
    bool in_band_range = false;
21✔
657

658
    unsigned int field_index = 1;
21✔
659
    std::vector<std::string> nodata_array;
21✔
660

661
    for (CPLXMLNode *field = record->psChild; field != nullptr;
63✔
662
         field = field->psNext)
42✔
663
    {
664
        if (field->eType != CXT_Element || !EQUAL(field->pszValue, "field"))
42✔
665
        {
666
            continue;
×
667
        }
668
        std::string fname = CPLGetXMLValue(field, "name", "");
42✔
669
        bool include = true;
42✔
670

671
        if (range.size() > 0)
42✔
672
        {
673
            include = false;
×
674
            if (range_index < range.size())
×
675
            {
676
                std::string current_range = range[range_index];
×
677
                std::string fname_test;
×
678

679
                if (atoi(current_range.c_str()) != 0)
×
680
                {
681
                    fname_test = CPLString().Printf("%i", field_index);
×
682
                }
683
                else
684
                {
685
                    fname_test = fname;
×
686
                }
687

688
                if (current_range == "*")
×
689
                {
690
                    include = true;
×
691
                }
692
                else if (current_range == fname_test)
×
693
                {
694
                    include = true;
×
695
                    range_index += 1;
×
696
                }
697
                else if (current_range.find(fname_test + ":") !=
×
698
                         std::string::npos)
699
                {
700
                    include = true;
×
701
                    in_band_range = true;
×
702
                }
703
                else if (current_range.find(":" + fname_test) !=
×
704
                         std::string::npos)
705
                {
706
                    include = true;
×
707
                    in_band_range = false;
×
708
                    range_index += 1;
×
709
                }
710
                else if (in_band_range)
×
711
                {
712
                    include = true;
×
713
                }
714
            }
715
        }
716

717
        if (include)
42✔
718
        {
719
            const std::string key =
720
                CPLString().Printf("FIELD_%i_", field_index);
84✔
721
            *metadata = CSLSetNameValue(*metadata, (key + "NAME").c_str(),
42✔
722
                                        fname.c_str());
723

724
            std::string nodata =
725
                CPLGetXMLValue(field, "Quantity.nilValues.NilValue", "");
84✔
726
            if (nodata != "")
42✔
727
            {
728
                *metadata = CSLSetNameValue(*metadata, (key + "NODATA").c_str(),
×
729
                                            nodata.c_str());
730
            }
731

732
            std::string descr =
733
                CPLGetXMLValue(field, "Quantity.description", "");
84✔
734
            if (descr != "")
42✔
735
            {
736
                *metadata = CSLSetNameValue(*metadata, (key + "DESCR").c_str(),
18✔
737
                                            descr.c_str());
738
            }
739

740
            path = "Quantity.constraint.AllowedValues.interval";
42✔
741
            std::string interval = CPLGetXMLValue(field, path, "");
42✔
742
            if (interval != "")
42✔
743
            {
744
                *metadata = CSLSetNameValue(
54✔
745
                    *metadata, (key + "INTERVAL").c_str(), interval.c_str());
54✔
746
            }
747

748
            nodata_array.push_back(std::move(nodata));
42✔
749
            fields += 1;
42✔
750
        }
751

752
        field_index += 1;
42✔
753
    }
754

755
    if (fields == 0)
21✔
756
    {
757
        CPLError(CE_Failure, CPLE_AppDefined,
×
758
                 "No data fields found (bad Range?).");
759
    }
760
    else
761
    {
762
        // todo: default to the first one?
763
        bServiceDirty = CPLUpdateXML(psService, "NoDataValue",
21✔
764
                                     Join(nodata_array, ",").c_str()) ||
59✔
765
                        bServiceDirty;
17✔
766
    }
767

768
    return fields;
21✔
769
}
770

771
/************************************************************************/
772
/*                          ExtractGridInfo()                           */
773
/*                                                                      */
774
/*      Collect info about grid from describe coverage for WCS 2.0.     */
775
/*                                                                      */
776
/************************************************************************/
777

778
bool WCSDataset201::ExtractGridInfo()
21✔
779
{
780
    // this is for checking what's in service and for filling in empty slots in
781
    // it if the service file can be considered ready for use, this could be
782
    // skipped
783

784
    CPLXMLNode *coverage = CPLGetXMLNode(psService, "CoverageDescription");
21✔
785

786
    if (coverage == nullptr)
21✔
787
    {
788
        CPLError(CE_Failure, CPLE_AppDefined,
×
789
                 "CoverageDescription missing from service.");
790
        return false;
×
791
    }
792

793
    std::string subtype = CoverageSubtype(coverage);
42✔
794

795
    // get CRS from boundedBy.Envelope and set the native flag to true
796
    // below we may set the CRS again but that won't be native (however, non
797
    // native CRS is not yet supported) also axis order swap is set
798
    std::string path = "boundedBy.Envelope";
42✔
799
    CPLXMLNode *envelope = CPLGetXMLNode(coverage, path.c_str());
21✔
800
    if (envelope == nullptr)
21✔
801
    {
802
        path = "boundedBy.EnvelopeWithTimePeriod";
×
803
        envelope = CPLGetXMLNode(coverage, path.c_str());
×
804
        if (envelope == nullptr)
×
805
        {
806
            CPLError(CE_Failure, CPLE_AppDefined, "Missing boundedBy.Envelope");
×
807
            return false;
×
808
        }
809
    }
810
    std::vector<std::string> bbox = ParseBoundingBox(envelope);
42✔
811
    if (!SetCRS(ParseCRS(envelope), true) || bbox.size() < 2)
21✔
812
    {
813
        return false;
×
814
    }
815

816
    // has the user set the domain?
817
    std::vector<std::string> domain =
818
        Split(CPLGetXMLValue(psService, "Domain", ""), ",");
42✔
819

820
    // names of axes
821
    std::vector<std::string> axes =
822
        Split(CPLGetXMLValue(coverage, (path + ".axisLabels").c_str(), ""), " ",
×
823
              axis_order_swap);
42✔
824
    std::vector<std::string> uoms =
825
        Split(CPLGetXMLValue(coverage, (path + ".uomLabels").c_str(), ""), " ",
×
826
              axis_order_swap);
42✔
827

828
    if (axes.size() < 2)
21✔
829
    {
830
        CPLError(CE_Failure, CPLE_AppDefined,
×
831
                 "The coverage has less than 2 dimensions or no axisLabels.");
832
        return false;
×
833
    }
834

835
    std::vector<int> domain_indexes = IndexOf(domain, axes);
42✔
836
    if (Contains(domain_indexes, -1))
21✔
837
    {
838
        CPLError(CE_Failure, CPLE_AppDefined,
×
839
                 "Axis in given domain does not exist in coverage.");
840
        return false;
×
841
    }
842
    if (domain_indexes.size() == 0)
21✔
843
    {  // default is the first two
844
        domain_indexes.push_back(0);
7✔
845
        domain_indexes.push_back(1);
7✔
846
    }
847
    if (domain.size() == 0)
21✔
848
    {
849
        domain.push_back(axes[0]);
7✔
850
        domain.push_back(axes[1]);
7✔
851
        CPLSetXMLValue(psService, "Domain", Join(domain, ",").c_str());
7✔
852
        bServiceDirty = true;
7✔
853
    }
854

855
    // GridFunction (is optional)
856
    // We support only linear grid functions.
857
    // axisOrder determines how data is arranged in the grid <order><axis
858
    // number> specifically: +2 +1 => swap grid envelope and the order of the
859
    // offsets
860
    std::vector<int> axisOrder;
42✔
861
    if (!ParseGridFunction(coverage, axisOrder))
21✔
862
    {
863
        return false;
×
864
    }
865

866
    const char *md_domain = "";
21✔
867
    char **metadata = CSLDuplicate(
63✔
868
        GetMetadata(md_domain));  // coverage metadata to be added/updated
21✔
869

870
    metadata = CSLSetNameValue(metadata, "DOMAIN", Join(domain, ",").c_str());
21✔
871

872
    // add coverage metadata: GeoServer TimeDomain
873

874
    CPLXMLNode *timedomain =
875
        CPLGetXMLNode(coverage, "metadata.Extension.TimeDomain");
21✔
876
    if (timedomain)
21✔
877
    {
878
        std::vector<std::string> timePositions;
×
879
        // "//timePosition"
880
        for (CPLXMLNode *node = timedomain->psChild; node != nullptr;
×
881
             node = node->psNext)
×
882
        {
883
            if (node->eType != CXT_Element ||
×
884
                strcmp(node->pszValue, "TimeInstant") != 0)
×
885
            {
886
                continue;
×
887
            }
888
            for (CPLXMLNode *node2 = node->psChild; node2 != nullptr;
×
889
                 node2 = node2->psNext)
×
890
            {
891
                if (node2->eType != CXT_Element ||
×
892
                    strcmp(node2->pszValue, "timePosition") != 0)
×
893
                {
894
                    continue;
×
895
                }
896
                timePositions.push_back(CPLGetXMLValue(node2, "", ""));
×
897
            }
898
        }
899
        metadata = CSLSetNameValue(metadata, "TimeDomain",
×
900
                                   Join(timePositions, ",").c_str());
×
901
    }
902

903
    // dimension metadata
904

905
    std::vector<std::string> slow =
906
        Split(bbox[0].c_str(), " ", axis_order_swap);
42✔
907
    std::vector<std::string> shigh =
908
        Split(bbox[1].c_str(), " ", axis_order_swap);
42✔
909
    bServiceDirty = CPLUpdateXML(psService, "Low", Join(slow, ",").c_str()) ||
35✔
910
                    bServiceDirty;
14✔
911
    bServiceDirty = CPLUpdateXML(psService, "High", Join(shigh, ",").c_str()) ||
35✔
912
                    bServiceDirty;
14✔
913
    if (slow.size() < 2 || shigh.size() < 2)
21✔
914
    {
915
        CPLError(CE_Failure, CPLE_AppDefined,
×
916
                 "The coverage has less than 2 dimensions.");
917
        CSLDestroy(metadata);
×
918
        return false;
×
919
    }
920
    // todo: if our x,y domain is not the first two? use domain_indexes?
921
    std::vector<double> low = Flist(slow, 0, 2);
42✔
922
    std::vector<double> high = Flist(shigh, 0, 2);
42✔
923
    std::vector<double> env;
42✔
924
    env.insert(env.end(), low.begin(), low.begin() + 2);
21✔
925
    env.insert(env.end(), high.begin(), high.begin() + 2);
21✔
926

927
    for (unsigned int i = 0; i < axes.size(); ++i)
66✔
928
    {
929
        const std::string key = CPLString().Printf("DIMENSION_%i_", i);
90✔
930
        metadata =
45✔
931
            CSLSetNameValue(metadata, (key + "AXIS").c_str(), axes[i].c_str());
45✔
932
        if (i < uoms.size())
45✔
933
        {
934
            metadata = CSLSetNameValue(metadata, (key + "UOM").c_str(),
39✔
935
                                       uoms[i].c_str());
39✔
936
        }
937
        if (i < 2)
45✔
938
        {
939
            metadata = CSLSetNameValue(
42✔
940
                metadata, (key + "INTERVAL").c_str(),
84✔
941
                CPLString().Printf("%.15g,%.15g", low[i], high[i]));
84✔
942
        }
943
        else if (i < slow.size() && i < shigh.size())
3✔
944
        {
945
            metadata = CSLSetNameValue(
3✔
946
                metadata, (key + "INTERVAL").c_str(),
6✔
947
                CPLString().Printf("%s,%s", slow[i].c_str(), shigh[i].c_str()));
6✔
948
        }
949
        else if (i < bbox.size())
×
950
        {
951
            metadata = CSLSetNameValue(metadata, (key + "INTERVAL").c_str(),
×
952
                                       bbox[i].c_str());
×
953
        }
954
    }
955

956
    // domainSet
957
    // requirement 23: the srsName here _shall_ be the same as in boundedBy
958
    // => we ignore it
959
    // the CRS of this dataset is from boundedBy (unless it is overridden)
960
    // this is the size of this dataset
961
    // this gives the geotransform of this dataset (unless there is CRS
962
    // override)
963

964
    CPLXMLNode *grid = GetGridNode(coverage, subtype);
21✔
965
    if (!grid)
21✔
966
    {
967
        CSLDestroy(metadata);
×
968
        return false;
×
969
    }
970

971
    //
972
    bool swap_grid_axis = false;
21✔
973
    if (axisOrder.size() >= 2 && axisOrder[domain_indexes[0]] == 2 &&
30✔
974
        axisOrder[domain_indexes[1]] == 1)
9✔
975
    {
976
        swap_grid_axis = !CPLGetXMLBoolean(psService, "NoGridAxisSwap");
9✔
977
    }
978
    path = "limits.GridEnvelope";
21✔
979
    std::vector<std::vector<int>> size =
980
        ParseGridEnvelope(CPLGetXMLNode(grid, path.c_str()), swap_grid_axis);
42✔
981
    std::vector<int> grid_size;
42✔
982
    if (size.size() < 2)
21✔
983
    {
984
        CPLError(CE_Failure, CPLE_AppDefined, "Can't parse the grid envelope.");
×
985
        CSLDestroy(metadata);
×
986
        return false;
×
987
    }
988

989
    grid_size.push_back(size[1][domain_indexes[0]] -
21✔
990
                        size[0][domain_indexes[0]] + 1);
21✔
991
    grid_size.push_back(size[1][domain_indexes[1]] -
21✔
992
                        size[0][domain_indexes[1]] + 1);
21✔
993

994
    path = "axisLabels";
21✔
995
    bool swap_grid_axis_labels =
996
        swap_grid_axis || CPLGetXMLBoolean(psService, "GridAxisLabelSwap");
21✔
997
    std::vector<std::string> grid_axes = Split(
998
        CPLGetXMLValue(grid, path.c_str(), ""), " ", swap_grid_axis_labels);
42✔
999
    // autocorrect MapServer thing
1000
    if (grid_axes.size() >= 2 && grid_axes[0] == "lat" &&
24✔
1001
        grid_axes[1] == "long")
3✔
1002
    {
1003
        grid_axes[0] = "long";
3✔
1004
        grid_axes[1] = "lat";
3✔
1005
    }
1006
    bServiceDirty =
21✔
1007
        CPLUpdateXML(psService, "GridAxes", Join(grid_axes, ",").c_str()) ||
35✔
1008
        bServiceDirty;
14✔
1009

1010
    std::vector<double> origin;
42✔
1011
    std::vector<std::vector<double>> offsets;
42✔
1012
    if (!GridOffsets(grid, subtype, swap_grid_axis, origin, offsets, axes,
21✔
1013
                     &metadata))
1014
    {
1015
        CSLDestroy(metadata);
×
1016
        return false;
×
1017
    }
1018

1019
    SetGeometry(grid_size, origin, offsets);
21✔
1020

1021
    // subsetting and dimension to bands
1022
    std::vector<std::string> dimensions;
42✔
1023
    std::string range;
42✔
1024
    std::vector<std::vector<std::string>> others;
42✔
1025
    ParseParameters(psService, dimensions, range, others);
21✔
1026

1027
    // it is ok to have trimming or even slicing for x/y, it just affects our
1028
    // bounding box but that is a todo item todo: BoundGeometry(domain_trim) if
1029
    // domain_trim.size() > 0
1030
    std::vector<std::vector<double>> domain_trim;
42✔
1031

1032
    // are all dimensions that are not x/y domain sliced?
1033
    // if not, bands can't be defined, see below
1034
    bool dimensions_are_ok = true;
21✔
1035
    for (unsigned int i = 0; i < axes.size(); ++i)
66✔
1036
    {
1037
        std::vector<std::string> params;
45✔
1038
        for (unsigned int j = 0; j < dimensions.size(); ++j)
51✔
1039
        {
1040
            if (dimensions[j].find(axes[i] + "(") != std::string::npos)
9✔
1041
            {
1042
                params = Split(FromParenthesis(dimensions[j]).c_str(), ",");
3✔
1043
                break;
3✔
1044
            }
1045
        }
1046
        int domain_index = IndexOf(axes[i], domain);
45✔
1047
        if (domain_index != -1)
45✔
1048
        {
1049
            domain_trim.push_back(Flist(params, 0, 2));
42✔
1050
            continue;
42✔
1051
        }
1052
        // size == 1 => sliced
1053
        if (params.size() != 1)
3✔
1054
        {
1055
            dimensions_are_ok = false;
×
1056
        }
1057
    }
1058
    // todo: add metadata: note: no bands, you need to subset to get data
1059

1060
    // check for CRS override
1061
    std::string crs = CPLGetXMLValue(psService, "SRS", "");
42✔
1062
    if (crs != "" && crs != osCRS)
21✔
1063
    {
1064
        if (!SetCRS(crs, false))
×
1065
        {
1066
            CSLDestroy(metadata);
×
1067
            return false;
×
1068
        }
1069
        // todo: support CRS override, it requires warping the grid to the new
1070
        // CRS SetGeometry(grid_size, origin, offsets);
1071
        CPLError(CE_Failure, CPLE_AppDefined,
×
1072
                 "CRS override not yet supported.");
1073
        CSLDestroy(metadata);
×
1074
        return false;
×
1075
    }
1076

1077
    // todo: ElevationDomain, DimensionDomain
1078

1079
    // rangeType
1080

1081
    // get the field metadata
1082
    // get the count of fields
1083
    // if Range is set in service that may limit the fields
1084
    int fields = ParseRange(coverage, range, &metadata);
21✔
1085
    // if fields is 0 an error message has been emitted
1086
    // but we let this go on since the user may be experimenting
1087
    // and she wants to see the resulting metadata and not just an error message
1088
    // situation is ~the same when bands == 0 when we exit here
1089

1090
    // todo: do this only if metadata is dirty
1091
    this->SetMetadata(metadata, md_domain);
21✔
1092
    CSLDestroy(metadata);
21✔
1093
    TrySaveXML();
21✔
1094

1095
    // determine the band count
1096
    int bands = 0;
21✔
1097
    if (dimensions_are_ok)
21✔
1098
    {
1099
        bands = fields;
21✔
1100
    }
1101
    bServiceDirty =
21✔
1102
        CPLUpdateXML(psService, "BandCount", CPLString().Printf("%d", bands)) ||
35✔
1103
        bServiceDirty;
14✔
1104

1105
    // set the Format value in service, unless it is set
1106
    // by the user, either through direct edit or options
1107
    if (!SetFormat(coverage))
21✔
1108
    {
1109
        // all attempts to find a format have failed...
1110
        CPLError(CE_Failure, CPLE_AppDefined,
×
1111
                 "All attempts to find a format have failed, giving up.");
1112
        return false;
×
1113
    }
1114

1115
    return true;
21✔
1116
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc