Coverage Report

Created: 2025-06-09 07:02

/src/gdal/frmts/wcs/wcsdataset100.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  WCS Client Driver
4
 * Purpose:  Implementation of Dataset class for WCS 1.0.
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
/*                         GetExtent()                                  */
34
/*                                                                      */
35
/************************************************************************/
36
37
std::vector<double> WCSDataset100::GetExtent(int nXOff, int nYOff, int nXSize,
38
                                             int nYSize, CPL_UNUSED int,
39
                                             CPL_UNUSED int)
40
0
{
41
0
    std::vector<double> extent;
42
    // WCS 1.0 extents are the outer edges of outer pixels.
43
0
    extent.push_back(adfGeoTransform[0] + (nXOff)*adfGeoTransform[1]);
44
0
    extent.push_back(adfGeoTransform[3] +
45
0
                     (nYOff + nYSize) * adfGeoTransform[5]);
46
0
    extent.push_back(adfGeoTransform[0] +
47
0
                     (nXOff + nXSize) * adfGeoTransform[1]);
48
0
    extent.push_back(adfGeoTransform[3] + (nYOff)*adfGeoTransform[5]);
49
0
    return extent;
50
0
}
51
52
/************************************************************************/
53
/*                        GetCoverageRequest()                          */
54
/*                                                                      */
55
/************************************************************************/
56
57
std::string WCSDataset100::GetCoverageRequest(bool /* scaled */, int nBufXSize,
58
                                              int nBufYSize,
59
                                              const std::vector<double> &extent,
60
                                              const std::string &osBandList)
