Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/wcs/wcsdataset110.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  WCS Client Driver
4
 * Purpose:  Implementation of Dataset class for WCS 1.1.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.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 "gmlutils.h"
20
#include "gdal_frmts.h"
21
#include "gdal_pam.h"
22
#include "ogr_spatialref.h"
23
#include "gmlcoverage.h"
24
25
#include <algorithm>
26
27
#include "wcsdataset.h"
28
#include "wcsutils.h"
29
30
using namespace WCSUtils;
31
32
/************************************************************************/
33
/*                         GetNativeExtent()                            */
34
/*                                                                      */
35
/************************************************************************/
36
37
std::vector<double> WCSDataset110::GetNativeExtent(int nXOff, int nYOff,
38
                                                   int nXSize, int nYSize,
39
                                                   CPL_UNUSED int nBufXSize,
40
                                                   CPL_UNUSED int nBufYSize)
41
0
{
42
0
    std::vector<double> extent;
43
44
    // outer edges of outer pixels.
45
0
    extent.push_back(m_gt.xorig + (nXOff)*m_gt.xscale);
46
0
    extent.push_back(m_gt.yorig + (nYOff + nYSize) * m_gt.yscale);
47
0
    extent.push_back(m_gt.xorig + (nXOff + nXSize) * m_gt.xscale);
48
0
    extent.push_back(m_gt.yorig + (nYOff)*m_gt.yscale);
49
50
0
    bool no_shrink = CPLGetXMLBoolean(psService, "OuterExtents");
51
52
    // WCS 1.1 extents are centers of outer pixels.
53
0
    if (!no_shrink)
54
0
    {
55
0
        extent[2] -= m_gt.xscale * 0.5;
56
0
        extent[0] += m_gt.xscale * 0.5;
57
0
        extent[1] -= m_gt.yscale * 0.5;
58
0
        extent[3] += m_gt.yscale * 0.5;
59
0
    }
60
61
0
    double dfXStep, dfYStep;
62
63
0
    if (!no_shrink)
64
0
    {
65
0
        dfXStep = (nXSize / (double)nBufXSize) * m_gt.xscale;
66
0
        dfYStep = (nYSize / (double)nBufYSize) * m_gt.yscale;
67
        // Carefully adjust bounds for pixel centered values at new
68
        // sampling density.
69
0
        if (nBufXSize != nXSize || nBufYSize != nYSize)
70
0
        {
71
0
            extent[0] = nXOff * m_gt.xscale + m_gt.xorig + dfXStep * 0.5;
72
0
            extent[2] = extent[0] + (nBufXSize - 1) * dfXStep;
73
74
0
            extent[3] = nYOff * m_gt.yscale + m_gt.yorig + dfYStep * 0.5;
75
0
            extent[1] = extent[3] + (nBufYSize - 1) * dfYStep;
76
0
        }
77
0
    }
78
0
    else
79
0
    {
80
0
        double adjust =
81
0
            CPLAtof(CPLGetXMLValue(psService, "BufSizeAdjust", "0.0"));
82
0
        dfXStep = (nXSize / ((double)nBufXSize + adjust)) * m_gt.xscale;
83
0
        dfYStep = (nYSize / ((double)nBufYSize + adjust)) * m_gt.yscale;
84
0
    }
85
86
0
    extent.push_back(dfXStep);
87
0
    extent.push_back(dfYStep);
88
89
0
    return extent;
90
0
}
91
92
/************************************************************************/
93
/*                        GetCoverageRequest()                          */
94
/*                                                                      */
95
/************************************************************************/
96
97
std::string WCSDataset110::GetCoverageRequest(bool scaled, int /* nBufXSize */,
98
                                              int /* nBufYSize */,
99
                                              const std::vector<double> &extent,
100
                                              const std::string &osBandList)
