Coverage Report

Created: 2025-06-09 07:42

/src/gdal/frmts/wcs/wcsdataset201.cpp
Line
Count
Source (jump to first uncovered line)
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)
39
0
{
40
0
    std::string subtype =
41
0
        CPLGetXMLValue(coverage, "ServiceParameters.CoverageSubtype", "");
42
0
    size_t pos = subtype.find("Coverage");
43
0
    if (pos != std::string::npos)
44
0
    {
45
0
        subtype.erase(pos);
46
0
    }
47
0
    return subtype;
48
0
}
49
50
/************************************************************************/
51
/*                         GetGridNode()                                */
52
/*                                                                      */
53
/************************************************************************/
54
55
static CPLXMLNode *GetGridNode(CPLXMLNode *coverage, const std::string &subtype)
56
0
{
57
0
    CPLXMLNode *grid = nullptr;
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
0
    std::string path = "domainSet";
63
0
    if (subtype == "RectifiedGrid")
64
0
    {
65
0
        grid = CPLGetXMLNode(coverage, (path + "." + subtype).c_str());
66
0
    }
67
0
    else if (subtype == "ReferenceableGrid")
68
0
    {
69
0
        grid = CPLGetXMLNode(coverage,
70
0
                             (path + "." + subtype + "ByVectors").c_str());
71
0
    }
72
0
    if (!grid)
73
0
    {
74
0
        CPLError(CE_Failure, CPLE_AppDefined,
75
0
                 "Can't handle coverages of type '%s'.", subtype.c_str());
76
0
    }
77
0
    return grid;
78
0
}
79
80
/************************************************************************/
81
/*                         ParseParameters()                            */
82
/*                                                                      */
83
/************************************************************************/
84
85
static void ParseParameters(CPLXMLNode *service,
86
                            std::vector<std::string> &dimensions,
87
                            std::string &range,
88
                            std::vector<std::vector<std::string>> &others)
89
0
{
90
0
    std::vector<std::string> parameters =
91
0
        Split(CPLGetXMLValue(service, "Parameters", ""), "&");
92
0
    for (unsigned int i = 0; i < parameters.size(); ++i)
93
0
    {
94
0
        std::vector<std::string> kv = Split(parameters[i].c_str(), "=");
95
0
        if (kv.size() < 2)
96
0
        {
97
0
            continue;
98
0
        }
99
0
        kv[0] = CPLString(kv[0]).toupper();
100
0
        if (kv[0] == "RANGESUBSET")
101
0
        {
102
0
            range = kv[1];
103
0
        }
104
0
        else if (kv[0] == "SUBSET")
105
0
        {
106
0
            dimensions = Split(kv[1].c_str(), ";");
107
0
        }
108
0
        else
109
0
        {
110
0
            others.push_back(std::vector<std::string>{kv[0], kv[1]});
111
0
        }
112
0
    }
113
    // fallback to service values, if any
114
0
    if (range == "")
115
0
    {
116
0
        range = CPLGetXMLValue(service, "RangeSubset", "");
117
0
    }
118
0
    if (dimensions.size() == 0)
119
0
    {
120
0
        dimensions = Split(CPLGetXMLValue(service, "Subset", ""), ";");
121
0
    }
122
0
}
123
124
/************************************************************************/
125
/*                         GetExtent()                                  */
126
/*                                                                      */
127
/************************************************************************/
128
129
std::vector<double> WCSDataset201::GetExtent(int nXOff, int nYOff, int nXSize,
130
                                             int nYSize,
131
                                             CPL_UNUSED int nBufXSize,
132
                                             CPL_UNUSED int nBufYSize)
133
0
{
134
0
    std::vector<double> extent;
135
    // WCS 2.0 extents are the outer edges of outer pixels.
136
0
    extent.push_back(adfGeoTransform[0] + (nXOff)*adfGeoTransform[1]);
137
0
    extent.push_back(adfGeoTransform[3] +
138
0
                     (nYOff + nYSize) * adfGeoTransform[5]);
139
0
    extent.push_back(adfGeoTransform[0] +
140
0
                     (nXOff + nXSize) * adfGeoTransform[1]);
141
0
    extent.push_back(adfGeoTransform[3] + (nYOff)*adfGeoTransform[5]);
142
0
    return extent;
143
0
}
144
145
/************************************************************************/
146
/*                        GetCoverageRequest()                          */
147
/*                                                                      */
148
/************************************************************************/
149
150
std::string
151
WCSDataset201::GetCoverageRequest(bool scaled, int nBufXSize, int nBufYSize,
152
                                  const std::vector<double> &extent,
153
                                  const std::string & /*osBandList*/)
