Coverage Report

Created: 2025-06-09 07:43

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