101
0
{
102
0
    CPLString osRequest;
103
104
    /* -------------------------------------------------------------------- */
105
    /*      URL encode strings that could have questionable characters.     */
106
    /* -------------------------------------------------------------------- */
107
0
    CPLString osCoverage = CPLGetXMLValue(psService, "CoverageName", "");
108
109
0
    char *pszEncoded = CPLEscapeString(osCoverage, -1, CPLES_URL);
110
0
    osCoverage = pszEncoded;
111
0
    CPLFree(pszEncoded);
112
113
0
    CPLString osFormat = CPLGetXMLValue(psService, "PreferredFormat", "");
114
115
0
    pszEncoded = CPLEscapeString(osFormat, -1, CPLES_URL);
116
0
    osFormat = pszEncoded;
117
0
    CPLFree(pszEncoded);
118
119
0
    CPLString osRangeSubset = CPLGetXMLValue(psService, "FieldName", "");
120
121
    // todo: MapServer seems to require interpolation
122
123
0
    CPLString interpolation = CPLGetXMLValue(psService, "Interpolation", "");
124
0
    if (interpolation == "")
125
0
    {
126
        // old undocumented key for interpolation in service
127
0
        interpolation = CPLGetXMLValue(psService, "Resample", "");
128
0
    }
129
0
    if (interpolation != "")
130
0
    {
131
0
        osRangeSubset += ":" + interpolation;
132
0
    }
133
134
0
    if (osBandList != "")
135
0
    {
136
0
        if (osBandIdentifier != "")
137
0
        {
138
0
            osRangeSubset += CPLString().Printf(
139
0
                "[%s[%s]]", osBandIdentifier.c_str(), osBandList.c_str());
140
0
        }
141
0
    }
142
143
0
    osRangeSubset = "&RangeSubset=" + URLEncode(osRangeSubset);
144
145
0
    double bbox_0 = extent[0],  // min X
146
0
        bbox_1 = extent[1],     // min Y
147
0
        bbox_2 = extent[2],     // max X
148
0
        bbox_3 = extent[3];     // max Y
149
150
0
    if (axis_order_swap)
151
0
    {
152
0
        bbox_0 = extent[1];  // min Y
153
0
        bbox_1 = extent[0];  // min X
154
0
        bbox_2 = extent[3];  // max Y
155
0
        bbox_3 = extent[2];  // max X
156
0
    }
157
0
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
158
0
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
159
0
    request += CPLString().Printf(
160
0
        "&VERSION=%s&REQUEST=GetCoverage&IDENTIFIER=%s"
161
0
        "&FORMAT=%s&BOUNDINGBOX=%.15g,%.15g,%.15g,%.15g,%s%s",
162
0
        CPLGetXMLValue(psService, "Version", ""), osCoverage.c_str(),
163
0
        osFormat.c_str(), bbox_0, bbox_1, bbox_2, bbox_3, osCRS.c_str(),
164
0
        osRangeSubset.c_str());
165
0
    double origin_1 = extent[0],  // min X
166
0
        origin_2 = extent[3],     // max Y
167
0
        offset_1 = extent[4],     // dX
168
0
        offset_2 = extent[5];     // dY
169
170
0
    if (axis_order_swap)
171
0
    {
172
0
        origin_1 = extent[3];  // max Y
173
0
        origin_2 = extent[0];  // min X
174
0
        offset_1 = extent[5];  // dY
175
0
        offset_2 = extent[4];  // dX
176
0
    }
177
0
    CPLString offsets;
178
0
    if (CPLGetXMLBoolean(psService, "OffsetsPositive"))
179
0
    {
180
0
        offset_1 = fabs(offset_1);
181
0
        offset_2 = fabs(offset_2);
182
0
    }
183
0
    if (EQUAL(CPLGetXMLValue(psService, "NrOffsets", "4"), "2"))
184
0
    {
185
0
        offsets = CPLString().Printf("%.15g,%.15g", offset_1, offset_2);
186
0
    }
187
0
    else
188
0
    {
189
0
        if (axis_order_swap)
190
0
        {
191
            // Only tested with GeoServer but this is the correct offset(?)
192
0
            offsets = CPLString().Printf("0,%.15g,%.15g,0", offset_2, offset_1);
193
0
        }
194
0
        else
195
0
        {
196
0
            offsets = CPLString().Printf("%.15g,0,0,%.15g", offset_1, offset_2);
197
0
        }
198
0
    }
199
0
    bool do_not_include =
200
0
        CPLGetXMLBoolean(psService, "GridCRSOptional") && !scaled;
201
0
    if (!do_not_include)
202
0
    {
203
0
        request += CPLString().Printf(
204
0
            "&GridBaseCRS=%s"
205
0
            "&GridCS=urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS"
206
0
            "&GridType=urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs"
207
0
            "&GridOrigin=%.15g,%.15g"
208
0
            "&GridOffsets=%s",
209
0
            osCRS.c_str(), origin_1, origin_2, offsets.c_str());
210
0
    }
211
0
    CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
212
0
    if (extra != "")
213
0
    {
214
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
215
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
216
0
        {
217
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
218
0
            request =
219
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
220
0
        }
221
0
    }
222
0
    extra = CPLGetXMLValue(psService, "GetCoverageExtra", "");
223
0
    if (extra != "")
224
0
    {
225
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
226
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
227
0
        {
228
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
229
0
            request =
230
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
231
0
        }
232
0
    }
233
0
    CPLDebug("WCS", "Requesting %s", request.c_str());
234
0
    return request;
235
0
}
236
237
/************************************************************************/
238
/*                        DescribeCoverageRequest()                     */
239
/*                                                                      */
240
/************************************************************************/
241
242
std::string WCSDataset110::DescribeCoverageRequest()
243
0
{
244
0
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
245
0
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
246
0
    request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
247
0
    request = CPLURLAddKVP(request.c_str(), "VERSION",
248
0
                           CPLGetXMLValue(psService, "Version", "1.1.0"));
249
0
    request = CPLURLAddKVP(request.c_str(), "IDENTIFIERS",
250
0
                           CPLGetXMLValue(psService, "CoverageName", ""));
251
0
    CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
252
0
    if (extra != "")
253
0
    {
254
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
255
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
256
0
        {
257
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
258
0
            request =
259
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
260
0
        }
261
0
    }
262
0
    extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
263
0
    if (extra != "")
264
0
    {
265
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
266
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
267
0
        {
268
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
269
0
            request =
270
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
271
0
        }
272
0
    }
273
0
    return request;
274
0
}
275
276
/************************************************************************/
277
/*                         CoverageOffering()                           */
278
/*                                                                      */
279
/************************************************************************/
280
281
CPLXMLNode *WCSDataset110::CoverageOffering(CPLXMLNode *psDC)
282
0
{
283
0
    return CPLGetXMLNode(psDC, "=CoverageDescriptions.CoverageDescription");
284
0
}
285
286
/************************************************************************/
287
/*                          ExtractGridInfo()                           */
288
/*                                                                      */
289
/*      Collect info about grid from describe coverage for WCS 1.1.     */
290
/*                                                                      */
291
/************************************************************************/
292
293
bool WCSDataset110::ExtractGridInfo()
294
295
0
{
296
0
    CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageDescription");
297
298
0
    if (psCO == nullptr)
299
0
        return false;
300
301
    /* -------------------------------------------------------------------- */
302
    /*      We need to strip off name spaces so it is easier to             */
303
    /*      searchfor plain gml names.                                      */
304
    /* -------------------------------------------------------------------- */
305
0
    CPLStripXMLNamespace(psCO, nullptr, TRUE);
306
307
    /* -------------------------------------------------------------------- */
308
    /*      Verify we have a SpatialDomain and GridCRS.                     */
309
    /* -------------------------------------------------------------------- */
310
0
    CPLXMLNode *psSD = CPLGetXMLNode(psCO, "Domain.SpatialDomain");
311
0
    CPLXMLNode *psGCRS = CPLGetXMLNode(psSD, "GridCRS");
312
313
0
    if (psSD == nullptr || psGCRS == nullptr)
314
0
    {
315
0
        CPLError(CE_Failure, CPLE_AppDefined,
316
0
                 "Unable to find GridCRS in CoverageDescription,\n"
317
0
                 "unable to process WCS Coverage.");
318
0
        return false;
319
0
    }
320
321
    /* -------------------------------------------------------------------- */
322
    /*      Establish our coordinate system.                                */
323
    /*   This is needed before geometry since we may have axis order swap.  */
324
    /* -------------------------------------------------------------------- */
325
0
    CPLString crs = ParseCRS(psGCRS);
326
327
0
    if (crs.empty())
328
0
    {
329
0
        CPLError(CE_Failure, CPLE_AppDefined,
330
0
                 "Unable to find GridCRS.GridBaseCRS");
331
0
        return false;
332
0
    }
333
334
    // SetCRS should fail only if the CRS is really unknown to GDAL
335
0
    if (!SetCRS(crs, true))
336
0
    {
337
0
        CPLError(CE_Failure, CPLE_AppDefined,
338
0
                 "Unable to interpret GridBaseCRS '%s'.", crs.c_str());
339
0
        return false;
340
0
    }
341
342
    /* -------------------------------------------------------------------- */
343
    /*      Collect size, origin, and offsets for SetGeometry()             */
344
    /*                                                                      */
345
    /*      Extract Geotransform from GridCRS.                              */
346
    /*                                                                      */
347
    /* -------------------------------------------------------------------- */
348
0
    const char *pszGridType = CPLGetXMLValue(
349
0
        psGCRS, "GridType", "urn:ogc:def:method:WCS::2dSimpleGrid");
350
0
    bool swap =
351
0
        axis_order_swap && !CPLGetXMLBoolean(psService, "NoGridAxisSwap");
352
0
    std::vector<double> origin =
353
0
        Flist(Split(CPLGetXMLValue(psGCRS, "GridOrigin", ""), " ", swap));
354
355
0
    std::vector<std::string> offset_1 =
356
0
        Split(CPLGetXMLValue(psGCRS, "GridOffsets", ""), " ");
357
0
    std::vector<std::string> offset_2;
358
0
    size_t n = offset_1.size();
359
0
    if (n % 2 != 0)
360
0
    {
361
0
        CPLError(CE_Failure, CPLE_AppDefined,
362
0
                 "GridOffsets has incorrect amount of coefficients.\n"
363
0
                 "Unable to process WCS coverage.");
364
0
        return false;
365
0
    }
366
0
    for (unsigned int i = 0; i < n / 2; ++i)
367
0
    {
368
0
        CPLString s = offset_1.back();
369
0
        offset_1.erase(offset_1.end() - 1);
370
0
#if defined(__GNUC__)
371
0
#pragma GCC diagnostic push
372
0
#pragma GCC diagnostic ignored "-Wnull-dereference"
373
0
#endif
374
0
        offset_2.insert(offset_2.begin(), s);
375
0
#if defined(__GNUC__)
376
0
#pragma GCC diagnostic pop
377
0
#endif
378
0
    }
379
0
    std::vector<std::vector<double>> offsets;
380
0
    if (swap)
381
0
    {
382
0
        offsets.push_back(Flist(offset_2));
383
0
        offsets.push_back(Flist(offset_1));
384
0
    }
385
0
    else
386
0
    {
387
0
        offsets.push_back(Flist(offset_1));
388
0
        offsets.push_back(Flist(offset_2));
389
0
    }
390
391
0
    if (strstr(pszGridType, ":2dGridIn2dCrs") ||
392
0
        strstr(pszGridType, ":2dGridin2dCrs"))
393
0
    {
394
0
        if (!(offset_1.size() == 2 && origin.size() == 2))
395
0
        {
396
0
            CPLError(CE_Failure, CPLE_AppDefined,
397
0
                     "2dGridIn2dCrs does not have expected GridOrigin or\n"
398
0
                     "GridOffsets values - unable to process WCS coverage.");
399
0
            return false;
400
0
        }
401
0
    }
402
403
0
    else if (strstr(pszGridType, ":2dGridIn3dCrs"))
404
0
    {
405
0
        if (!(offset_1.size() == 3 && origin.size() == 3))
406
0
        {
407
0
            CPLError(CE_Failure, CPLE_AppDefined,
408
0
                     "2dGridIn3dCrs does not have expected GridOrigin or\n"
409
0
                     "GridOffsets values - unable to process WCS coverage.");
410
0
            return false;
411
0
        }
412
0
    }
413
414
0
    else if (strstr(pszGridType, ":2dSimpleGrid"))
415
0
    {
416
0
        if (!(offset_1.size() == 1 && origin.size() == 2))
417
0
        {
418
0
            CPLError(CE_Failure, CPLE_AppDefined,
419
0
                     "2dSimpleGrid does not have expected GridOrigin or\n"
420
0
                     "GridOffsets values - unable to process WCS coverage.");
421
0
            return false;
422
0
        }
423
0
    }
424
425
0
    else
426
0
    {
427
0
        CPLError(CE_Failure, CPLE_AppDefined,
428
0
                 "Unrecognized GridCRS.GridType value '%s',\n"
429
0
                 "unable to process WCS coverage.",
430
0
                 pszGridType);
431
0
        return false;
432
0
    }
433
434
    /* -------------------------------------------------------------------- */
435
    /*      Search for an ImageCRS for raster size.                         */
436
    /* -------------------------------------------------------------------- */
437
0
    std::vector<int> size;
438
0
    CPLXMLNode *psNode;
439
440
0
    for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0;
441
0
         psNode = psNode->psNext)