154
0
{
155
0
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
156
0
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
157
0
    request += "&REQUEST=GetCoverage";
158
0
    request +=
159
0
        "&VERSION=" + std::string(CPLGetXMLValue(psService, "Version", ""));
160
0
    request += "&COVERAGEID=" +
161
0
               URLEncode(CPLGetXMLValue(psService, "CoverageName", ""));
162
163
    // note: native_crs is not really supported
164
0
    if (!native_crs)
165
0
    {
166
0
        std::string crs = URLEncode(CPLGetXMLValue(psService, "SRS", ""));
167
0
        request += "&OUTPUTCRS=" + crs;
168
0
        request += "&SUBSETTINGCRS=" + crs;
169
0
    }
170
171
0
    std::vector<std::string> domain =
172
0
        Split(CPLGetXMLValue(psService, "Domain", ""), ",");
173
0
    if (domain.size() < 2)
174
0
    {
175
        // eek!
176
0
        domain.push_back("E");
177
0
        domain.push_back("N");
178
0
    }
179
0
    const char *x = domain[0].c_str();
180
0
    const char *y = domain[1].c_str();
181
0
    if (CPLGetXMLBoolean(psService, "SubsetAxisSwap"))
182
0
    {
183
0
        const char *tmp = x;
184
0
        x = y;
185
0
        y = tmp;
186
0
    }
187
188
0
    std::vector<std::string> low =
189
0
        Split(CPLGetXMLValue(psService, "Low", ""), ",");
190
0
    std::vector<std::string> high =
191
0
        Split(CPLGetXMLValue(psService, "High", ""), ",");
192
0
    std::string a = CPLString().Printf("%.17g", extent[0]);
193
0
    if (low.size() > 1 && CPLAtof(low[0].c_str()) > extent[0])
194
0
    {
195
0
        a = low[0];
196
0
    }
197
0
    std::string b = CPLString().Printf("%.17g", extent[2]);
198
0
    if (high.size() > 1 && CPLAtof(high[0].c_str()) < extent[2])
199
0
    {
200
0
        b = high[0];
201
0
    }
202
    /*
203
    std::string a = CPLString().Printf(
204
        "%.17g", MAX(adfGeoTransform[0], extent[0]));
205
    std::string b = CPLString().Printf(
206
        "%.17g", MIN(adfGeoTransform[0] + nRasterXSize * adfGeoTransform[1],
207
    extent[2]));
208
    */
209
210
    // 09-147 KVP Protocol: subset keys must be unique
211
    // GeoServer: seems to require plain SUBSET for x and y
212
213
0
    request +=
214
0
        CPLString().Printf("&SUBSET=%s%%28%s,%s%%29", x, a.c_str(), b.c_str());
215
216
0
    a = CPLString().Printf("%.17g", extent[1]);
217
0
    if (low.size() > 1 && CPLAtof(low[1].c_str()) > extent[1])
218
0
    {
219
0
        a = low[1];
220
0
    }
221
0
    b = CPLString().Printf("%.17g", extent[3]);
222
0
    if (high.size() > 1 && CPLAtof(high[1].c_str()) < extent[3])
223
0
    {
224
0
        b = high[1];
225
0
    }
226
    /*
227
    a = CPLString().Printf(
228
        "%.17g", MAX(adfGeoTransform[3] + nRasterYSize * adfGeoTransform[5],
229
    extent[1])); b = CPLString().Printf(
230
        "%.17g", MIN(adfGeoTransform[3], extent[3]));
231
    */
232
233
0
    request +=
234
0
        CPLString().Printf("&SUBSET=%s%%28%s,%s%%29", y, a.c_str(), b.c_str());
235
236
    // Dimension and range parameters:
237
0
    std::vector<std::string> dimensions;
238
0
    std::string range;
239
0
    std::vector<std::vector<std::string>> others;
240
0
    ParseParameters(psService, dimensions, range, others);
241
242
    // set subsets for axis other than x/y
243
0
    for (unsigned int i = 0; i < dimensions.size(); ++i)
244
0
    {
245
0
        size_t pos = dimensions[i].find("(");
246
0
        std::string dim = dimensions[i].substr(0, pos);
247
0
        if (IndexOf(dim, domain) != -1)
248
0
        {
249
0
            continue;
250
0
        }
251
0
        std::vector<std::string> params =
252
0
            Split(FromParenthesis(dimensions[i]).c_str(), ",");
253
0
        request +=
254
0
            "&SUBSET" + CPLString().Printf("%i", i) + "=" + dim + "%28";  // (
255
0
        for (unsigned int j = 0; j < params.size(); ++j)
256
0
        {
257
            // todo: %22 (") should be used only for non-numbers
258
0
            request += "%22" + params[j] + "%22";
259
0
        }
260
0
        request += "%29";  // )
261
0
    }
262
263
0
    if (scaled)
264
0
    {
265
0
        CPLString tmp;
266
        // scaling is expressed in grid axes
267
0
        if (CPLGetXMLBoolean(psService, "UseScaleFactor"))
268
0
        {
269
0
            double fx = fabs((extent[2] - extent[0]) / adfGeoTransform[1] /
270
0
                             ((double)nBufXSize + 0.5));
271
0
            double fy = fabs((extent[3] - extent[1]) / adfGeoTransform[5] /
272
0
                             ((double)nBufYSize + 0.5));
273
0
            tmp.Printf("&SCALEFACTOR=%.15g", MIN(fx, fy));
274
0
        }
275
0
        else
276
0
        {
277
0
            std::vector<std::string> grid_axes =
278
0
                Split(CPLGetXMLValue(psService, "GridAxes", ""), ",");
279
0
            if (grid_axes.size() < 2)
280
0
            {
281
                // eek!
282
0
                grid_axes.push_back("E");
283
0
                grid_axes.push_back("N");
284
0
            }
285
0
            tmp.Printf("&SCALESIZE=%s%%28%i%%29,%s%%28%i%%29",
286
0
                       grid_axes[0].c_str(), nBufXSize, grid_axes[1].c_str(),
287
0
                       nBufYSize);
288
0
        }
289
0
        request += tmp;
290
0
    }
291
292
0
    if (range != "" && range != "*")
293
0
    {
294
0
        request += "&RANGESUBSET=" + range;
295
0
    }
296
297
    // other parameters may come from
298
    // 1) URL (others)
299
    // 2) Service file
300
0
    const char *keys[] = {WCS_URL_PARAMETERS};
301
0
    for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
302
0
    {
303
0
        std::string value;
304
0
        int ix = IndexOf(CPLString(keys[i]).toupper(), others);
305
0
        if (ix >= 0)
306
0
        {
307
0
            value = others[ix][1];
308
0
        }
309
0
        else
310
0
        {
311
0
            value = CPLGetXMLValue(psService, keys[i], "");
312
0
        }
313
0
        if (value != "")
314
0
        {
315
0
            request = CPLURLAddKVP(request.c_str(), keys[i], value.c_str());
316
0
        }
317
0
    }
318
    // add extra parameters
319
0
    std::string extra = CPLGetXMLValue(psService, "Parameters", "");
320
0
    if (extra != "")
321
0
    {
322
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
323
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
324
0
        {
325
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
326
0
            request =
327
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
328
0
        }
329
0
    }
330
0
    std::vector<std::string> pairs =
331
0
        Split(CPLGetXMLValue(psService, "GetCoverageExtra", ""), "&");
332
0
    for (unsigned int i = 0; i < pairs.size(); ++i)
333
0
    {
334
0
        std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
335
0
        if (pair.size() > 1)
336
0
        {
337
0
            request =
338
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
339
0
        }
340
0
    }
341
342
0
    CPLDebug("WCS", "Requesting %s", request.c_str());
343
0
    return request;
344
0
}
345
346
/************************************************************************/
347
/*                        DescribeCoverageRequest()                     */
348
/*                                                                      */
349
/************************************************************************/
350
351
std::string WCSDataset201::DescribeCoverageRequest()
352
0
{
353
0
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
354
0
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
355
0
    request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
356
0
    request = CPLURLAddKVP(request.c_str(), "VERSION",
357
0
                           CPLGetXMLValue(psService, "Version", "2.0.1"));
358
0
    request = CPLURLAddKVP(request.c_str(), "COVERAGEID",
359
0
                           CPLGetXMLValue(psService, "CoverageName", ""));
360
0
    std::string extra = CPLGetXMLValue(psService, "Parameters", "");
361
0
    if (extra != "")
362
0
    {
363
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
364
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
365
0
        {
366
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
367
0
            request =
368
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
369
0
        }
370
0
    }
371
0
    extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
372
0
    if (extra != "")
373
0
    {
374
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
375
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
376
0
        {
377
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
378
0
            request =
379
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
380
0
        }
381
0
    }
382
0
    CPLDebug("WCS", "Requesting %s", request.c_str());
383
0
    return request;
384
0
}
385
386
/************************************************************************/
387
/*                             GridOffsets()                            */
388
/*                                                                      */
389
/************************************************************************/
390
391
bool WCSDataset201::GridOffsets(CPLXMLNode *grid, const std::string &subtype,
392
                                bool swap_grid_axis,
393
                                std::vector<double> &origin,
394
                                std::vector<std::vector<double>> &offset,
395
                                std::vector<std::string> axes, char ***metadata)