61
0
{
62
63
    /* -------------------------------------------------------------------- */
64
    /*      URL encode strings that could have questionable characters.     */
65
    /* -------------------------------------------------------------------- */
66
0
    CPLString osCoverage = CPLGetXMLValue(psService, "CoverageName", "");
67
68
0
    char *pszEncoded = CPLEscapeString(osCoverage, -1, CPLES_URL);
69
0
    osCoverage = pszEncoded;
70
0
    CPLFree(pszEncoded);
71
72
0
    CPLString osFormat = CPLGetXMLValue(psService, "PreferredFormat", "");
73
74
0
    pszEncoded = CPLEscapeString(osFormat, -1, CPLES_URL);
75
0
    osFormat = pszEncoded;
76
0
    CPLFree(pszEncoded);
77
78
    /* -------------------------------------------------------------------- */
79
    /*      Do we have a time we want to use?                               */
80
    /* -------------------------------------------------------------------- */
81
0
    CPLString osTime;
82
83
0
    osTime =
84
0
        CSLFetchNameValueDef(papszSDSModifiers, "time", osDefaultTime.c_str());
85
86
    /* -------------------------------------------------------------------- */
87
    /*      Construct a "simple" GetCoverage request (WCS 1.0).             */
88
    /* -------------------------------------------------------------------- */
89
0
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
90
0
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
91
0
    request = CPLURLAddKVP(request.c_str(), "REQUEST", "GetCoverage");
92
0
    request = CPLURLAddKVP(request.c_str(), "VERSION",
93
0
                           CPLGetXMLValue(psService, "Version", "1.0.0"));
94
0
    request = CPLURLAddKVP(request.c_str(), "COVERAGE", osCoverage.c_str());
95
0
    request = CPLURLAddKVP(request.c_str(), "FORMAT", osFormat.c_str());
96
0
    request += CPLString().Printf(
97
0
        "&BBOX=%.15g,%.15g,%.15g,%.15g&WIDTH=%d&HEIGHT=%d&CRS=%s", extent[0],
98
0
        extent[1], extent[2], extent[3], nBufXSize, nBufYSize, osCRS.c_str());
99
0
    CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
100
0
    if (extra != "")
101
0
    {
102
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
103
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
104
0
        {
105
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
106
0
            request =
107
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
108
0
        }
109
0
    }
110
0
    extra = CPLGetXMLValue(psService, "GetCoverageExtra", "");
111
0
    if (extra != "")
112
0
    {
113
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
114
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
115
0
        {
116
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
117
0
            request =
118
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
119
0
        }
120
0
    }
121
122
0
    CPLString interpolation = CPLGetXMLValue(psService, "Interpolation", "");
123
0
    if (interpolation == "")
124
0
    {
125
        // old undocumented key for interpolation in service
126
0
        interpolation = CPLGetXMLValue(psService, "Resample", "");
127
0
    }
128
0
    if (interpolation != "")
129
0
    {
130
0
        request += "&INTERPOLATION=" + interpolation;
131
0
    }
132
133
0
    if (osTime != "")
134
0
    {
135
0
        request += "&time=";
136
0
        request += osTime;
137
0
    }
138
139
0
    if (osBandList != "")
140
0
    {
141
0
        request += CPLString().Printf("&%s=%s", osBandIdentifier.c_str(),
142
0
                                      osBandList.c_str());
143
0
    }
144
0
    return request;
145
0
}
146
147
/************************************************************************/
148
/*                      DescribeCoverageRequest()                       */
149
/*                                                                      */
150
/************************************************************************/
151
152
std::string WCSDataset100::DescribeCoverageRequest()
153
0
{
154
0
    std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
155
0
    request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
156
0
    request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
157
0
    request = CPLURLAddKVP(request.c_str(), "VERSION",
158
0
                           CPLGetXMLValue(psService, "Version", "1.0.0"));
159
0
    request = CPLURLAddKVP(request.c_str(), "COVERAGE",
160
0
                           CPLGetXMLValue(psService, "CoverageName", ""));
161
0
    CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
162
0
    if (extra != "")
163
0
    {
164
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
165
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
166
0
        {
167
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
168
0
            request =
169
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
170
0
        }
171
0
    }
172
0
    extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
173
0
    if (extra != "")
174
0
    {
175
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
176
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
177
0
        {
178
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
179
0
            request =
180
0
                CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
181
0
        }
182
0
    }
183
0
    return request;
184
0
}
185
186
/************************************************************************/
187
/*                         CoverageOffering()                           */
188
/*                                                                      */
189
/************************************************************************/
190
191
CPLXMLNode *WCSDataset100::CoverageOffering(CPLXMLNode *psDC)
192
0
{
193
0
    return CPLGetXMLNode(psDC, "=CoverageDescription.CoverageOffering");
194
0
}
195
196
/************************************************************************/
197
/*                         ExtractGridInfo()                            */
198
/*                                                                      */
199
/*      Collect info about grid from describe coverage for WCS 1.0.0    */
200
/*      and above.                                                      */
201
/************************************************************************/
202
203
bool WCSDataset100::ExtractGridInfo()
204
205
0
{
206
0
    CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageOffering");
207
208
0
    if (psCO == nullptr)
209
0
        return FALSE;
210
211
    /* -------------------------------------------------------------------- */
212
    /*      We need to strip off name spaces so it is easier to             */
213
    /*      searchfor plain gml names.                                      */
214
    /* -------------------------------------------------------------------- */
215
0
    CPLStripXMLNamespace(psCO, nullptr, TRUE);
216
217
    /* -------------------------------------------------------------------- */
218
    /*      Verify we have a Rectified Grid.                                */
219
    /* -------------------------------------------------------------------- */
220
0
    CPLXMLNode *psRG =
221
0
        CPLGetXMLNode(psCO, "domainSet.spatialDomain.RectifiedGrid");
222
223
0
    if (psRG == nullptr)
224
0
    {
225
0
        CPLError(CE_Failure, CPLE_AppDefined,
226
0
                 "Unable to find RectifiedGrid in CoverageOffering,\n"
227
0
                 "unable to process WCS Coverage.");
228
0
        return FALSE;
229
0
    }
230
231
    /* -------------------------------------------------------------------- */
232
    /*      Extract size, geotransform and coordinate system.               */
233
    /*      Projection is, if it is, from Point.srsName                     */
234
    /* -------------------------------------------------------------------- */
235
0
    char *pszProjection = nullptr;
236
0
    if (WCSParseGMLCoverage(psRG, &nRasterXSize, &nRasterYSize, adfGeoTransform,
237
0
                            &pszProjection) != CE_None)
238
0
    {
239
0
        CPLFree(pszProjection);
240
0
        return FALSE;
241
0
    }
242
0
    if (pszProjection)
243
0
        m_oSRS.SetFromUserInput(
244
0
            pszProjection,
245
0
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
246
0
    CPLFree(pszProjection);
247
248
    // MapServer have origin at pixel boundary
249
0
    if (CPLGetXMLBoolean(psService, "OriginAtBoundary"))
250
0
    {
251
0
        adfGeoTransform[0] += adfGeoTransform[1] * 0.5;
252
0
        adfGeoTransform[0] += adfGeoTransform[2] * 0.5;
253
0
        adfGeoTransform[3] += adfGeoTransform[4] * 0.5;
254
0
        adfGeoTransform[3] += adfGeoTransform[5] * 0.5;
255
0
    }
256
257
    /* -------------------------------------------------------------------- */
258
    /*      Fallback to nativeCRSs declaration.                             */
259
    /* -------------------------------------------------------------------- */
260
0
    const char *pszNativeCRSs =
261
0
        CPLGetXMLValue(psCO, "supportedCRSs.nativeCRSs", nullptr);
262
263
0
    if (pszNativeCRSs == nullptr)
264
0
        pszNativeCRSs =
265
0
            CPLGetXMLValue(psCO, "supportedCRSs.requestResponseCRSs", nullptr);
266
267
0
    if (pszNativeCRSs == nullptr)
268
0
        pszNativeCRSs =
269
0
            CPLGetXMLValue(psCO, "supportedCRSs.requestCRSs", nullptr);
270
271
0
    if (pszNativeCRSs == nullptr)
272
0
        pszNativeCRSs =
273
0
            CPLGetXMLValue(psCO, "supportedCRSs.responseCRSs", nullptr);
274
275
0
    if (pszNativeCRSs != nullptr && m_oSRS.IsEmpty())
276
0
    {
277
0
        if (m_oSRS.SetFromUserInput(
278
0
                pszNativeCRSs,
279
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
280
0
            OGRERR_NONE)
281
0
        {
282
0
            CPLDebug("WCS", "<nativeCRSs> element contents not parsable:\n%s",
283
0
                     pszNativeCRSs);
284
0
        }
285
0
    }
286
287
    // We should try to use the services name for the CRS if possible.
288
0
    if (pszNativeCRSs != nullptr &&
289
0
        (STARTS_WITH_CI(pszNativeCRSs, "EPSG:") ||
290
0
         STARTS_WITH_CI(pszNativeCRSs, "AUTO:") ||
291
0
         STARTS_WITH_CI(pszNativeCRSs, "Image ") ||
292
0
         STARTS_WITH_CI(pszNativeCRSs, "Engineering ") ||
293
0
         STARTS_WITH_CI(pszNativeCRSs, "OGC:")))
294
0
    {
295
0
        osCRS = pszNativeCRSs;
296
297
0
        size_t nDivider = osCRS.find(" ");
298
299
0
        if (nDivider != std::string::npos)
300
0
            osCRS.resize(nDivider - 1);
301
0
    }
302
303
    /* -------------------------------------------------------------------- */
304
    /*      Do we have a coordinate system override?                        */
305
    /* -------------------------------------------------------------------- */
306
0
    const char *pszProjOverride = CPLGetXMLValue(psService, "SRS", nullptr);
307
308
0
    if (pszProjOverride)
309
0
    {
310
0
        if (m_oSRS.SetFromUserInput(
311
0
                pszProjOverride,
312
0
                OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
313
0
            OGRERR_NONE)
314
0
        {
315
0
            CPLError(CE_Failure, CPLE_AppDefined,
316
0
                     "<SRS> element contents not parsable:\n%s",
317
0
                     pszProjOverride);
318
0
            return FALSE;
319
0
        }
320
321
0
        if (STARTS_WITH_CI(pszProjOverride, "EPSG:") ||
322
0
            STARTS_WITH_CI(pszProjOverride, "AUTO:") ||
323
0
            STARTS_WITH_CI(pszProjOverride, "OGC:") ||
324
0
            STARTS_WITH_CI(pszProjOverride, "Image ") ||
325
0
            STARTS_WITH_CI(pszProjOverride, "Engineering "))
326
0
            osCRS = pszProjOverride;
327
0
    }
328
329
    /* -------------------------------------------------------------------- */
330
    /*      Build CRS name to use.                                          */
331
    /* -------------------------------------------------------------------- */
332
0
    if (!m_oSRS.IsEmpty() && osCRS == "")
333
0
    {
334
0
        const char *pszAuth = m_oSRS.GetAuthorityName(nullptr);
335
0
        if (pszAuth != nullptr && EQUAL(pszAuth, "EPSG"))
336
0
        {
337
0
            pszAuth = m_oSRS.GetAuthorityCode(nullptr);
338
0
            if (pszAuth)
339
0
            {
340
0
                osCRS = "EPSG:";
341
0
                osCRS += pszAuth;
342
0
            }
343
0
            else
344
0
            {
345
0
                CPLError(CE_Failure, CPLE_AppDefined,
346
0
                         "Unable to define CRS to use.");
347
0
                return FALSE;
348
0
            }
349
0
        }
350
0
    }
351
352
    /* -------------------------------------------------------------------- */
353
    /*      Pick a format type if we don't already have one selected.       */
354
    /*                                                                      */
355
    /*      We will prefer anything that sounds like TIFF, otherwise        */
356
    /*      falling back to the first supported format.  Should we          */
357
    /*      consider preferring the nativeFormat if available?              */
358
    /* -------------------------------------------------------------------- */
359
0
    if (CPLGetXMLValue(psService, "PreferredFormat", nullptr) == nullptr)
360
0
    {
361
0
        CPLXMLNode *psSF = CPLGetXMLNode(psCO, "supportedFormats");
362
0
        CPLXMLNode *psNode;
363
0
        char **papszFormatList = nullptr;
364
0
        CPLString osPreferredFormat;
365
0
        int iFormat;
366
367
0
        if (psSF == nullptr)
368
0
        {
369
0
            CPLError(
370
0
                CE_Failure, CPLE_AppDefined,
371
0
                "No <PreferredFormat> tag in service definition file, and no\n"
372
0
                "<supportedFormats> in coverageOffering.");
373
0
            return FALSE;
374
0
        }
375
376
0
        for (psNode = psSF->psChild; psNode != nullptr; psNode = psNode->psNext)
377
0
        {
378
0
            if (psNode->eType == CXT_Element &&
379
0
                EQUAL(psNode->pszValue, "formats") &&
380
0
                psNode->psChild != nullptr &&
381
0
                psNode->psChild->eType == CXT_Text)
382
0
            {
383
                // This check is looking for deprecated WCS 1.0 capabilities
384
                // with multiple formats space delimited in a single <formats>
385
                // element per GDAL ticket 1748 (done by MapServer 4.10 and
386
                // earlier for instance).
387
0
                if (papszFormatList == nullptr && psNode->psNext == nullptr &&
388
0
                    strstr(psNode->psChild->pszValue, " ") != nullptr &&
389
0
                    strstr(psNode->psChild->pszValue, ";") == nullptr)
390
0
                {
391
0
                    char **papszSubList =
392
0
                        CSLTokenizeString(psNode->psChild->pszValue);
393
0
                    papszFormatList =
394
0
                        CSLInsertStrings(papszFormatList, -1, papszSubList);
395
0
                    CSLDestroy(papszSubList);
396
0
                }
397
0
                else
398
0
                {
399
0
                    papszFormatList = CSLAddString(papszFormatList,
400
0
                                                   psNode->psChild->pszValue);
401
0
                }
402
0
            }
403
0
        }
404
405
0
        for (iFormat = 0;
406
0
             papszFormatList != nullptr && papszFormatList[iFormat] != nullptr;
407
0
             iFormat++)
408
0
        {
409
0
            if (osPreferredFormat.empty())
410
0
                osPreferredFormat = papszFormatList[iFormat];
411
412
0
            if (strstr(papszFormatList[iFormat], "tiff") != nullptr ||
413
0
                strstr(papszFormatList[iFormat], "TIFF") != nullptr ||
414
0
                strstr(papszFormatList[iFormat], "Tiff") != nullptr)
415
0
            {
416
0
                osPreferredFormat = papszFormatList[iFormat];
417
0
                break;
418
0
            }
419
0
        }
420
421
0
        CSLDestroy(papszFormatList);
422
423
0
        if (!osPreferredFormat.empty())
424
0
        {
425
0
            bServiceDirty = true;
426
0
            CPLCreateXMLElementAndValue(psService, "PreferredFormat",
427
0
                                        osPreferredFormat);
428
0
        }
429
0
    }
430
431
    /* -------------------------------------------------------------------- */
432
    /*      Try to identify a nodata value.  For now we only support the    */
433
    /*      singleValue mechanism.                                          */
434
    /* -------------------------------------------------------------------- */
435
0
    if (CPLGetXMLValue(psService, "NoDataValue", nullptr) == nullptr)
436
0
    {
437
0
        const char *pszSV = CPLGetXMLValue(
438
0
            psCO, "rangeSet.RangeSet.nullValues.singleValue", nullptr);
439
440
0
        if (pszSV != nullptr && (CPLAtof(pszSV) != 0.0 || *pszSV == DIGIT_ZERO))
441
0
        {
442
0
            bServiceDirty = true;
443
0
            CPLCreateXMLElementAndValue(psService, "NoDataValue", pszSV);
444
0
        }
445
0
    }
446
447
    /* -------------------------------------------------------------------- */
448
    /*      Do we have a Band range type.  For now we look for a fairly     */
449
    /*      specific configuration.  The rangeset my have one axis named    */
450
    /*      "Band", with a set of ascending numerical values.               */
451
    /* -------------------------------------------------------------------- */
452
0
    osBandIdentifier = CPLGetXMLValue(psService, "BandIdentifier", "");
453
0
    CPLXMLNode *psAD = CPLGetXMLNode(
454
0
        psService,
455
0
        "CoverageOffering.rangeSet.RangeSet.axisDescription.AxisDescription");
456
0
    CPLXMLNode *psValues;
457
458
0
    if (osBandIdentifier.empty() && psAD != nullptr &&
459
0
        (EQUAL(CPLGetXMLValue(psAD, "name", ""), "Band") ||
460
0
         EQUAL(CPLGetXMLValue(psAD, "name", ""), "Bands")) &&
461
0
        ((psValues = CPLGetXMLNode(psAD, "values")) != nullptr))
462
0
    {
463
0
        CPLXMLNode *psSV;
464
0
        int iBand;
465
466
0
        osBandIdentifier = CPLGetXMLValue(psAD, "name", "");
467
468
0
        for (psSV = psValues->psChild, iBand = 1; psSV != nullptr;
469
0
             psSV = psSV->psNext, iBand++)
470
0
        {
471
0
            if (psSV->eType != CXT_Element ||
472
0
                !EQUAL(psSV->pszValue, "singleValue") ||
473
0
                psSV->psChild == nullptr || psSV->psChild->eType != CXT_Text ||
474
0
                atoi(psSV->psChild->pszValue) != iBand)
475
0
            {
476
0
                osBandIdentifier = "";
477
0
                break;
478
0
            }
479
0
        }
480
481
0
        if (!osBandIdentifier.empty())
482
0
        {
483
0
            bServiceDirty = true;
484
0
            CPLSetXMLValue(psService, "BandIdentifier",
485
0
                           osBandIdentifier.c_str());
486
0
        }
487
0
    }
488
489
    /* -------------------------------------------------------------------- */
490
    /*      Do we have a temporal domain?  If so, try to identify a         */
491
    /*      default time value.                                             */
492
    /* -------------------------------------------------------------------- */
493
0
    osDefaultTime = CPLGetXMLValue(psService, "DefaultTime", "");
494
0
    CPLXMLNode *psTD =
495
0
        CPLGetXMLNode(psService, "CoverageOffering.domainSet.temporalDomain");
496
0
    CPLString osServiceURL = CPLGetXMLValue(psService, "ServiceURL", "");
497
0
    CPLString osCoverageExtra =
498
0
        CPLGetXMLValue(psService, "GetCoverageExtra", "");
499
500
0
    if (psTD != nullptr)
501
0
    {
502
0
        CPLXMLNode *psTime;
503
504
        // collect all the allowed time positions.
505
506
0
        for (psTime = psTD->psChild; psTime != nullptr; psTime = psTime->psNext)
507
0
        {
508
0
            if (psTime->eType == CXT_Element &&
509
0
                EQUAL(psTime->pszValue, "timePosition") &&
510
0
                psTime->psChild != nullptr &&
511
0
                psTime->psChild->eType == CXT_Text)
512
0
                aosTimePositions.push_back(psTime->psChild->pszValue);
513
0
        }
514
515
        // we will default to the last - likely the most recent - entry.
516
517
0
        if (!aosTimePositions.empty() && osDefaultTime.empty() &&
518
0
            osServiceURL.ifind("time=") == std::string::npos &&
519
0
            osCoverageExtra.ifind("time=") == std::string::npos)
520
0
        {
521
0
            osDefaultTime = aosTimePositions.back();
522
0
            bServiceDirty = true;
523
0
            CPLCreateXMLElementAndValue(psService, "DefaultTime",
524
0
                                        osDefaultTime.c_str());
525
0
        }
526
0
    }
527
528
0
    return true;
529
0
}
530
531
/************************************************************************/
532
/*                      ParseCapabilities()                             */
533
/************************************************************************/
534
535
CPLErr WCSDataset100::ParseCapabilities(CPLXMLNode *Capabilities,
536
                                        const std::string & /* url */)
537
0
{
538
539
0
    CPLStripXMLNamespace(Capabilities, nullptr, TRUE);
540
541
0
    if (strcmp(Capabilities->pszValue, "WCS_Capabilities") != 0)
542
0
    {
543
0
        CPLError(CE_Failure, CPLE_AppDefined,
544
0
                 "Error in capabilities document.\n");
545
0
        return CE_Failure;
546
0
    }
547
548
0
    char **metadata = nullptr;
549
0
    CPLString path = "WCS_GLOBAL#";
550
551
0
    CPLString key = path + "version";
552
0
    metadata = CSLSetNameValue(metadata, key, Version());
553
554
0
    for (CPLXMLNode *node = Capabilities->psChild; node != nullptr;
555
0
         node = node->psNext)
556
0
    {
557
0
        const char *attr = node->pszValue;
558
0
        if (node->eType == CXT_Attribute && EQUAL(attr, "updateSequence"))
559
0
        {
560
0
            key = path + "updateSequence";
561
0
            CPLString value = CPLGetXMLValue(node, nullptr, "");
562
0
            metadata = CSLSetNameValue(metadata, key, value);
563
0
        }
564
0
    }
565
566
    // identification metadata
567
0
    CPLString path2 = path;
568
0
    CPLXMLNode *service = AddSimpleMetaData(
569
0
        &metadata, Capabilities, path2, "Service",
570
0
        {"description", "name", "label", "fees", "accessConstraints"});
571
0
    if (service)
572
0
    {
573
0
        CPLString path3 = std::move(path2);
574
0
        CPLString kw = GetKeywords(service, "keywords", "keyword");
575
0
        if (kw != "")
576
0
        {
577
0
            CPLString name = path + "keywords";
578
0
            metadata = CSLSetNameValue(metadata, name, kw);
579
0
        }
580
0
        CPLXMLNode *party = AddSimpleMetaData(
581
0
            &metadata, service, path3, "responsibleParty",
582
0
            {"individualName", "organisationName", "positionName"});
583
0
        CPLXMLNode *info = CPLGetXMLNode(party, "contactInfo");
584
0
        if (party && info)
585
0
        {
586
0
            CPLString path4 = path3 + "contactInfo.";
587
0
            CPLString path5 = path4;
588
0
            AddSimpleMetaData(&metadata, info, path4, "address",
589
0
                              {"deliveryPoint", "city", "administrativeArea",
590
0
                               "postalCode", "country",
591
0
                               "electronicMailAddress"});
592
0
            AddSimpleMetaData(&metadata, info, path5, "phone",
593
0
                              {"voice", "facsimile"});
594
0
        }
595
0
    }
596
597
    // provider metadata
598
    // operations metadata
599
0
    CPLString DescribeCoverageURL;
600
0
    DescribeCoverageURL = CPLGetXMLValue(
601
0
        CPLGetXMLNode(
602
0
            CPLGetXMLNode(
603
0
                CPLSearchXMLNode(
604
0
                    CPLSearchXMLNode(Capabilities, "DescribeCoverage"), "Get"),
605
0
                "OnlineResource"),
606
0
            "href"),
607
0
        nullptr, "");
608
    // if DescribeCoverageURL looks wrong (i.e. has localhost) should we change
609
    // it?
610
611
0
    this->SetMetadata(metadata, "");
612
0
    CSLDestroy(metadata);
613
0
    metadata = nullptr;
614
615
0
    if (CPLXMLNode *contents = CPLGetXMLNode(Capabilities, "ContentMetadata"))
616
0
    {
617
0
        int index = 1;
618
0
        for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
619
0
             summary = summary->psNext)
620
0
        {
621
0
            if (summary->eType != CXT_Element ||
622
0
                !EQUAL(summary->pszValue, "CoverageOfferingBrief"))
623
0
            {
624
0
                continue;
625
0
            }
626
0
            CPLString path3;
627
0
            path3.Printf("SUBDATASET_%d_", index);
628
0
            index += 1;
629
630
            // the name and description of the subdataset:
631
            // GDAL Data Model:
632
            // The value of the _NAME is a string that can be passed to
633
            // GDALOpen() to access the file.
634
635
0
            CPLXMLNode *node = CPLGetXMLNode(summary, "name");
636
0
            if (node)
637
0
            {
638
0
                CPLString key2 = path3 + "NAME";
639
0
                CPLString name = CPLGetXMLValue(node, nullptr, "");
640
0
                CPLString value = DescribeCoverageURL;
641
0
                value = CPLURLAddKVP(value, "VERSION", this->Version());
642
0
                value = CPLURLAddKVP(value, "COVERAGE", name);
643
0
                metadata = CSLSetNameValue(metadata, key2, value);
644
0
            }
645
0
            else
646
0
            {
647
0
                CSLDestroy(metadata);
648
0
                CPLError(CE_Failure, CPLE_AppDefined,
649
0
                         "Error in capabilities document.\n");
650
0
                return CE_Failure;
651
0
            }
652
653
0
            node = CPLGetXMLNode(summary, "label");
654
0
            if (node)
655
0
            {
656
0
                CPLString key2 = path3 + "DESC";
657
0
                metadata = CSLSetNameValue(metadata, key2,
658
0
                                           CPLGetXMLValue(node, nullptr, ""));
659
0
            }
660
0
            else
661
0
            {
662
0
                CSLDestroy(metadata);
663
0
                CPLError(CE_Failure, CPLE_AppDefined,
664
0
                         "Error in capabilities document.\n");
665
0
                return CE_Failure;
666
0
            }
667
668
            // todo: compose global bounding box from lonLatEnvelope
669
670
            // further subdataset (coverage) parameters are parsed in
671
            // ParseCoverageCapabilities
672
0
        }
673
0
    }
674
0
    this->SetMetadata(metadata, "SUBDATASETS");
675
0
    CSLDestroy(metadata);
676
0
    return CE_None;
677
0
}
678
679
void WCSDataset100::ParseCoverageCapabilities(CPLXMLNode *capabilities,
680
                                              const std::string &coverage,
681
                                              CPLXMLNode *metadata)
682
0
{
683
0
    CPLStripXMLNamespace(capabilities, nullptr, TRUE);
684
0
    if (CPLXMLNode *contents = CPLGetXMLNode(capabilities, "ContentMetadata"))
685
0
    {
686
0
        for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
687
0
             summary = summary->psNext)
688
0
        {
689
0
            if (summary->eType != CXT_Element ||
690
0
                !EQUAL(summary->pszValue, "CoverageOfferingBrief"))
691
0
            {
692
0
                continue;
693
0
            }
694
695
0
            CPLXMLNode *node = CPLGetXMLNode(summary, "name");
696
0
            if (node)
697
0
            {
698
0
                CPLString name = CPLGetXMLValue(node, nullptr, "");
699
0
                if (name != coverage)
700
0
                {
701
0
                    continue;
702
0
                }
703
0
            }
704
705
0
            XMLCopyMetadata(summary, metadata, "label");
706
0
            XMLCopyMetadata(summary, metadata, "description");
707
708
0
            CPLString kw = GetKeywords(summary, "keywords", "keyword");
709
0
            CPLAddXMLAttributeAndValue(
710
0
                CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
711
0
                "keywords");
712
713
            // skip metadataLink
714
0
        }
715
0
    }
716
0
}