442
0
    {
443
0
        if (psNode->eType != CXT_Element ||
444
0
            !EQUAL(psNode->pszValue, "BoundingBox"))
445
0
            continue;
446
447
0
        CPLString osBBCRS = ParseCRS(psNode);
448
0
        if (strstr(osBBCRS, ":imageCRS"))
449
0
        {
450
0
            std::vector<std::string> bbox = ParseBoundingBox(psNode);
451
0
            if (bbox.size() >= 2)
452
0
            {
453
0
                std::vector<int> low = Ilist(Split(bbox[0].c_str(), " "), 0, 2);
454
0
                std::vector<int> high =
455
0
                    Ilist(Split(bbox[1].c_str(), " "), 0, 2);
456
0
                if (low[0] == 0 && low[1] == 0)
457
0
                {
458
0
                    size.push_back(high[0]);
459
0
                    size.push_back(high[1]);
460
0
                }
461
0
            }
462
0
        }
463
0
    }
464
465
    /* -------------------------------------------------------------------- */
466
    /*      Otherwise we search for a bounding box in our coordinate        */
467
    /*      system and derive the size from that.                           */
468
    /* -------------------------------------------------------------------- */
469
0
    for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0;
470
0
         psNode = psNode->psNext)
471
0
    {
472
0
        if (psNode->eType != CXT_Element ||
473
0
            !EQUAL(psNode->pszValue, "BoundingBox"))
474
0
            continue;
475
476
0
        CPLString osBBCRS = ParseCRS(psNode);
477
0
        if (osBBCRS == osCRS)
478
0
        {
479
0
            std::vector<std::string> bbox = ParseBoundingBox(psNode);
480
0
            bool not_rot =
481
0
                (offsets[0].size() == 1 && offsets[1].size() == 1) ||
482
0
                ((swap && offsets[0][0] == 0.0 && offsets[1][1] == 0.0) ||
483
0
                 (!swap && offsets[0][1] == 0.0 && offsets[1][0] == 0.0));
484
0
            if (bbox.size() >= 2 && not_rot)
485
0
            {
486
0
                std::vector<double> low =
487
0
                    Flist(Split(bbox[0].c_str(), " ", axis_order_swap), 0, 2);
488
0
                std::vector<double> high =
489
0
                    Flist(Split(bbox[1].c_str(), " ", axis_order_swap), 0, 2);
490
0
                double c1 = offsets[0][0];
491
0
                double c2 =
492
0
                    offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
493
0
                size.push_back((int)((high[0] - low[0]) / c1 + 1.01));
494
0
                size.push_back((int)((high[1] - low[1]) / fabs(c2) + 1.01));
495
0
            }
496
0
        }
497
0
    }