396
0
{
397
    // todo: use domain_index
398
399
    // origin position, center of cell
400
0
    CPLXMLNode *point = CPLGetXMLNode(grid, "origin.Point.pos");
401
0
    origin = Flist(
402
0
        Split(CPLGetXMLValue(point, nullptr, ""), " ", axis_order_swap), 0, 2);
403
404
    // offsets = coefficients of affine transformation from cell coords to
405
    // CRS coords, (1,2) and (4,5)
406
407
0
    if (subtype == "RectifiedGrid")
408
0
    {
409
410
        // for rectified grid the geo transform is from origin and offsetVectors
411
0
        int i = 0;
412
0
        for (CPLXMLNode *node = grid->psChild; node != nullptr;
413
0
             node = node->psNext)
414
0
        {
415
0
            if (node->eType != CXT_Element ||
416
0
                !EQUAL(node->pszValue, "offsetVector"))
417
0
            {
418
0
                continue;
419
0
            }
420
0
            offset.push_back(Flist(
421
0
                Split(CPLGetXMLValue(node, nullptr, ""), " ", axis_order_swap),
422
0
                0, 2));
423
0
            if (i == 1)
424
0
            {
425
0
                break;
426
0
            }
427
0
            i++;
428
0
        }
429
0
        if (offset.size() < 2)
430
0
        {
431
            // error or not?
432
0
            offset.push_back(std::vector<double>{1, 0});  // x
433
0
            offset.push_back(std::vector<double>{0, 1});  // y
434
0
        }
435
        // if axis_order_swap
436
        // the offset order should be swapped
437
        // Rasdaman does it
438
        // MapServer and GeoServer not
439
0
        if (swap_grid_axis)
440
0
        {
441
0
            std::swap(offset[0], offset[1]);
442
0
        }
443
0
    }
444
0
    else
445
0
    {  // if (coverage_type == "ReferenceableGrid"(ByVector)) {
446
447
        // for vector referenceable grid the geo transform is from
448
        // offsetVector, coefficients, gridAxesSpanned, sequenceRule
449
        // in generalGridAxis.GeneralGridAxis
450
0
        for (CPLXMLNode *node = grid->psChild; node != nullptr;
451
0
             node = node->psNext)
452
0
        {
453
0
            CPLXMLNode *axis = CPLGetXMLNode(node, "GeneralGridAxis");
454
0
            if (!axis)
455
0
            {
456
0
                continue;
457
0
            }
458
0
            std::string spanned = CPLGetXMLValue(axis, "gridAxesSpanned", "");
459
0
            int index = IndexOf(spanned, axes);
460
0
            if (index == -1)
461
0
            {
462
0
                CPLError(CE_Failure, CPLE_AppDefined,
463
0
                         "This is not a rectilinear grid(?).");
464
0
                return false;
465
0
            }
466
0
            std::string coeffs = CPLGetXMLValue(axis, "coefficients", "");
467
0
            if (coeffs != "")
468
0
            {
469
0
                *metadata = CSLSetNameValue(
470
0
                    *metadata,
471
0
                    CPLString().Printf("DIMENSION_%i_COEFFS", index).c_str(),
472
0
                    coeffs.c_str());
473
0
            }
474
0
            std::string order =
475
0
                CPLGetXMLValue(axis, "sequenceRule.axisOrder", "");
476
0
            std::string rule = CPLGetXMLValue(axis, "sequenceRule", "");
477
0
            if (!(order == "+1" && rule == "Linear"))
478
0
            {
479
0
                CPLError(CE_Failure, CPLE_AppDefined,
480
0
                         "Grids with sequence rule '%s' and axis order '%s' "
481
0
                         "are not supported.",
482
0
                         rule.c_str(), order.c_str());
483
0
                return false;
484
0
            }
485
0
            CPLXMLNode *offset_node = CPLGetXMLNode(axis, "offsetVector");
486
0
            if (offset_node)
487
0
            {
488
0
                offset.push_back(
489
0
                    Flist(Split(CPLGetXMLValue(offset_node, nullptr, ""), " ",
490
0
                                axis_order_swap),
491
0
                          0, 2));
492
0
            }
493
0
            else
494
0
            {
495
0
                CPLError(CE_Failure, CPLE_AppDefined,
496
0
                         "Missing offset vector in grid axis.");
497
0
                return false;
498
0
            }
499
0
        }
500
        // todo: make sure offset order is the same as the axes order but see
501
        // above
502
0
    }
503
0
    if (origin.size() < 2 || offset.size() < 2)
504
0
    {
505
0
        CPLError(CE_Failure, CPLE_AppDefined,
506
0
                 "Could not parse origin or offset vectors from grid.");
507
0
        return false;
508
0
    }
509
0
    return true;
510
0
}
511
512
/************************************************************************/
513
/*                             GetSubdataset()                          */
514
/*                                                                      */
515
/************************************************************************/
516
517
std::string WCSDataset201::GetSubdataset(const std::string &coverage)
518
0
{
519
0
    char **metadata = GDALPamDataset::GetMetadata("SUBDATASETS");
520
0
    std::string subdataset;
521
0
    if (metadata != nullptr)
522
0
    {
523
0
        for (int i = 0; metadata[i] != nullptr; ++i)
524
0
        {
525
0
            char *key;
526
0
            std::string url = CPLParseNameValue(metadata[i], &key);
527
0
            if (key != nullptr && strstr(key, "SUBDATASET_") &&
528
0
                strstr(key, "_NAME"))
529
0
            {
530
0
                if (coverage == CPLURLGetValue(url.c_str(), "coverageId"))
531
0
                {
532
0
                    subdataset = key;
533
0
                    subdataset.erase(subdataset.find("_NAME"), 5);
534
0
                    CPLFree(key);
535
0
                    break;
536
0
                }
537
0
            }
538
0
            CPLFree(key);
539
0
        }
540
0
    }
541
0
    return subdataset;
542
0
}
543
544
/************************************************************************/
545
/*                             SetFormat()                              */
546
/*                                                                      */
547
/************************************************************************/
548
549
bool WCSDataset201::SetFormat(CPLXMLNode *coverage)
550
0
{
551
    // set the Format value in service,
552
    // unless it is set by the user
553
0
    std::string format = CPLGetXMLValue(psService, "Format", "");
554
555
    // todo: check the value against list of supported formats?
556
0
    if (format != "")
557
0
    {
558
0
        return true;
559
0
    }
560
561
    /*      We will prefer anything that sounds like TIFF, otherwise        */
562
    /*      falling back to the first supported format.  Should we          */
563
    /*      consider preferring the nativeFormat if available?              */
564
565
0
    char **metadata = GDALPamDataset::GetMetadata(nullptr);
566
0
    const char *value =
567
0
        CSLFetchNameValue(metadata, "WCS_GLOBAL#formatSupported");
568
0
    if (value == nullptr)
569
0
    {
570
0
        format = CPLGetXMLValue(coverage, "ServiceParameters.nativeFormat", "");
571
0
    }
572
0
    else
573
0
    {
574
0
        std::vector<std::string> format_list = Split(value, ",");
575
0
        for (unsigned j = 0; j < format_list.size(); ++j)
576
0
        {
577
0
            if (CPLString(format_list[j]).ifind("tiff") != std::string::npos)
578
0
            {
579
0
                format = format_list[j];
580
0
                break;
581
0
            }
582
0
        }
583
0
        if (format == "" && format_list.size() > 0)
584
0
        {
585
0
            format = format_list[0];
586
0
        }
587
0
    }
588
0
    if (format != "")
589
0
    {
590
0
        CPLSetXMLValue(psService, "Format", format.c_str());
591
0
        bServiceDirty = true;
592
0
        return true;
593
0
    }
594
0
    else
595
0
    {
596
0
        return false;
597
0
    }
598
0
}
599
600
/************************************************************************/
601
/*                         ParseGridFunction()                          */
602
/*                                                                      */
603
/************************************************************************/
604
605
bool WCSDataset201::ParseGridFunction(CPLXMLNode *coverage,
606
                                      std::vector<int> &axisOrder)
