Coverage Report

Created: 2026-08-11 08:26

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