498
499
0
    if (size.size() < 2)
500
0
    {
501
0
        CPLError(CE_Failure, CPLE_AppDefined,
502
0
                 "Could not determine the size of the grid.");
503
0
        return false;
504
0
    }
505
506
0
    SetGeometry(size, origin, offsets);
507
508
    /* -------------------------------------------------------------------- */
509
    /*      Do we have a coordinate system override?                        */
510
    /* -------------------------------------------------------------------- */
511
0
    const char *pszProjOverride = CPLGetXMLValue(psService, "SRS", nullptr);
512
513
0
    if (pszProjOverride)
514
0
    {
515
0
        if (m_oSRS.SetFromUserInput(
516
0
                pszProjOverride,
517
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
518
0
            OGRERR_NONE)
519
0
        {
520
0
            CPLError(CE_Failure, CPLE_AppDefined,
521
0
                     "<SRS> element contents not parsable:\n%s",
522
0
                     pszProjOverride);
523
0
            return false;
524
0
        }
525
0
    }
526
527
    /* -------------------------------------------------------------------- */
528
    /*      Pick a format type if we don't already have one selected.       */
529
    /*                                                                      */
530
    /*      We will prefer anything that sounds like TIFF, otherwise        */
531
    /*      falling back to the first supported format.  Should we          */
532
    /*      consider preferring the nativeFormat if available?              */
533
    /* -------------------------------------------------------------------- */
534
0
    if (CPLGetXMLValue(psService, "PreferredFormat", nullptr) == nullptr)
535
0
    {
536
0
        CPLString osPreferredFormat;
537
538
0
        for (psNode = psCO->psChild; psNode != nullptr; psNode = psNode->psNext)
539
0
        {
540
0
            if (psNode->eType == CXT_Element &&
541
0
                EQUAL(psNode->pszValue, "SupportedFormat") && psNode->psChild &&
542
0
                psNode->psChild->eType == CXT_Text)
543
0
            {
544
0
                if (osPreferredFormat.empty())
545
0
                    osPreferredFormat = psNode->psChild->pszValue;
546
547
0
                if (strstr(psNode->psChild->pszValue, "tiff") != nullptr ||
548
0
                    strstr(psNode->psChild->pszValue, "TIFF") != nullptr ||
549
0
                    strstr(psNode->psChild->pszValue, "Tiff") != nullptr)
550
0
                {
551
0
                    osPreferredFormat = psNode->psChild->pszValue;
552
0
                    break;
553
0
                }
554
0
            }
555
0
        }
556
557
0
        if (!osPreferredFormat.empty())
558
0
        {
559
0
            bServiceDirty = true;
560
0
            CPLCreateXMLElementAndValue(psService, "PreferredFormat",
561
0
                                        osPreferredFormat);
562
0
        }
563
0
    }
564
565
    /* -------------------------------------------------------------------- */
566
    /*      Try to identify a nodata value.  For now we only support the    */
567
    /*      singleValue mechanism.                                          */
568
    /* -------------------------------------------------------------------- */
569
0
    if (CPLGetXMLValue(psService, "NoDataValue", nullptr) == nullptr)
570
0
    {
571
0
        const char *pszSV =
572
0
            CPLGetXMLValue(psCO, "Range.Field.NullValue", nullptr);
573
574
0
        if (pszSV != nullptr && (CPLAtof(pszSV) != 0.0 || *pszSV == DIGIT_ZERO))
575
0
        {
576
0
            bServiceDirty = true;
577
0
            CPLCreateXMLElementAndValue(psService, "NoDataValue", pszSV);
578
0
        }
579
0
    }
580
581
    /* -------------------------------------------------------------------- */
582
    /*      Grab the field name, if possible.                               */
583
    /* -------------------------------------------------------------------- */
584
0
    if (CPLGetXMLValue(psService, "FieldName", nullptr) == nullptr)
585
0
    {
586
0
        CPLString osFieldName =
587
0
            CPLGetXMLValue(psCO, "Range.Field.Identifier", "");
588
589
0
        if (!osFieldName.empty())
590
0
        {
591
0
            bServiceDirty = true;
592
0
            CPLCreateXMLElementAndValue(psService, "FieldName", osFieldName);
593
0
        }
594
0
        else
595
0
        {
596
0
            CPLError(
597
0
                CE_Failure, CPLE_AppDefined,
598
0
                "Unable to find required Identifier name %s for Range Field.",
599
0
                osCRS.c_str());
600
0
            return false;
601
0
        }
602
0
    }
603
604
    /* -------------------------------------------------------------------- */
605
    /*      Do we have a "Band" axis?  If so try to grab the bandcount      */
606
    /*      and data type from it.                                          */
607
    /* -------------------------------------------------------------------- */
608
0
    osBandIdentifier = CPLGetXMLValue(psService, "BandIdentifier", "");
609
0
    CPLXMLNode *psAxis =
610
0
        CPLGetXMLNode(psService, "CoverageDescription.Range.Field.Axis");
611
612
0
    if (osBandIdentifier.empty() &&
613
0
        (EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Band") ||
614
0
         EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Bands")) &&
615
0
        CPLGetXMLNode(psAxis, "AvailableKeys") != nullptr)
616
0
    {
617
0
        osBandIdentifier = CPLGetXMLValue(psAxis, "Identifier", "");
618
619
        // verify keys are ascending starting at 1
620
0
        CPLXMLNode *psValues = CPLGetXMLNode(psAxis, "AvailableKeys");
621
0
        CPLXMLNode *psSV;
622
0
        int iBand;
623
624
0
        for (psSV = psValues->psChild, iBand = 1; psSV != nullptr;
625
0
             psSV = psSV->psNext, iBand++)
626
0
        {
627
0
            if (psSV->eType != CXT_Element || !EQUAL(psSV->pszValue, "Key") ||
628
0
                psSV->psChild == nullptr || psSV->psChild->eType != CXT_Text ||
629
0
                atoi(psSV->psChild->pszValue) != iBand)
630
0
            {
631
0
                osBandIdentifier = "";
632
0
                break;
633
0
            }
634
0
        }
635
636
0
        if (!osBandIdentifier.empty())
637
0
        {
638
0
            if (CPLGetXMLValue(psService, "BandIdentifier", nullptr) == nullptr)
639
0
            {
640
0
                bServiceDirty = true;
641
0
                CPLSetXMLValue(psService, "BandIdentifier",
642
0
                               osBandIdentifier.c_str());
643
0
            }
644
645
0
            if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
646
0
            {
647
0
                bServiceDirty = true;
648
0
                CPLSetXMLValue(psService, "BandCount",
649
0
                               CPLString().Printf("%d", iBand - 1));
650
0
            }
651
0
        }
652
653
        // Is this an ESRI server returning a GDAL recognised data type?
654
0
        CPLString osDataType = CPLGetXMLValue(psAxis, "DataType", "");
655
0
        if (GDALGetDataTypeByName(osDataType) != GDT_Unknown &&
656
0
            CPLGetXMLValue(psService, "BandType", nullptr) == nullptr)
657
0
        {
658
0
            bServiceDirty = true;
659
0
            CPLCreateXMLElementAndValue(psService, "BandType", osDataType);
660
0
        }
661
0
    }
662
663
0
    return true;
664
0
}
665
666
/************************************************************************/
667
/*                         ParseCapabilities()                          */
668
/************************************************************************/
669
670
CPLErr WCSDataset110::ParseCapabilities(CPLXMLNode *Capabilities,
671
                                        const std::string &url)
672
0
{
673
0
    CPLStripXMLNamespace(Capabilities, nullptr, TRUE);
674
675
    // make sure this is a capabilities document
676
0
    if (strcmp(Capabilities->pszValue, "Capabilities") != 0)
677
0
    {
678
0
        CPLError(CE_Failure, CPLE_AppDefined,
679
0
                 "Error in capabilities document.\n");
680
0
        return CE_Failure;
681
0
    }
682
683
0
    char **metadata = nullptr;
684
0
    std::string path = "WCS_GLOBAL#";
685
686
0
    CPLString key = path + "version";
687
0
    metadata = CSLSetNameValue(metadata, key, Version());
688
689
0
    for (CPLXMLNode *node = Capabilities->psChild; node != nullptr;
690
0
         node = node->psNext)
691
0
    {
692
0
        const char *attr = node->pszValue;
693
0
        if (node->eType == CXT_Attribute && EQUAL(attr, "updateSequence"))
694
0
        {
695
0
            key = path + "updateSequence";
696
0
            CPLString value = CPLGetXMLValue(node, nullptr, "");
697
0
            metadata = CSLSetNameValue(metadata, key, value);
698
0
        }
699
0
    }
700
701
    // identification metadata
702
0
    std::string path2 = path;
703
0
    CPLXMLNode *service = AddSimpleMetaData(
704
0
        &metadata, Capabilities, path2, "ServiceIdentification",
705
0
        {"Title", "Abstract", "Fees", "AccessConstraints"});
706
0
    CPLString kw = GetKeywords(service, "Keywords", "Keyword");
707
0
    if (kw != "")
708
0
    {
709
0
        CPLString name = path + "Keywords";
710
0
        metadata = CSLSetNameValue(metadata, name, kw);
711
0
    }
712
0
    CPLString profiles = GetKeywords(service, "", "Profile");
713
0
    if (profiles != "")
714
0
    {
715
0
        CPLString name = path + "Profiles";
716
0
        metadata = CSLSetNameValue(metadata, name, profiles);
717
0
    }
718
719
    // provider metadata
720
0
    path2 = path;
721
0
    CPLXMLNode *provider = AddSimpleMetaData(
722
0
        &metadata, Capabilities, path2, "ServiceProvider", {"ProviderName"});
723
0
    if (provider)
724
0
    {
725
0
        CPLXMLNode *site = CPLGetXMLNode(provider, "ProviderSite");
726
0
        if (site)
727
0
        {
728
0
            std::string path3 = path2 + "ProviderSite";
729
0
            CPLString value =
730
0
                CPLGetXMLValue(CPLGetXMLNode(site, "href"), nullptr, "");
731
0
            metadata = CSLSetNameValue(metadata, path3.c_str(), value);
732
0
        }
733
0
        std::string path3 = std::move(path2);
734
0
        CPLXMLNode *contact =
735
0
            AddSimpleMetaData(&metadata, provider, path3, "ServiceContact",
736
0
                              {"IndividualName", "PositionName", "Role"});
737
0
        if (contact)
738
0
        {
739
0
            std::string path4 = std::move(path3);
740
0
            CPLXMLNode *info =
741
0
                AddSimpleMetaData(&metadata, contact, path4, "ContactInfo",
742
0
                                  {"HoursOfService", "ContactInstructions"});
743
0
            if (info)
744
0
            {
745
0
                std::string path5 = path4;
746
0
                std::string path6 = path4;
747
0
                AddSimpleMetaData(&metadata, info, path5, "Address",
748
0
                                  {"DeliveryPoint", "City",
749
0
                                   "AdministrativeArea", "PostalCode",
750
0
                                   "Country", "ElectronicMailAddress"});
751
0
                AddSimpleMetaData(&metadata, info, path6, "Phone",
752
0
                                  {"Voice", "Facsimile"});
753
0
                CPL_IGNORE_RET_VAL(path4);
754
0
            }
755
0
        }
756
0
    }
757
758
    // operations metadata
759
0
    CPLString DescribeCoverageURL = "";
760
0
    CPLXMLNode *service2 = CPLGetXMLNode(Capabilities, "OperationsMetadata");
761
0
    if (service2)
762
0
    {
763
0
        for (CPLXMLNode *operation = service2->psChild; operation != nullptr;
764
0
             operation = operation->psNext)
765
0
        {
766
0
            if (operation->eType != CXT_Element ||
767
0
                !EQUAL(operation->pszValue, "Operation"))
768
0
            {
769
0
                continue;
770
0
            }
771
0
            if (EQUAL(CPLGetXMLValue(CPLGetXMLNode(operation, "name"), nullptr,
772
0
                                     ""),
773
0
                      "DescribeCoverage"))
774
0
            {
775
0
                DescribeCoverageURL = CPLGetXMLValue(
776
0
                    CPLGetXMLNode(CPLSearchXMLNode(operation, "Get"), "href"),
777
0
                    nullptr, "");
778
0
            }
779
0
        }
780
0
    }
781
    // if DescribeCoverageURL looks wrong, we change it
782
0
    if (DescribeCoverageURL.find("localhost") != std::string::npos)
783
0
    {
784
0
        DescribeCoverageURL = URLRemoveKey(url.c_str(), "request");
785
0
    }
786
787
    // service metadata (in 2.0)
788
0
    CPLString ext = "ServiceMetadata";
789
0
    CPLString formats = GetKeywords(Capabilities, ext, "formatSupported");
790
0
    if (formats != "")
791
0
    {
792
0
        CPLString name = path + "formatSupported";
793
0
        metadata = CSLSetNameValue(metadata, name, formats);
794
0
    }
795
    // wcs:Extensions: interpolation, CRS, others?
796
0
    ext += ".Extension";
797
0
    CPLString interpolation =
798
0
        GetKeywords(Capabilities, ext, "interpolationSupported");
799
0
    if (interpolation == "")
800
0
    {
801
0
        interpolation =
802
0
            GetKeywords(Capabilities, ext + ".InterpolationMetadata",
803
0
                        "InterpolationSupported");
804
0
    }
805
0
    if (interpolation != "")
806
0
    {
807
0
        CPLString name = path + "InterpolationSupported";
808
0
        metadata = CSLSetNameValue(metadata, name, interpolation);
809
0
    }
810
0
    CPLString crs = GetKeywords(Capabilities, ext, "crsSupported");
811
0
    if (crs == "")
812
0
    {
813
0
        crs = GetKeywords(Capabilities, ext + ".CrsMetadata", "crsSupported");
814
0
    }
815
0
    if (crs != "")
816
0
    {
817
0
        CPLString name = path + "crsSupported";
818
0
        metadata = CSLSetNameValue(metadata, name, crs);
819
0
    }
820
821
0
    this->SetMetadata(metadata, "");
822
0
    CSLDestroy(metadata);
823
0
    metadata = nullptr;
824
825
    // contents metadata
826
0
    CPLXMLNode *contents = CPLGetXMLNode(Capabilities, "Contents");
827
0
    if (contents)
828
0
    {
829
0
        int index = 1;
830
0
        for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
831
0
             summary = summary->psNext)
832
0
        {
833
0
            if (summary->eType != CXT_Element ||
834
0
                !EQUAL(summary->pszValue, "CoverageSummary"))
835
0
            {
836
0
                continue;
837
0
            }
838
0
            CPLString path3;
839
0
            path3.Printf("SUBDATASET_%d_", index);
840
0
            index += 1;
841
842
            // the name and description of the subdataset:
843
            // GDAL Data Model:
844
            // The value of the _NAME is a string that can be passed to
845
            // GDALOpen() to access the file.
846
847
0
            CPLString key2 = path3 + "NAME";
848
849
0
            CPLString name = DescribeCoverageURL;
850
0
            name = CPLURLAddKVP(name, "version", this->Version());
851
852
0
            CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId");
853
0
            std::string id;
854
0
            if (node)
855
0
            {
856
0
                id = CPLGetXMLValue(node, nullptr, "");
857
0
            }
858
0
            else
859
0
            {
860
0
                node = CPLGetXMLNode(summary, "Identifier");
861
0
                if (node)
862
0
                {
863
0
                    id = CPLGetXMLValue(node, nullptr, "");
864
0
                }
865
0
                else
866
0
                {
867
                    // todo: maybe not an error since CoverageSummary may be
868
                    // within CoverageSummary (07-067r5 Fig4)
869
0
                    CSLDestroy(metadata);
870
0
                    CPLError(CE_Failure, CPLE_AppDefined,
871
0
                             "Error in capabilities document.\n");
872
0
                    return CE_Failure;
873
0
                }
874
0
            }
875
0
            name = CPLURLAddKVP(name, "coverage", id.c_str());
876
0
            name = "WCS:" + name;
877
0
            metadata = CSLSetNameValue(metadata, key2, name);
878
879
0
            key2 = path3 + "DESC";
880
881
0
            node = CPLGetXMLNode(summary, "Title");
882
0
            if (node)
883
0
            {
884
0
                metadata = CSLSetNameValue(metadata, key2,
885
0
                                           CPLGetXMLValue(node, nullptr, ""));
886
0
            }
887
0
            else
888
0
            {
889
0
                metadata = CSLSetNameValue(metadata, key2, id.c_str());
890
0
            }
891
892
            // todo: compose global bounding box from WGS84BoundingBox and
893
            // BoundingBox
894
895
            // further subdataset (coverage) parameters are parsed in
896
            // ParseCoverageCapabilities
897
0
        }
898
0
    }
899
0
    this->SetMetadata(metadata, "SUBDATASETS");
900
0
    CSLDestroy(metadata);
901
0
    return CE_None;
902
0
}
903
904
void WCSDataset110::ParseCoverageCapabilities(CPLXMLNode *capabilities,
905
                                              const std::string &coverage,
906
                                              CPLXMLNode *metadata)