607
0
{
608
0
    CPLXMLNode *function =
609
0
        CPLGetXMLNode(coverage, "coverageFunction.GridFunction");
610
0
    if (function)
611
0
    {
612
0
        std::string path = "sequenceRule";
613
0
        std::string sequenceRule = CPLGetXMLValue(function, path.c_str(), "");
614
0
        path += ".axisOrder";
615
0
        axisOrder =
616
0
            Ilist(Split(CPLGetXMLValue(function, path.c_str(), ""), " "));
617
        // for now require simple
618
0
        if (sequenceRule != "Linear")
619
0
        {
620
0
            CPLError(CE_Failure, CPLE_AppDefined,
621
0
                     "Can't handle '%s' coverages.", sequenceRule.c_str());
622
0
            return false;
623
0
        }
624
0
    }
625
0
    return true;
626
0
}
627
628
/************************************************************************/
629
/*                             ParseRange()                             */
630
/*                                                                      */
631
/************************************************************************/
632
633
int WCSDataset201::ParseRange(CPLXMLNode *coverage,
634
                              const std::string &range_subset, char ***metadata)
635
0
{
636
0
    int fields = 0;
637
    // Default is to include all (types permitting?)
638
    // Can also be controlled with Range parameter
639
640
    // The contents of a rangeType is a swe:DataRecord
641
0
    const char *path = "rangeType.DataRecord";
642
0
    CPLXMLNode *record = CPLGetXMLNode(coverage, path);
643
0
    if (!record)
644
0
    {
645
0
        CPLError(CE_Failure, CPLE_AppDefined,
646
0
                 "Attributes are not defined in a DataRecord, giving up.");
647
0
        return 0;
648
0
    }
649
650
    // mapserver does not like field names, it wants indexes
651
    // so we should be able to give those
652
653
    // if Range is set remove those not in it
654
0
    std::vector<std::string> range = Split(range_subset.c_str(), ",");
655
    // todo: add check for range subsetting profile existence in server metadata
656
    // here
657
0
    unsigned int range_index = 0;  // index for reading from range
658
0
    bool in_band_range = false;
659
660
0
    unsigned int field_index = 1;
661
0
    std::vector<std::string> nodata_array;
662
663
0
    for (CPLXMLNode *field = record->psChild; field != nullptr;
664
0
         field = field->psNext)
665
0
    {
666
0
        if (field->eType != CXT_Element || !EQUAL(field->pszValue, "field"))
667
0
        {
668
0
            continue;
669
0
        }
670
0
        std::string fname = CPLGetXMLValue(field, "name", "");
671
0
        bool include = true;
672
673
0
        if (range.size() > 0)
674
0
        {
675
0
            include = false;
676
0
            if (range_index < range.size())
677
0
            {
678
0
                std::string current_range = range[range_index];
679
0
                std::string fname_test;
680
681
0
                if (atoi(current_range.c_str()) != 0)
682
0
                {
683
0
                    fname_test = CPLString().Printf("%i", field_index);
684
0
                }
685
0
                else
686
0
                {
687
0
                    fname_test = fname;
688
0
                }
689
690
0
                if (current_range == "*")
691
0
                {
692
0
                    include = true;
693
0
                }
694
0
                else if (current_range == fname_test)
695
0
                {
696
0
                    include = true;
697
0
                    range_index += 1;
698
0
                }
699
0
                else if (current_range.find(fname_test + ":") !=
700
0
                         std::string::npos)
701
0
                {
702
0
                    include = true;
703
0
                    in_band_range = true;
704
0
                }
705
0
                else if (current_range.find(":" + fname_test) !=
706
0
                         std::string::npos)
707
0
                {
708
0
                    include = true;
709
0
                    in_band_range = false;
710
0
                    range_index += 1;
711
0
                }
712
0
                else if (in_band_range)
713
0
                {
714
0
                    include = true;
715
0
                }
716
0
            }
717
0
        }
718
719
0
        if (include)
720
0
        {
721
0
            const std::string key =
722
0
                CPLString().Printf("FIELD_%i_", field_index);
723
0
            *metadata = CSLSetNameValue(*metadata, (key + "NAME").c_str(),
724
0
                                        fname.c_str());
725
726
0
            std::string nodata =
727
0
                CPLGetXMLValue(field, "Quantity.nilValues.NilValue", "");
728
0
            if (nodata != "")
729
0
            {
730
0
                *metadata = CSLSetNameValue(*metadata, (key + "NODATA").c_str(),
731
0
                                            nodata.c_str());
732
0
            }
733
734
0
            std::string descr =
735
0
                CPLGetXMLValue(field, "Quantity.description", "");
736
0
            if (descr != "")
737
0
            {
738
0
                *metadata = CSLSetNameValue(*metadata, (key + "DESCR").c_str(),
739
0
                                            descr.c_str());
740
0
            }
741
742
0
            path = "Quantity.constraint.AllowedValues.interval";
743
0
            std::string interval = CPLGetXMLValue(field, path, "");
744
0
            if (interval != "")
745
0
            {
746
0
                *metadata = CSLSetNameValue(
747
0
                    *metadata, (key + "INTERVAL").c_str(), interval.c_str());
748
0
            }
749
750
0
            nodata_array.push_back(std::move(nodata));
751
0
            fields += 1;
752
0
        }
753
754
0
        field_index += 1;
755
0
    }
756
757
0
    if (fields == 0)
758
0
    {
759
0
        CPLError(CE_Failure, CPLE_AppDefined,
760
0
                 "No data fields found (bad Range?).");
761
0
    }
762
0
    else
763
0
    {
764
        // todo: default to the first one?
765
0
        bServiceDirty = CPLUpdateXML(psService, "NoDataValue",
766
0
                                     Join(nodata_array, ",").c_str()) ||
767
0
                        bServiceDirty;
768
0
    }
769
770
0
    return fields;
771
0
}
772
773
/************************************************************************/
774
/*                          ExtractGridInfo()                           */
775
/*                                                                      */
776
/*      Collect info about grid from describe coverage for WCS 2.0.     */
777
/*                                                                      */
778
/************************************************************************/
779
780
bool WCSDataset201::ExtractGridInfo()
781
0
{
782
    // this is for checking what's in service and for filling in empty slots in
783
    // it if the service file can be considered ready for use, this could be
784
    // skipped
785
786
0
    CPLXMLNode *coverage = CPLGetXMLNode(psService, "CoverageDescription");
787
788
0
    if (coverage == nullptr)
789
0
    {
790
0
        CPLError(CE_Failure, CPLE_AppDefined,
791
0
                 "CoverageDescription missing from service.");
792
0
        return false;
793
0
    }
794
795
0
    std::string subtype = CoverageSubtype(coverage);
796
797
    // get CRS from boundedBy.Envelope and set the native flag to true
798
    // below we may set the CRS again but that won't be native (however, non
799
    // native CRS is not yet supported) also axis order swap is set
800
0
    std::string path = "boundedBy.Envelope";
801
0
    CPLXMLNode *envelope = CPLGetXMLNode(coverage, path.c_str());
802
0
    if (envelope == nullptr)
803
0
    {
804
0
        path = "boundedBy.EnvelopeWithTimePeriod";
805
0
        envelope = CPLGetXMLNode(coverage, path.c_str());
806
0
        if (envelope == nullptr)
807
0
        {
808
0
            CPLError(CE_Failure, CPLE_AppDefined, "Missing boundedBy.Envelope");
809
0
            return false;
810
0
        }
811
0
    }
812
0
    std::vector<std::string> bbox = ParseBoundingBox(envelope);
813
0
    if (!SetCRS(ParseCRS(envelope), true) || bbox.size() < 2)
814
0
    {
815
0
        return false;
816
0
    }
817
818
    // has the user set the domain?
819
0
    std::vector<std::string> domain =
820
0
        Split(CPLGetXMLValue(psService, "Domain", ""), ",");
821
822
    // names of axes
823
0
    std::vector<std::string> axes =
824
0
        Split(CPLGetXMLValue(coverage, (path + ".axisLabels").c_str(), ""), " ",
825
0
              axis_order_swap);
826
0
    std::vector<std::string> uoms =
827
0
        Split(CPLGetXMLValue(coverage, (path + ".uomLabels").c_str(), ""), " ",
828
0
              axis_order_swap);
829
830
0
    if (axes.size() < 2)
831
0
    {
832
0
        CPLError(CE_Failure, CPLE_AppDefined,
833
0
                 "The coverage has less than 2 dimensions or no axisLabels.");
834
0
        return false;
835
0
    }
836
837
0
    std::vector<int> domain_indexes = IndexOf(domain, axes);
838
0
    if (Contains(domain_indexes, -1))
839
0
    {
840
0
        CPLError(CE_Failure, CPLE_AppDefined,
841
0
                 "Axis in given domain does not exist in coverage.");
842
0
        return false;
843
0
    }
844
0
    if (domain_indexes.size() == 0)
845
0
    {  // default is the first two
846
0
        domain_indexes.push_back(0);
847
0
        domain_indexes.push_back(1);
848
0
    }
849
0
    if (domain.size() == 0)
850
0
    {
851
0
        domain.push_back(axes[0]);
852
0
        domain.push_back(axes[1]);
853
0
        CPLSetXMLValue(psService, "Domain", Join(domain, ",").c_str());
854
0
        bServiceDirty = true;
855
0
    }
856
857
    // GridFunction (is optional)
858
    // We support only linear grid functions.
859
    // axisOrder determines how data is arranged in the grid <order><axis
860
    // number> specifically: +2 +1 => swap grid envelope and the order of the
861
    // offsets
862
0
    std::vector<int> axisOrder;
863
0
    if (!ParseGridFunction(coverage, axisOrder))
864
0
    {
865
0
        return false;
866
0
    }
867
868
0
    const char *md_domain = "";
869
0
    char **metadata = CSLDuplicate(
870
0
        GetMetadata(md_domain));  // coverage metadata to be added/updated
871
872
0
    metadata = CSLSetNameValue(metadata, "DOMAIN", Join(domain, ",").c_str());
873
874
    // add coverage metadata: GeoServer TimeDomain
875
876
0
    CPLXMLNode *timedomain =
877
0
        CPLGetXMLNode(coverage, "metadata.Extension.TimeDomain");
878
0
    if (timedomain)
879
0
    {
880
0
        std::vector<std::string> timePositions;
881
        // "//timePosition"
882
0
        for (CPLXMLNode *node = timedomain->psChild; node != nullptr;
883
0
             node = node->psNext)
884
0
        {
885
0
            if (node->eType != CXT_Element ||
886
0
                strcmp(node->pszValue, "TimeInstant") != 0)
887
0
            {
888
0
                continue;
889
0
            }
890
0
            for (CPLXMLNode *node2 = node->psChild; node2 != nullptr;
891
0
                 node2 = node2->psNext)
892
0
            {
893
0
                if (node2->eType != CXT_Element ||
894
0
                    strcmp(node2->pszValue, "timePosition") != 0)
895
0
                {
896
0
                    continue;
897
0
                }
898
0
                timePositions.push_back(CPLGetXMLValue(node2, "", ""));
899
0
            }
900
0
        }
901
0
        metadata = CSLSetNameValue(metadata, "TimeDomain",
902
0
                                   Join(timePositions, ",").c_str());
903
0
    }
904
905
    // dimension metadata
906
907
0
    std::vector<std::string> slow =
908
0
        Split(bbox[0].c_str(), " ", axis_order_swap);
909
0
    std::vector<std::string> shigh =
910
0
        Split(bbox[1].c_str(), " ", axis_order_swap);
911
0
    bServiceDirty = CPLUpdateXML(psService, "Low", Join(slow, ",").c_str()) ||
912
0
                    bServiceDirty;
913
0
    bServiceDirty = CPLUpdateXML(psService, "High", Join(shigh, ",").c_str()) ||
914
0
                    bServiceDirty;
915
0
    if (slow.size() < 2 || shigh.size() < 2)
916
0
    {
917
0
        CPLError(CE_Failure, CPLE_AppDefined,
918
0
                 "The coverage has less than 2 dimensions.");
919
0
        CSLDestroy(metadata);
920
0
        return false;
921
0
    }
922
    // todo: if our x,y domain is not the first two? use domain_indexes?
923
0
    std::vector<double> low = Flist(slow, 0, 2);
924
0
    std::vector<double> high = Flist(shigh, 0, 2);
925
0
    std::vector<double> env;
926
0
    env.insert(env.end(), low.begin(), low.begin() + 2);
927
0
    env.insert(env.end(), high.begin(), high.begin() + 2);
928
929
0
    for (unsigned int i = 0; i < axes.size(); ++i)
930
0
    {
931
0
        const std::string key = CPLString().Printf("DIMENSION_%i_", i);
932
0
        metadata =
933
0
            CSLSetNameValue(metadata, (key + "AXIS").c_str(), axes[i].c_str());
934
0
        if (i < uoms.size())
935
0
        {
936
0
            metadata = CSLSetNameValue(metadata, (key + "UOM").c_str(),
937
0
                                       uoms[i].c_str());
938
0
        }
939
0
        if (i < 2)
940
0
        {
941
0
            metadata = CSLSetNameValue(
942
0
                metadata, (key + "INTERVAL").c_str(),
943
0
                CPLString().Printf("%.15g,%.15g", low[i], high[i]));
944
0
        }
945
0
        else if (i < slow.size() && i < shigh.size())
946
0
        {
947
0
            metadata = CSLSetNameValue(
948
0
                metadata, (key + "INTERVAL").c_str(),
949
0
                CPLString().Printf("%s,%s", slow[i].c_str(), shigh[i].c_str()));
950
0
        }
951
0
        else if (i < bbox.size())
952
0
        {
953
0
            metadata = CSLSetNameValue(metadata, (key + "INTERVAL").c_str(),
954
0
                                       bbox[i].c_str());
955
0
        }
956
0
    }
957
958
    // domainSet
959
    // requirement 23: the srsName here _shall_ be the same as in boundedBy
960
    // => we ignore it
961
    // the CRS of this dataset is from boundedBy (unless it is overridden)
962
    // this is the size of this dataset
963
    // this gives the geotransform of this dataset (unless there is CRS
964
    // override)
965
966
0
    CPLXMLNode *grid = GetGridNode(coverage, subtype);
967
0
    if (!grid)
968
0
    {
969
0
        CSLDestroy(metadata);
970
0
        return false;
971
0
    }
972
973
    //
974
0
    bool swap_grid_axis = false;
975
0
    if (axisOrder.size() >= 2 && axisOrder[domain_indexes[0]] == 2 &&
976
0
        axisOrder[domain_indexes[1]] == 1)
977
0
    {
978
0
        swap_grid_axis = !CPLGetXMLBoolean(psService, "NoGridAxisSwap");
979
0
    }
980
0
    path = "limits.GridEnvelope";
981
0
    std::vector<std::vector<int>> size =
982
0
        ParseGridEnvelope(CPLGetXMLNode(grid, path.c_str()), swap_grid_axis);
983
0
    std::vector<int> grid_size;
984
0
    if (size.size() < 2)
985
0
    {
986
0
        CPLError(CE_Failure, CPLE_AppDefined, "Can't parse the grid envelope.");
987
0
        CSLDestroy(metadata);
988
0
        return false;
989
0
    }
990
991
0
    grid_size.push_back(size[1][domain_indexes[0]] -
992
0
                        size[0][domain_indexes[0]] + 1);
993
0
    grid_size.push_back(size[1][domain_indexes[1]] -
994
0
                        size[0][domain_indexes[1]] + 1);
995
996
0
    path = "axisLabels";
997
0
    bool swap_grid_axis_labels =
998
0
        swap_grid_axis || CPLGetXMLBoolean(psService, "GridAxisLabelSwap");
999
0
    std::vector<std::string> grid_axes = Split(
1000
0
        CPLGetXMLValue(grid, path.c_str(), ""), " ", swap_grid_axis_labels);
1001
    // autocorrect MapServer thing
1002
0
    if (grid_axes.size() >= 2 && grid_axes[0] == "lat" &&
1003
0
        grid_axes[1] == "long")
1004
0
    {
1005
0
        grid_axes[0] = "long";
1006
0
        grid_axes[1] = "lat";
1007
0
    }
1008
0
    bServiceDirty =
1009
0
        CPLUpdateXML(psService, "GridAxes", Join(grid_axes, ",").c_str()) ||
1010
0
        bServiceDirty;
1011
1012
0
    std::vector<double> origin;
1013
0
    std::vector<std::vector<double>> offsets;
1014
0
    if (!GridOffsets(grid, subtype, swap_grid_axis, origin, offsets, axes,
1015
0
                     &metadata))
1016
0
    {
1017
0
        CSLDestroy(metadata);
1018
0
        return false;
1019
0
    }
1020
1021
0
    SetGeometry(grid_size, origin, offsets);
1022
1023
    // subsetting and dimension to bands
1024
0
    std::vector<std::string> dimensions;
1025
0
    std::string range;
1026
0
    std::vector<std::vector<std::string>> others;
1027
0
    ParseParameters(psService, dimensions, range, others);
1028
1029
    // it is ok to have trimming or even slicing for x/y, it just affects our
1030
    // bounding box but that is a todo item todo: BoundGeometry(domain_trim) if
1031
    // domain_trim.size() > 0
1032
0
    std::vector<std::vector<double>> domain_trim;
1033
1034
    // are all dimensions that are not x/y domain sliced?
1035
    // if not, bands can't be defined, see below
1036
0
    bool dimensions_are_ok = true;
1037
0
    for (unsigned int i = 0; i < axes.size(); ++i)
1038
0
    {
1039
0
        std::vector<std::string> params;
1040
0
        for (unsigned int j = 0; j < dimensions.size(); ++j)
1041
0
        {
1042
0
            if (dimensions[j].find(axes[i] + "(") != std::string::npos)
1043
0
            {
1044
0
                params = Split(FromParenthesis(dimensions[j]).c_str(), ",");
1045
0
                break;
1046
0
            }
1047
0
        }
1048
0
        int domain_index = IndexOf(axes[i], domain);
1049
0
        if (domain_index != -1)
1050
0
        {
1051
0
            domain_trim.push_back(Flist(params, 0, 2));
1052
0
            continue;
1053
0
        }
1054
        // size == 1 => sliced
1055
0
        if (params.size() != 1)
1056
0
        {
1057
0
            dimensions_are_ok = false;
1058
0
        }
1059
0
    }
1060
    // todo: add metadata: note: no bands, you need to subset to get data
1061
1062
    // check for CRS override
1063
0
    std::string crs = CPLGetXMLValue(psService, "SRS", "");
1064
0
    if (crs != "" && crs != osCRS)
1065
0
    {
1066
0
        if (!SetCRS(crs, false))
1067
0
        {
1068
0
            CSLDestroy(metadata);
1069
0
            return false;
1070
0
        }
1071
        // todo: support CRS override, it requires warping the grid to the new
1072
        // CRS SetGeometry(grid_size, origin, offsets);
1073
0
        CPLError(CE_Failure, CPLE_AppDefined,
1074
0
                 "CRS override not yet supported.");
1075
0
        CSLDestroy(metadata);
1076
0
        return false;
1077
0
    }
1078
1079
    // todo: ElevationDomain, DimensionDomain
1080
1081
    // rangeType
1082
1083
    // get the field metadata
1084
    // get the count of fields
1085
    // if Range is set in service that may limit the fields
1086
0
    int fields = ParseRange(coverage, range, &metadata);
1087
    // if fields is 0 an error message has been emitted
1088
    // but we let this go on since the user may be experimenting
1089
    // and she wants to see the resulting metadata and not just an error message
1090
    // situation is ~the same when bands == 0 when we exit here
1091
1092
    // todo: do this only if metadata is dirty
1093
0
    this->SetMetadata(metadata, md_domain);
1094
0
    CSLDestroy(metadata);
1095
0
    TrySaveXML();
1096
1097
    // determine the band count
1098
0
    int bands = 0;
1099
0
    if (dimensions_are_ok)
1100
0
    {
1101
0
        bands = fields;
1102
0
    }
1103
0
    bServiceDirty =
1104
0
        CPLUpdateXML(psService, "BandCount", CPLString().Printf("%d", bands)) ||
1105
0
        bServiceDirty;
1106
1107
    // set the Format value in service, unless it is set
1108
    // by the user, either through direct edit or options
1109
0
    if (!SetFormat(coverage))
1110
0
    {
1111
        // all attempts to find a format have failed...
1112
0
        CPLError(CE_Failure, CPLE_AppDefined,
1113
0
                 "All attempts to find a format have failed, giving up.");
1114
0
        return false;
1115
0
    }
1116
1117
0
    return true;
1118
0
}