907
0
{
908
0
    CPLStripXMLNamespace(capabilities, nullptr, TRUE);
909
0
    CPLXMLNode *contents = CPLGetXMLNode(capabilities, "Contents");
910
0
    if (contents)
911
0
    {
912
0
        for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
913
0
             summary = summary->psNext)
914
0
        {
915
0
            if (summary->eType != CXT_Element ||
916
0
                !EQUAL(summary->pszValue, "CoverageSummary"))
917
0
            {
918
0
                continue;
919
0
            }
920
0
            CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId");
921
0
            CPLString id;
922
0
            if (node)
923
0
            {
924
0
                id = CPLGetXMLValue(node, nullptr, "");
925
0
            }
926
0
            else
927
0
            {
928
0
                node = CPLGetXMLNode(summary, "Identifier");
929
0
                if (node)
930
0
                {
931
0
                    id = CPLGetXMLValue(node, nullptr, "");
932
0
                }
933
0
                else
934
0
                {
935
0
                    id = "";
936
0
                }
937
0
            }
938
0
            if (id != coverage)
939
0
            {
940
0
                continue;
941
0
            }
942
943
            // Description
944
            // todo: there could be Title and Abstract for each supported
945
            // language
946
0
            XMLCopyMetadata(summary, metadata, "Title");
947
0
            XMLCopyMetadata(summary, metadata, "Abstract");
948
949
            // 2.0.1 stuff
950
0
            XMLCopyMetadata(summary, metadata, "CoverageSubtype");
951
952
            // Keywords
953
0
            CPLString kw = GetKeywords(summary, "Keywords", "Keyword");
954
0
            CPLAddXMLAttributeAndValue(
955
0
                CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
956
0
                "Keywords");
957
958
            // WCSContents
959
0
            const char *tags[] = {"SupportedCRS", "SupportedFormat",
960
0
                                  "OtherSource"};
961
0
            for (unsigned int i = 0; i < CPL_ARRAYSIZE(tags); i++)
962
0
            {
963
0
                kw = GetKeywords(summary, "", tags[i]);
964
0
                CPLAddXMLAttributeAndValue(
965
0
                    CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
966
0
                    tags[i]);
967
0
            }
968
969
            // skipping WGS84BoundingBox, BoundingBox, Metadata, Extension
970
            // since those we'll get from coverage description
971
0
        }
972
0
    }
973
0
}