• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.02
/frmts/wcs/wcsdataset.cpp
1
/******************************************************************************
2
 *
3
 * Project:  WCS Client Driver
4
 * Purpose:  Implementation of Dataset and RasterBand classes for WCS.
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
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13

14
#include "cpl_string.h"
15
#include "cpl_minixml.h"
16
#include "cpl_http.h"
17
#include "gmlutils.h"
18
#include "gdal_frmts.h"
19
#include "gdal_pam.h"
20
#include "ogr_spatialref.h"
21
#include "gmlcoverage.h"
22

23
#include <algorithm>
24

25
#include "wcsdataset.h"
26
#include "wcsrasterband.h"
27
#include "wcsutils.h"
28
#include "wcsdrivercore.h"
29

30
using namespace WCSUtils;
31

32
/************************************************************************/
33
/*                             WCSDataset()                             */
34
/************************************************************************/
35

36
WCSDataset::WCSDataset(int version, const char *cache_dir)
121✔
37
    : m_cache_dir(cache_dir), bServiceDirty(false), psService(nullptr),
38
      papszSDSModifiers(nullptr), m_Version(version), native_crs(true),
39
      axis_order_swap(false), pabySavedDataBuffer(nullptr),
40
      papszHttpOptions(nullptr), nMaxCols(-1), nMaxRows(-1)
121✔
41
{
42
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
121✔
43

44
    apszCoverageOfferingMD[0] = nullptr;
121✔
45
    apszCoverageOfferingMD[1] = nullptr;
121✔
46
}
121✔
47

48
/************************************************************************/
49
/*                            ~WCSDataset()                             */
50
/************************************************************************/
51

52
WCSDataset::~WCSDataset()
121✔
53

54
{
55
    // perhaps this should be moved into a FlushCache(bool bAtClosing) method.
56
    if (bServiceDirty && !STARTS_WITH_CI(GetDescription(), "<WCS_GDAL>"))
121✔
57
    {
58
        CPLSerializeXMLTreeToFile(psService, GetDescription());
24✔
59
        bServiceDirty = false;
24✔
60
    }
61

62
    CPLDestroyXMLNode(psService);
121✔
63

64
    CSLDestroy(papszHttpOptions);
121✔
65
    CSLDestroy(papszSDSModifiers);
121✔
66

67
    CPLFree(apszCoverageOfferingMD[0]);
121✔
68

69
    FlushMemoryResult();
121✔
70
}
121✔
71

72
/************************************************************************/
73
/*                           SetCRS()                                   */
74
/*                                                                      */
75
/*      Set the name and the WKT of the projection of this dataset.     */
76
/*      Based on the projection, sets the axis order flag.              */
77
/*      Also set the native flag.                                       */
78
/************************************************************************/
79

80
bool WCSDataset::SetCRS(const std::string &crs, bool native)
57✔
81
{
82
    osCRS = crs;
57✔
83
    char *pszProjection = nullptr;
57✔
84
    if (!CRSImpliesAxisOrderSwap(osCRS, axis_order_swap, &pszProjection))
57✔
85
    {
86
        return false;
×
87
    }
88
    m_oSRS.importFromWkt(pszProjection);
57✔
89
    CPLFree(pszProjection);
57✔
90
    native_crs = native;
57✔
91
    return true;
57✔
92
}
93

94
/************************************************************************/
95
/*                           SetGeometry()                              */
96
/*                                                                      */
97
/*      Set GeoTransform and RasterSize from the coverage envelope,     */
98
/*      axis_order, grid size, and grid offsets.                        */
99
/************************************************************************/
100

101
void WCSDataset::SetGeometry(const std::vector<int> &size,
57✔
102
                             const std::vector<double> &origin,
103
                             const std::vector<std::vector<double>> &offsets)
104
{
105
    // note that this method is not used by wcsdataset100.cpp
106
    nRasterXSize = size[0];
57✔
107
    nRasterYSize = size[1];
57✔
108

109
    m_gt[0] = origin[0];
57✔
110
    m_gt[1] = offsets[0][0];
57✔
111
    m_gt[2] = offsets[0].size() == 1 ? 0.0 : offsets[0][1];
57✔
112
    m_gt[3] = origin[1];
57✔
113
    m_gt[4] = offsets[1].size() == 1 ? 0.0 : offsets[1][0];
57✔
114
    m_gt[5] = offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
57✔
115

116
    if (!CPLGetXMLBoolean(psService, "OriginAtBoundary"))
57✔
117
    {
118
        m_gt[0] -= m_gt[1] * 0.5;
54✔
119
        m_gt[0] -= m_gt[2] * 0.5;
54✔
120
        m_gt[3] -= m_gt[4] * 0.5;
54✔
121
        m_gt[3] -= m_gt[5] * 0.5;
54✔
122
    }
123
}
57✔
124

125
/************************************************************************/
126
/*                           TestUseBlockIO()                           */
127
/*                                                                      */
128
/*      Check whether we should use blocked IO (true) or direct io      */
129
/*      (FALSE) for a given request configuration and environment.      */
130
/************************************************************************/
131

132
int WCSDataset::TestUseBlockIO(CPL_UNUSED int nXOff, CPL_UNUSED int nYOff,
80✔
133
                               int nXSize, int nYSize, int nBufXSize,
134
                               int nBufYSize) const
135
{
136
    int bUseBlockedIO = bForceCachedIO;
80✔
137

138
    if (nYSize == 1 || nXSize * ((double)nYSize) < 100.0)
80✔
139
        bUseBlockedIO = TRUE;
56✔
140

141
    if (nBufYSize == 1 || nBufXSize * ((double)nBufYSize) < 100.0)
80✔
142
        bUseBlockedIO = TRUE;
56✔
143

144
    if (bUseBlockedIO &&
136✔
145
        CPLTestBool(CPLGetConfigOption("GDAL_ONE_BIG_READ", "NO")))
56✔
146
        bUseBlockedIO = FALSE;
×
147

148
    return bUseBlockedIO;
80✔
149
}
150

151
/************************************************************************/
152
/*                             IRasterIO()                              */
153
/************************************************************************/
154

155
CPLErr WCSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
22✔
156
                             int nXSize, int nYSize, void *pData, int nBufXSize,
157
                             int nBufYSize, GDALDataType eBufType,
158
                             int nBandCount, BANDMAP_TYPE panBandMap,
159
                             GSpacing nPixelSpace, GSpacing nLineSpace,
160
                             GSpacing nBandSpace,
161
                             GDALRasterIOExtraArg *psExtraArg)
162

163
{
164
    if ((nMaxCols > 0 && nMaxCols < nBufXSize) ||
22✔
165
        (nMaxRows > 0 && nMaxRows < nBufYSize))
22✔
166
        return CE_Failure;
×
167

168
    /* -------------------------------------------------------------------- */
169
    /*      We need various criteria to skip out to block based methods.    */
170
    /* -------------------------------------------------------------------- */
171
    if (TestUseBlockIO(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize))
22✔
172
        return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
11✔
173
                                         pData, nBufXSize, nBufYSize, eBufType,
174
                                         nBandCount, panBandMap, nPixelSpace,
175
                                         nLineSpace, nBandSpace, psExtraArg);
11✔
176
    else
177
        return DirectRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
11✔
178
                              nBufXSize, nBufYSize, eBufType, nBandCount,
179
                              panBandMap, nPixelSpace, nLineSpace, nBandSpace,
180
                              psExtraArg);
11✔
181
}
182

183
/************************************************************************/
184
/*                           DirectRasterIO()                           */
185
/*                                                                      */
186
/*      Make exactly one request to the server for this data.           */
187
/************************************************************************/
188

189
CPLErr WCSDataset::DirectRasterIO(CPL_UNUSED GDALRWFlag eRWFlag, int nXOff,
24✔
190
                                  int nYOff, int nXSize, int nYSize,
191
                                  void *pData, int nBufXSize, int nBufYSize,
192
                                  GDALDataType eBufType, int nBandCount,
193
                                  const int *panBandMap, GSpacing nPixelSpace,
194
                                  GSpacing nLineSpace, GSpacing nBandSpace,
195
                                  GDALRasterIOExtraArg *psExtraArg)
196
{
197
    CPLDebug("WCS", "DirectRasterIO(%d,%d,%d,%d) -> (%d,%d) (%d bands)\n",
24✔
198
             nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nBandCount);
199

200
    /* -------------------------------------------------------------------- */
201
    /*      Get the coverage.                                               */
202
    /* -------------------------------------------------------------------- */
203

204
    // if INTERLEAVE is set to PIXEL, then we'll request all bands.
205
    // That is necessary at least with MapServer, which seems to often
206
    // return all bands instead of requested.
207
    // todo: in 2.0.1 the band list in this dataset may be user-defined
208

209
    int band_count = nBandCount;
24✔
210
    if (EQUAL(CPLGetXMLValue(psService, "INTERLEAVE", ""), "PIXEL"))
24✔
211
    {
212
        band_count = 0;
24✔
213
    }
214

215
    CPLHTTPResult *psResult = nullptr;
24✔
216
    CPLErr eErr =
217
        GetCoverage(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
24✔
218
                    band_count, panBandMap, psExtraArg, &psResult);
219

220
    if (eErr != CE_None)
24✔
221
        return eErr;
×
222

223
    /* -------------------------------------------------------------------- */
224
    /*      Try and open result as a dataset.                               */
225
    /* -------------------------------------------------------------------- */
226
    GDALDataset *poTileDS = GDALOpenResult(psResult);
24✔
227

228
    if (poTileDS == nullptr)
24✔
229
        return CE_Failure;
×
230

231
    /* -------------------------------------------------------------------- */
232
    /*      Verify configuration.                                           */
233
    /* -------------------------------------------------------------------- */
234
    if (poTileDS->GetRasterXSize() != nBufXSize ||
48✔
235
        poTileDS->GetRasterYSize() != nBufYSize)
24✔
236
    {
237
        CPLError(CE_Failure, CPLE_AppDefined,
×
238
                 "Returned tile does not match expected configuration.\n"
239
                 "Got %dx%d instead of %dx%d.",
240
                 poTileDS->GetRasterXSize(), poTileDS->GetRasterYSize(),
241
                 nBufXSize, nBufYSize);
242
        delete poTileDS;
×
243
        return CE_Failure;
×
244
    }
245

246
    if (band_count != 0 && ((!osBandIdentifier.empty() &&
24✔
247
                             poTileDS->GetRasterCount() != nBandCount) ||
×
248
                            (osBandIdentifier.empty() &&
×
249
                             poTileDS->GetRasterCount() != GetRasterCount())))
×
250
    {
251
        CPLError(CE_Failure, CPLE_AppDefined,
×
252
                 "Returned tile does not match expected band count.");
253
        delete poTileDS;
×
254
        return CE_Failure;
×
255
    }
256

257
    /* -------------------------------------------------------------------- */
258
    /*      Pull requested bands from the downloaded dataset.               */
259
    /* -------------------------------------------------------------------- */
260
    eErr = CE_None;
24✔
261

262
    for (int iBand = 0; iBand < nBandCount && eErr == CE_None; iBand++)
69✔
263
    {
264
        GDALRasterBand *poTileBand = nullptr;
45✔
265

266
        if (!osBandIdentifier.empty())
45✔
267
            poTileBand = poTileDS->GetRasterBand(iBand + 1);
18✔
268
        else
269
            poTileBand = poTileDS->GetRasterBand(panBandMap[iBand]);
27✔
270

271
        eErr = poTileBand->RasterIO(GF_Read, 0, 0, nBufXSize, nBufYSize,
45✔
272
                                    ((GByte *)pData) + iBand * nBandSpace,
45✔
273
                                    nBufXSize, nBufYSize, eBufType, nPixelSpace,
274
                                    nLineSpace, nullptr);
275
    }
276

277
    /* -------------------------------------------------------------------- */
278
    /*      Cleanup                                                         */
279
    /* -------------------------------------------------------------------- */
280
    delete poTileDS;
24✔
281

282
    FlushMemoryResult();
24✔
283

284
    return eErr;
24✔
285
}
286

287
static bool ProcessError(CPLHTTPResult *psResult);
288

289
/************************************************************************/
290
/*                            GetCoverage()                             */
291
/*                                                                      */
292
/*      Issue the appropriate version of request for a given window,    */
293
/*      buffer size and band list.                                      */
294
/************************************************************************/
295

296
CPLErr WCSDataset::GetCoverage(int nXOff, int nYOff, int nXSize, int nYSize,
72✔
297
                               int nBufXSize, int nBufYSize, int nBandCount,
298
                               const int *panBandList,
299
                               GDALRasterIOExtraArg *psExtraArg,
300
                               CPLHTTPResult **ppsResult)
301

302
{
303
    /* -------------------------------------------------------------------- */
304
    /*      Figure out the georeferenced extents.                           */
305
    /* -------------------------------------------------------------------- */
306
    std::vector<double> extent =
307
        GetNativeExtent(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
144✔
308

309
    /* -------------------------------------------------------------------- */
310
    /*      Build band list if we have the band identifier.                 */
311
    /* -------------------------------------------------------------------- */
312
    std::string osBandList;
144✔
313

314
    if (!osBandIdentifier.empty() && nBandCount > 0 && panBandList != nullptr)
72✔
315
    {
316
        int iBand;
317

318
        for (iBand = 0; iBand < nBandCount; iBand++)
×
319
        {
320
            if (iBand > 0)
×
321
                osBandList += ",";
×
322
            osBandList += CPLString().Printf("%d", panBandList[iBand]);
×
323
        }
324
    }
325

326
    /* -------------------------------------------------------------------- */
327
    /*      Construct a KVP GetCoverage request.                            */
328
    /* -------------------------------------------------------------------- */
329
    bool scaled = nBufXSize != nXSize || nBufYSize != nYSize;
72✔
330
    std::string osRequest =
331
        GetCoverageRequest(scaled, nBufXSize, nBufYSize, extent, osBandList);
144✔
332
    // for the test setup we need the actual URLs this driver generates
333
    // fprintf(stdout, "URL=%s\n", osRequest.c_str());
334

335
    /* -------------------------------------------------------------------- */
336
    /*      Fetch the result.                                               */
337
    /* -------------------------------------------------------------------- */
338
    CPLErrorReset();
72✔
339
    if (psExtraArg && psExtraArg->pfnProgress != nullptr)
72✔
340
    {
341
        *ppsResult = CPLHTTPFetchEx(
24✔
342
            osRequest.c_str(), papszHttpOptions, psExtraArg->pfnProgress,
24✔
343
            psExtraArg->pProgressData, nullptr, nullptr);
344
    }
345
    else
346
    {
347
        *ppsResult = CPLHTTPFetch(osRequest.c_str(), papszHttpOptions);
48✔
348
    }
349

350
    if (ProcessError(*ppsResult))
72✔
351
        return CE_Failure;
×
352
    else
353
        return CE_None;
72✔
354
}
355

356
/************************************************************************/
357
/*                          DescribeCoverage()                          */
358
/*                                                                      */
359
/*      Fetch the DescribeCoverage result and attach it to the          */
360
/*      service description.                                            */
361
/************************************************************************/
362

363
int WCSDataset::DescribeCoverage()
25✔
364

365
{
366
    std::string osRequest;
50✔
367

368
    /* -------------------------------------------------------------------- */
369
    /*      Fetch coverage description for this coverage.                   */
370
    /* -------------------------------------------------------------------- */
371

372
    CPLXMLNode *psDC = nullptr;
25✔
373

374
    // if it is in cache, get it from there
375
    std::string dc_filename =
376
        this->GetDescription();  // the WCS_GDAL file (<basename>.xml)
50✔
377
    dc_filename.erase(dc_filename.length() - 4, 4);
25✔
378
    dc_filename += ".DC.xml";
25✔
379
    if (FileIsReadable(dc_filename))
25✔
380
    {
381
        psDC = CPLParseXMLFile(dc_filename.c_str());
×
382
    }
383

384
    if (!psDC)
25✔
385
    {
386
        osRequest = DescribeCoverageRequest();
25✔
387
        CPLErrorReset();
25✔
388
        CPLHTTPResult *psResult =
389
            CPLHTTPFetch(osRequest.c_str(), papszHttpOptions);
25✔
390
        if (ProcessError(psResult))
25✔
391
        {
392
            return FALSE;
1✔
393
        }
394

395
        /* --------------------------------------------------------------------
396
         */
397
        /*      Parse result. */
398
        /* --------------------------------------------------------------------
399
         */
400
        psDC = CPLParseXMLString((const char *)psResult->pabyData);
24✔
401
        CPLHTTPDestroyResult(psResult);
24✔
402

403
        if (psDC == nullptr)
24✔
404
        {
405
            return FALSE;
×
406
        }
407

408
        // if we have cache, put it there
409
        if (dc_filename != "")
24✔
410
        {
411
            CPLSerializeXMLTreeToFile(psDC, dc_filename.c_str());
24✔
412
        }
413
    }
414

415
    CPLStripXMLNamespace(psDC, nullptr, TRUE);
24✔
416

417
    /* -------------------------------------------------------------------- */
418
    /*      Did we get a CoverageOffering?                                  */
419
    /* -------------------------------------------------------------------- */
420
    CPLXMLNode *psCO = CoverageOffering(psDC);
24✔
421

422
    if (!psCO)
24✔
423
    {
424
        CPLDestroyXMLNode(psDC);
×
425

426
        CPLError(CE_Failure, CPLE_AppDefined,
×
427
                 "Failed to fetch a <CoverageOffering> back %s.",
428
                 osRequest.c_str());
429
        return FALSE;
×
430
    }
431

432
    /* -------------------------------------------------------------------- */
433
    /*      Duplicate the coverage offering, and insert into                */
434
    /* -------------------------------------------------------------------- */
435
    CPLXMLNode *psNext = psCO->psNext;
24✔
436
    psCO->psNext = nullptr;
24✔
437

438
    CPLAddXMLChild(psService, CPLCloneXMLTree(psCO));
24✔
439
    bServiceDirty = true;
24✔
440

441
    psCO->psNext = psNext;
24✔
442

443
    CPLDestroyXMLNode(psDC);
24✔
444
    return TRUE;
24✔
445
}
446

447
/************************************************************************/
448
/*                            ProcessError()                            */
449
/*                                                                      */
450
/*      Process an HTTP error, reporting it via CPL, and destroying     */
451
/*      the HTTP result object.  Returns TRUE if there was an error,    */
452
/*      or FALSE if the result seems ok.                                */
453
/************************************************************************/
454

455
static bool ProcessError(CPLHTTPResult *psResult)
121✔
456

457
{
458
    /* -------------------------------------------------------------------- */
459
    /*      There isn't much we can do in this case.  Hopefully an error    */
460
    /*      was already issued by CPLHTTPFetch()                            */
461
    /* -------------------------------------------------------------------- */
462
    if (psResult == nullptr || psResult->nDataLen == 0)
121✔
463
    {
464
        CPLHTTPDestroyResult(psResult);
1✔
465
        return TRUE;
1✔
466
    }
467

468
    /* -------------------------------------------------------------------- */
469
    /*      If we got an html document, we presume it is an error           */
470
    /*      message and report it verbatim up to a certain size limit.      */
471
    /* -------------------------------------------------------------------- */
472

473
    if (psResult->pszContentType != nullptr &&
120✔
474
        strstr(psResult->pszContentType, "html") != nullptr)
120✔
475
    {
476
        std::string osErrorMsg = (char *)psResult->pabyData;
×
477

478
        if (osErrorMsg.size() > 2048)
×
479
            osErrorMsg.resize(2048);
×
480

481
        CPLError(CE_Failure, CPLE_AppDefined, "Malformed Result:\n%s",
×
482
                 osErrorMsg.c_str());
483
        CPLHTTPDestroyResult(psResult);
×
484
        return TRUE;
×
485
    }
486

487
    /* -------------------------------------------------------------------- */
488
    /*      Does this look like a service exception?  We would like to      */
489
    /*      check based on the Content-type, but this seems quite           */
490
    /*      undependable, even from MapServer!                              */
491
    /* -------------------------------------------------------------------- */
492
    if (strstr((const char *)psResult->pabyData, "ExceptionReport"))
120✔
493
    {
494
        CPLXMLNode *psTree =
495
            CPLParseXMLString((const char *)psResult->pabyData);
×
496
        CPLStripXMLNamespace(psTree, nullptr, TRUE);
×
497
        std::string msg = CPLGetXMLValue(
498
            psTree, "=ServiceExceptionReport.ServiceException", "");
×
499
        if (msg == "")
×
500
        {
501
            msg = CPLGetXMLValue(
502
                psTree, "=ExceptionReport.Exception.exceptionCode", "");
×
503
            if (msg != "")
×
504
            {
505
                msg += ": ";
×
506
            }
507
            msg += CPLGetXMLValue(
508
                psTree, "=ExceptionReport.Exception.ExceptionText", "");
×
509
        }
510
        if (msg != "")
×
511
            CPLError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
×
512
        else
513
            CPLError(CE_Failure, CPLE_AppDefined,
×
514
                     "Corrupt Service Exception:\n%s",
515
                     (const char *)psResult->pabyData);
×
516
        CPLDestroyXMLNode(psTree);
×
517
        CPLHTTPDestroyResult(psResult);
×
518
        return TRUE;
×
519
    }
520

521
    /* -------------------------------------------------------------------- */
522
    /*      Hopefully the error already issued by CPLHTTPFetch() is         */
523
    /*      sufficient.                                                     */
524
    /* -------------------------------------------------------------------- */
525
    if (CPLGetLastErrorNo() != 0)
120✔
526
    {
527
        CPLHTTPDestroyResult(psResult);
×
528
        return TRUE;
×
529
    }
530

531
    return false;
120✔
532
}
533

534
/************************************************************************/
535
/*                       EstablishRasterDetails()                       */
536
/*                                                                      */
537
/*      Do a "test" coverage query to work out the number of bands,     */
538
/*      and pixel data type of the remote coverage.                     */
539
/************************************************************************/
540

541
int WCSDataset::EstablishRasterDetails()
72✔
542

543
{
544
    CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageOffering");
72✔
545

546
    const char *pszCols =
547
        CPLGetXMLValue(psCO, "dimensionLimit.columns", nullptr);
72✔
548
    const char *pszRows = CPLGetXMLValue(psCO, "dimensionLimit.rows", nullptr);
72✔
549
    if (pszCols && pszRows)
72✔
550
    {
551
        nMaxCols = atoi(pszCols);
×
552
        nMaxRows = atoi(pszRows);
×
553
        SetMetadataItem("MAXNCOLS", pszCols, "IMAGE_STRUCTURE");
×
554
        SetMetadataItem("MAXNROWS", pszRows, "IMAGE_STRUCTURE");
×
555
    }
556

557
    /* -------------------------------------------------------------------- */
558
    /*      Do we already have bandcount and pixel type settings?           */
559
    /* -------------------------------------------------------------------- */
560
    if (CPLGetXMLValue(psService, "BandCount", nullptr) != nullptr &&
133✔
561
        CPLGetXMLValue(psService, "BandType", nullptr) != nullptr)
61✔
562
        return TRUE;
48✔
563

564
    /* -------------------------------------------------------------------- */
565
    /*      Fetch a small block of raster data.                             */
566
    /* -------------------------------------------------------------------- */
567
    CPLHTTPResult *psResult = nullptr;
24✔
568
    CPLErr eErr;
569

570
    eErr = GetCoverage(0, 0, 2, 2, 2, 2, 0, nullptr, nullptr, &psResult);
24✔
571
    if (eErr != CE_None)
24✔
572
        return false;
×
573

574
    /* -------------------------------------------------------------------- */
575
    /*      Try and open result as a dataset.                               */
576
    /* -------------------------------------------------------------------- */
577
    GDALDataset *poDS = GDALOpenResult(psResult);
24✔
578

579
    if (poDS == nullptr)
24✔
580
        return false;
×
581

582
    const auto poSRS = poDS->GetSpatialRef();
24✔
583
    m_oSRS.Clear();
24✔
584
    if (poSRS)
24✔
585
        m_oSRS = *poSRS;
24✔
586

587
    /* -------------------------------------------------------------------- */
588
    /*      Record details.                                                 */
589
    /* -------------------------------------------------------------------- */
590
    if (poDS->GetRasterCount() < 1)
24✔
591
    {
592
        delete poDS;
×
593
        return false;
×
594
    }
595

596
    if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
24✔
597
        CPLCreateXMLElementAndValue(
11✔
598
            psService, "BandCount",
599
            CPLString().Printf("%d", poDS->GetRasterCount()));
22✔
600

601
    CPLCreateXMLElementAndValue(
24✔
602
        psService, "BandType",
603
        GDALGetDataTypeName(poDS->GetRasterBand(1)->GetRasterDataType()));
604

605
    bServiceDirty = true;
24✔
606

607
    /* -------------------------------------------------------------------- */
608
    /*      Cleanup                                                         */
609
    /* -------------------------------------------------------------------- */
610
    delete poDS;
24✔
611

612
    FlushMemoryResult();
24✔
613

614
    return TRUE;
24✔
615
}
616

617
/************************************************************************/
618
/*                         FlushMemoryResult()                          */
619
/*                                                                      */
620
/*      This actually either cleans up the in memory /vsimem/           */
621
/*      temporary file, or the on disk temporary file.                  */
622
/************************************************************************/
623
void WCSDataset::FlushMemoryResult()
265✔
624

625
{
626
    if (!osResultFilename.empty())
265✔
627
    {
628
        VSIUnlink(osResultFilename.c_str());
72✔
629
        osResultFilename = "";
72✔
630
    }
631

632
    if (pabySavedDataBuffer)
265✔
633
    {
634
        CPLFree(pabySavedDataBuffer);
72✔
635
        pabySavedDataBuffer = nullptr;
72✔
636
    }
637
}
265✔
638

639
/************************************************************************/
640
/*                           GDALOpenResult()                           */
641
/*                                                                      */
642
/*      Open a CPLHTTPResult as a GDALDataset (if possible).  First     */
643
/*      attempt is to open handle it "in memory".  Eventually we        */
644
/*      will add support for handling it on file if necessary.          */
645
/*                                                                      */
646
/*      This method will free CPLHTTPResult, the caller should not      */
647
/*      access it after the call.                                       */
648
/************************************************************************/
649

650
GDALDataset *WCSDataset::GDALOpenResult(CPLHTTPResult *psResult)
72✔
651

652
{
653
    FlushMemoryResult();
72✔
654

655
    CPLDebug("WCS", "GDALOpenResult() on content-type: %s",
72✔
656
             psResult->pszContentType);
657

658
    /* -------------------------------------------------------------------- */
659
    /*      If this is multipart/related content type, we should search     */
660
    /*      for the second part.                                            */
661
    /* -------------------------------------------------------------------- */
662
    GByte *pabyData = psResult->pabyData;
72✔
663
    int nDataLen = psResult->nDataLen;
72✔
664

665
    if (psResult->pszContentType &&
216✔
666
        strstr(psResult->pszContentType, "multipart") &&
72✔
667
        CPLHTTPParseMultipartMime(psResult))
×
668
    {
669
        if (psResult->nMimePartCount > 1)
×
670
        {
671
            pabyData = psResult->pasMimePart[1].pabyData;
×
672
            nDataLen = psResult->pasMimePart[1].nDataLen;
×
673

674
            const char *pszContentTransferEncoding =
675
                CSLFetchNameValue(psResult->pasMimePart[1].papszHeaders,
×
676
                                  "Content-Transfer-Encoding");
677
            if (pszContentTransferEncoding &&
×
678
                EQUAL(pszContentTransferEncoding, "base64"))
×
679
            {
680
                nDataLen = CPLBase64DecodeInPlace(pabyData);
×
681
            }
682
        }
683
    }
684

685
/* -------------------------------------------------------------------- */
686
/*      Create a memory file from the result.                           */
687
/* -------------------------------------------------------------------- */
688
#ifdef DEBUG_WCS
689
    // this facility is used by requests.pl to generate files for the test
690
    // server
691
    std::string xfn = CPLGetXMLValue(psService, "filename", "");
692
    if (xfn != "")
693
    {
694
        VSILFILE *fpTemp = VSIFOpenL(xfn, "wb");
695
        VSIFWriteL(pabyData, nDataLen, 1, fpTemp);
696
        VSIFCloseL(fpTemp);
697
    }
698
#endif
699
    // Eventually we should be looking at mime info and stuff to figure
700
    // out an optimal filename, but for now we just use a fixed one.
701
    osResultFilename = VSIMemGenerateHiddenFilename("wcsresult.dat");
72✔
702

703
    VSILFILE *fp = VSIFileFromMemBuffer(osResultFilename.c_str(), pabyData,
72✔
704
                                        nDataLen, FALSE);
705

706
    if (fp == nullptr)
72✔
707
    {
708
        CPLHTTPDestroyResult(psResult);
×
709
        return nullptr;
×
710
    }
711

712
    VSIFCloseL(fp);
72✔
713

714
    /* -------------------------------------------------------------------- */
715
    /*      Try opening this result as a gdaldataset.                       */
716
    /* -------------------------------------------------------------------- */
717
    GDALDataset *poDS =
718
        (GDALDataset *)GDALOpen(osResultFilename.c_str(), GA_ReadOnly);
72✔
719

720
    /* -------------------------------------------------------------------- */
721
    /*      If opening it in memory didn't work, perhaps we need to         */
722
    /*      write to a temp file on disk?                                   */
723
    /* -------------------------------------------------------------------- */
724
    if (poDS == nullptr)
72✔
725
    {
726
        std::string osTempFilename =
727
            CPLString().Printf("/tmp/%p_wcs.dat", this);
×
728
        VSILFILE *fpTemp = VSIFOpenL(osTempFilename.c_str(), "wb");
×
729
        if (fpTemp == nullptr)
×
730
        {
731
            CPLError(CE_Failure, CPLE_OpenFailed,
×
732
                     "Failed to create temporary file:%s",
733
                     osTempFilename.c_str());
734
        }
735
        else
736
        {
737
            if (VSIFWriteL(pabyData, nDataLen, 1, fpTemp) != 1)
×
738
            {
739
                CPLError(CE_Failure, CPLE_OpenFailed,
×
740
                         "Failed to write temporary file:%s",
741
                         osTempFilename.c_str());
742
                VSIFCloseL(fpTemp);
×
743
                VSIUnlink(osTempFilename.c_str());
×
744
            }
745
            else
746
            {
747
                VSIFCloseL(fpTemp);
×
748
                VSIUnlink(osResultFilename.c_str());
×
749
                osResultFilename = std::move(osTempFilename);
×
750

751
                poDS =
752
                    GDALDataset::Open(osResultFilename.c_str(),
×
753
                                      GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
754
            }
755
        }
756
    }
757

758
    /* -------------------------------------------------------------------- */
759
    /*      Steal the memory buffer from HTTP result.                       */
760
    /* -------------------------------------------------------------------- */
761
    pabySavedDataBuffer = psResult->pabyData;
72✔
762

763
    psResult->pabyData = nullptr;
72✔
764

765
    if (poDS == nullptr)
72✔
766
        FlushMemoryResult();
×
767

768
    CPLHTTPDestroyResult(psResult);
72✔
769

770
    return poDS;
72✔
771
}
772

773
/************************************************************************/
774
/*                            WCSParseVersion()                         */
775
/************************************************************************/
776

777
static int WCSParseVersion(const char *version)
217✔
778
{
779
    if (EQUAL(version, "2.0.1"))
217✔
780
        return 201;
63✔
781
    if (EQUAL(version, "1.1.2"))
154✔
782
        return 112;
18✔
783
    if (EQUAL(version, "1.1.1"))
136✔
784
        return 111;
51✔
785
    if (EQUAL(version, "1.1.0"))
85✔
786
        return 110;
39✔
787
    if (EQUAL(version, "1.0.0"))
46✔
788
        return 100;
46✔
789
    return 0;
×
790
}
791

792
/************************************************************************/
793
/*                             Version()                                */
794
/************************************************************************/
795

796
const char *WCSDataset::Version() const
147✔
797
{
798
    if (this->m_Version == 201)
147✔
799
        return "2.0.1";
67✔
800
    if (this->m_Version == 112)
80✔
801
        return "1.1.2";
11✔
802
    if (this->m_Version == 111)
69✔
803
        return "1.1.1";
35✔
804
    if (this->m_Version == 110)
34✔
805
        return "1.1.0";
11✔
806
    if (this->m_Version == 100)
23✔
807
        return "1.0.0";
23✔
808
    return "";
×
809
}
810

811
/************************************************************************/
812
/*                      FetchCapabilities()                             */
813
/************************************************************************/
814

815
#define WCS_HTTP_OPTIONS "TIMEOUT", "USERPWD", "HTTPAUTH"
816

817
static bool FetchCapabilities(GDALOpenInfo *poOpenInfo,
24✔
818
                              const std::string &urlIn, const std::string &path)
819
{
820
    std::string url = CPLURLAddKVP(urlIn.c_str(), "SERVICE", "WCS");
48✔
821
    url = CPLURLAddKVP(url.c_str(), "REQUEST", "GetCapabilities");
24✔
822
    std::string extra = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
24✔
823
                                             "GetCapabilitiesExtra", "");
48✔
824
    if (extra != "")
24✔
825
    {
826
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
×
827
        for (unsigned int i = 0; i < pairs.size(); ++i)
×
828
        {
829
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
×
830
            url = CPLURLAddKVP(url.c_str(), pair[0].c_str(), pair[1].c_str());
×
831
        }
832
    }
833
    char **options = nullptr;
24✔
834
    const char *keys[] = {WCS_HTTP_OPTIONS};
24✔
835
    for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
96✔
836
    {
837
        std::string value =
838
            CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, keys[i], "");
144✔
839
        if (value != "")
72✔
840
        {
841
            options = CSLSetNameValue(options, keys[i], value.c_str());
×
842
        }
843
    }
844
    CPLHTTPResult *psResult = CPLHTTPFetch(url.c_str(), options);
24✔
845
    CSLDestroy(options);
24✔
846
    if (ProcessError(psResult))
24✔
847
    {
848
        return false;
×
849
    }
850
    CPLXMLTreeCloser doc(CPLParseXMLString((const char *)psResult->pabyData));
48✔
851
    CPLHTTPDestroyResult(psResult);
24✔
852
    if (doc.get() == nullptr)
24✔
853
    {
854
        return false;
×
855
    }
856
    CPLXMLNode *capabilities = doc.get();
24✔
857
    CPLSerializeXMLTreeToFile(capabilities, path.c_str());
24✔
858
    return true;
24✔
859
}
860

861
/************************************************************************/
862
/*                      CreateFromCapabilities()                        */
863
/************************************************************************/
864

865
WCSDataset *WCSDataset::CreateFromCapabilities(const std::string &cache,
24✔
866
                                               const std::string &path,
867
                                               const std::string &url)
868
{
869
    CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
48✔
870
    if (doc.get() == nullptr)
24✔
871
    {
872
        return nullptr;
×
873
    }
874
    CPLXMLNode *capabilities = doc.getDocumentElement();
24✔
875
    if (capabilities == nullptr)
24✔
876
    {
877
        return nullptr;
×
878
    }
879
    // get version, this version will overwrite the user's request
880
    int version_from_server =
881
        WCSParseVersion(CPLGetXMLValue(capabilities, "version", ""));
24✔
882
    if (version_from_server == 0)
24✔
883
    {
884
        // broken server, assume 1.0.0
885
        version_from_server = 100;
×
886
    }
887
    WCSDataset *poDS;
888
    if (version_from_server == 201)
24✔
889
    {
890
        poDS = new WCSDataset201(cache.c_str());
7✔
891
    }
892
    else if (version_from_server / 10 == 11)
17✔
893
    {
894
        poDS = new WCSDataset110(version_from_server, cache.c_str());
12✔
895
    }
896
    else
897
    {
898
        poDS = new WCSDataset100(cache.c_str());
5✔
899
    }
900
    if (poDS->ParseCapabilities(capabilities, url) != CE_None)
24✔
901
    {
902
        delete poDS;
×
903
        return nullptr;
×
904
    }
905
    poDS->SetDescription(RemoveExt(path).c_str());
24✔
906
    poDS->TrySaveXML();
24✔
907
    return poDS;
24✔
908
}
909

910
/************************************************************************/
911
/*                        CreateFromMetadata()                          */
912
/************************************************************************/
913

914
WCSDataset *WCSDataset::CreateFromMetadata(const std::string &cache,
24✔
915
                                           const std::string &path)
916
{
917
    WCSDataset *poDS;
918
    if (FileIsReadable(path))
24✔
919
    {
920
        CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
24✔
921
        CPLXMLNode *metadata = doc.get();
24✔
922
        if (metadata == nullptr)
24✔
923
        {
924
            return nullptr;
×
925
        }
926
        int version_from_metadata = WCSParseVersion(CPLGetXMLValue(
24✔
927
            SearchChildWithValue(SearchChildWithValue(metadata, "domain", ""),
24✔
928
                                 "key", "WCS_GLOBAL#version"),
929
            nullptr, ""));
930
        if (version_from_metadata == 201)
24✔
931
        {
932
            poDS = new WCSDataset201(cache.c_str());
7✔
933
        }
934
        else if (version_from_metadata / 10 == 11)
17✔
935
        {
936
            poDS = new WCSDataset110(version_from_metadata, cache.c_str());
12✔
937
        }
938
        else if (version_from_metadata / 10 == 10)
5✔
939
        {
940
            poDS = new WCSDataset100(cache.c_str());
5✔
941
        }
942
        else
943
        {
944
            CPLError(CE_Failure, CPLE_AppDefined,
×
945
                     "The metadata does not contain version. RECREATE_META?");
946
            return nullptr;
×
947
        }
948
        std::string modifiedPath = RemoveExt(RemoveExt(path));
48✔
949
        poDS->SetDescription(modifiedPath.c_str());
24✔
950
        poDS->TryLoadXML();  // todo: avoid reload
24✔
951
    }
952
    else
953
    {
954
        // obviously there was an error
955
        // processing the Capabilities file
956
        // so we show it to the user
957
        GByte *pabyOut = nullptr;
×
958
        std::string modifiedPath = RemoveExt(RemoveExt(path)) + ".xml";
×
959
        if (!VSIIngestFile(nullptr, modifiedPath.c_str(), &pabyOut, nullptr,
×
960
                           -1))
961
            return nullptr;
×
962
        std::string error = reinterpret_cast<char *>(pabyOut);
×
963
        if (error.size() > 2048)
×
964
        {
965
            error.resize(2048);
×
966
        }
967
        CPLError(CE_Failure, CPLE_AppDefined, "Error:\n%s", error.c_str());
×
968
        CPLFree(pabyOut);
×
969
        return nullptr;
×
970
    }
971
    return poDS;
24✔
972
}
973

974
/************************************************************************/
975
/*                        BootstrapGlobal()                             */
976
/************************************************************************/
977

978
static WCSDataset *BootstrapGlobal(GDALOpenInfo *poOpenInfo,
48✔
979
                                   const std::string &cache,
980
                                   const std::string &url)
981
{
982
    // do we have the capabilities file
983
    std::string filename;
96✔
984
    bool cached;
985
    if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
48✔
986
    {
987
        return nullptr;  // error in cache
×
988
    }
989
    if (!cached)
48✔
990
    {
991
        filename = "XXXXX";
24✔
992
        if (AddEntryToCache(cache, url, filename, ".xml") != CE_None)
24✔
993
        {
994
            return nullptr;  // error in cache
×
995
        }
996
        if (!FetchCapabilities(poOpenInfo, url, filename))
24✔
997
        {
998
            DeleteEntryFromCache(cache, "", url);
×
999
            return nullptr;
×
1000
        }
1001
        return WCSDataset::CreateFromCapabilities(cache, filename, url);
24✔
1002
    }
1003
    std::string metadata = RemoveExt(filename) + ".aux.xml";
48✔
1004
    bool recreate_meta =
1005
        CPLFetchBool(poOpenInfo->papszOpenOptions, "RECREATE_META", false);
24✔
1006
    if (FileIsReadable(metadata) && !recreate_meta)
24✔
1007
    {
1008
        return WCSDataset::CreateFromMetadata(cache, metadata);
24✔
1009
    }
1010
    // we have capabilities but not meta
1011
    return WCSDataset::CreateFromCapabilities(cache, filename, url);
×
1012
}
1013

1014
/************************************************************************/
1015
/*                          CreateService()                             */
1016
/************************************************************************/
1017

1018
static CPLXMLNode *CreateService(const std::string &base_url,
24✔
1019
                                 const std::string &version,
1020
                                 const std::string &coverage,
1021
                                 const std::string &parameters)
1022
{
1023
    // construct WCS_GDAL XML into psService
1024
    std::string xml = "<WCS_GDAL>";
24✔
1025
    xml += "<ServiceURL>" + base_url + "</ServiceURL>";
24✔
1026
    xml += "<Version>" + version + "</Version>";
24✔
1027
    xml += "<CoverageName>" + coverage + "</CoverageName>";
24✔
1028
    xml += "<Parameters>" + parameters + "</Parameters>";
24✔
1029
    xml += "</WCS_GDAL>";
24✔
1030
    CPLXMLNode *psService = CPLParseXMLString(xml.c_str());
24✔
1031
    return psService;
48✔
1032
}
1033

1034
/************************************************************************/
1035
/*                          UpdateService()                             */
1036
/************************************************************************/
1037

1038
#define WCS_SERVICE_OPTIONS                                                    \
1039
    "PreferredFormat", "NoDataValue", "BlockXSize", "BlockYSize",              \
1040
        "OverviewCount", "GetCoverageExtra", "DescribeCoverageExtra",          \
1041
        "Domain", "BandCount", "BandType", "DefaultTime", "CRS"
1042

1043
#define WCS_TWEAK_OPTIONS                                                      \
1044
    "OriginAtBoundary", "OuterExtents", "BufSizeAdjust", "OffsetsPositive",    \
1045
        "NrOffsets", "GridCRSOptional", "NoGridAxisSwap", "GridAxisLabelSwap", \
1046
        "SubsetAxisSwap", "UseScaleFactor", "INTERLEAVE"
1047

1048
static bool UpdateService(CPLXMLNode *service, GDALOpenInfo *poOpenInfo)
72✔
1049
{
1050
    bool updated = false;
72✔
1051
    // descriptions in frmt_wcs.html
1052
    const char *keys[] = {"Subset",
72✔
1053
                          "RangeSubsetting",
1054
                          WCS_URL_PARAMETERS,
1055
                          WCS_SERVICE_OPTIONS,
1056
                          WCS_TWEAK_OPTIONS,
1057
                          WCS_HTTP_OPTIONS
1058
#ifdef DEBUG_WCS
1059
                          ,
1060
                          "filename"
1061
#endif
1062
    };
1063
    for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
2,808✔
1064
    {
1065
        const char *value;
1066
        if (CSLFindString(poOpenInfo->papszOpenOptions, keys[i]) != -1)
2,736✔
1067
        {
1068
            value = "TRUE";
23✔
1069
        }
1070
        else
1071
        {
1072
            value = CSLFetchNameValue(poOpenInfo->papszOpenOptions, keys[i]);
2,713✔
1073
            if (value == nullptr)
2,713✔
1074
            {
1075
                continue;
2,580✔
1076
            }
1077
        }
1078
        updated = CPLUpdateXML(service, keys[i], value) || updated;
156✔
1079
    }
1080
    return updated;
72✔
1081
}
1082

1083
/************************************************************************/
1084
/*                          CreateFromCache()                           */
1085
/************************************************************************/
1086

1087
static WCSDataset *CreateFromCache(const std::string &cache)
×
1088
{
1089
    WCSDataset *ds = new WCSDataset201(cache.c_str());
×
1090
    if (!ds)
×
1091
    {
1092
        return nullptr;
×
1093
    }
1094
    char **metadata = nullptr;
×
1095
    std::vector<std::string> contents = ReadCache(cache);
×
1096
    std::string path = "SUBDATASET_";
×
1097
    unsigned int index = 1;
×
1098
    for (unsigned int i = 0; i < contents.size(); ++i)
×
1099
    {
1100
        std::string name = path + CPLString().Printf("%d_", index) + "NAME";
×
1101
        std::string value = "WCS:" + contents[i];
×
1102
        metadata = CSLSetNameValue(metadata, name.c_str(), value.c_str());
×
1103
        index += 1;
×
1104
    }
1105
    ds->SetMetadata(metadata, "SUBDATASETS");
×
1106
    CSLDestroy(metadata);
×
1107
    return ds;
×
1108
}
1109

1110
/************************************************************************/
1111
/*                              ParseURL()                              */
1112
/************************************************************************/
1113

1114
static void ParseURL(std::string &url, std::string &version,
96✔
1115
                     std::string &coverage, std::string &parameters)
1116
{
1117
    version = CPLURLGetValue(url.c_str(), "version");
96✔
1118
    url = URLRemoveKey(url.c_str(), "version");
96✔
1119
    // the default version, the aim is to have version explicitly in cache keys
1120
    if (WCSParseVersion(version.c_str()) == 0)
96✔
1121
    {
1122
        version = "2.0.1";
×
1123
    }
1124
    coverage = CPLURLGetValue(url.c_str(), "coverageid");  // 2.0
96✔
1125
    if (coverage == "")
96✔
1126
    {
1127
        coverage = CPLURLGetValue(url.c_str(), "identifiers");  // 1.1
96✔
1128
        if (coverage == "")
96✔
1129
        {
1130
            coverage = CPLURLGetValue(url.c_str(), "coverage");  // 1.0
96✔
1131
            url = URLRemoveKey(url.c_str(), "coverage");
96✔
1132
        }
1133
        else
1134
        {
1135
            url = URLRemoveKey(url.c_str(), "identifiers");
×
1136
        }
1137
    }
1138
    else
1139
    {
1140
        url = URLRemoveKey(url.c_str(), "coverageid");
×
1141
    }
1142
    size_t pos = url.find("?");
96✔
1143
    if (pos == std::string::npos)
96✔
1144
    {
1145
        url += "?";
×
1146
        return;
×
1147
    }
1148
    parameters = url.substr(pos + 1, std::string::npos);
96✔
1149
    url.erase(pos + 1, std::string::npos);
96✔
1150
}
1151

1152
/************************************************************************/
1153
/*                                Open()                                */
1154
/************************************************************************/
1155

1156
GDALDataset *WCSDataset::Open(GDALOpenInfo *poOpenInfo)
97✔
1157

1158
{
1159
    if (!WCSDriverIdentify(poOpenInfo))
97✔
1160
    {
1161
        return nullptr;
×
1162
    }
1163

1164
    std::string cache =
1165
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "");
194✔
1166
    if (!SetupCache(cache, CPLFetchBool(poOpenInfo->papszOpenOptions,
97✔
1167
                                        "CLEAR_CACHE", false)))
1168
    {
1169
        return nullptr;
×
1170
    }
1171
    CPLXMLNode *service = nullptr;
97✔
1172
    char **papszModifiers = nullptr;
97✔
1173

1174
    if (poOpenInfo->nHeaderBytes == 0 &&
97✔
1175
        STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "WCS:"))
96✔
1176
    {
1177
        /* --------------------------------------------------------------------
1178
         */
1179
        /*      Filename is WCS:URL */
1180
        /* --------------------------------------------------------------------
1181
         */
1182
        std::string url = (const char *)(poOpenInfo->pszFilename + 4);
96✔
1183

1184
        const char *del = CSLFetchNameValue(poOpenInfo->papszOpenOptions,
96✔
1185
                                            "DELETE_FROM_CACHE");
1186
        if (del != nullptr)
96✔
1187
        {
1188
            int k = atoi(del);
×
1189
            std::vector<std::string> contents = ReadCache(cache);
×
1190
            if (k > 0 && k <= (int)contents.size())
×
1191
            {
1192
                DeleteEntryFromCache(cache, "", contents[k - 1]);
×
1193
            }
1194
        }
1195

1196
        if (url == "")
96✔
1197
        {
1198
            return CreateFromCache(cache);
×
1199
        }
1200

1201
        if (CPLFetchBool(poOpenInfo->papszOpenOptions, "REFRESH_CACHE", false))
96✔
1202
        {
1203
            DeleteEntryFromCache(cache, "", url);
×
1204
        }
1205

1206
        // the cache:
1207
        // db = key=URL database
1208
        // key.xml = service file
1209
        // key.xml.aux.xml = metadata file
1210
        // key.xml = Capabilities response
1211
        // key.aux.xml = Global metadata
1212
        // key.DC.xml = DescribeCoverage response
1213

1214
        std::string filename;
96✔
1215
        bool cached;
1216
        if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
96✔
1217
        {
1218
            return nullptr;  // error in cache
×
1219
        }
1220

1221
        std::string full_url = url, version, coverage, parameters;
96✔
1222
        ParseURL(url, version, coverage, parameters);
96✔
1223

1224
        // The goal is to get the service XML and a filename for it
1225

1226
        bool updated = false;
96✔
1227
        if (cached)
96✔
1228
        {
1229
            /* --------------------------------------------------------------------
1230
             */
1231
            /*          The fast route, service file is in cache. */
1232
            /* --------------------------------------------------------------------
1233
             */
1234
            if (coverage == "")
48✔
1235
            {
1236
                std::string url2 =
1237
                    CPLURLAddKVP(url.c_str(), "version", version.c_str());
×
1238
                WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
×
1239
                return global;
×
1240
            }
1241
            service = CPLParseXMLFile(filename.c_str());
48✔
1242
        }
1243
        else
1244
        {
1245
            /* --------------------------------------------------------------------
1246
             */
1247
            /*          Get capabilities. */
1248
            /* --------------------------------------------------------------------
1249
             */
1250
            std::string url2 =
1251
                CPLURLAddKVP(url.c_str(), "version", version.c_str());
48✔
1252
            if (parameters != "")
48✔
1253
            {
1254
                url2 += "&" + parameters;
48✔
1255
            }
1256
            WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
48✔
1257
            if (!global)
48✔
1258
            {
1259
                return nullptr;
×
1260
            }
1261
            if (coverage == "")
48✔
1262
            {
1263
                return global;
24✔
1264
            }
1265
            if (version == "")
24✔
1266
            {
1267
                version = global->Version();
×
1268
            }
1269
            service = CreateService(url, version, coverage, parameters);
24✔
1270
            /* --------------------------------------------------------------------
1271
             */
1272
            /*          The filename for the new service file. */
1273
            /* --------------------------------------------------------------------
1274
             */
1275
            filename = "XXXXX";
24✔
1276
            if (AddEntryToCache(cache, full_url, filename, ".xml") != CE_None)
24✔
1277
            {
1278
                return nullptr;  // error in cache
×
1279
            }
1280
            // Create basic service metadata
1281
            // copy global metadata (not SUBDATASETS metadata)
1282
            std::string global_base = std::string(global->GetDescription());
48✔
1283
            std::string global_meta = global_base + ".aux.xml";
48✔
1284
            std::string capabilities = global_base + ".xml";
48✔
1285
            CPLXMLTreeCloser doc(CPLParseXMLFile(global_meta.c_str()));
48✔
1286
            CPLXMLNode *metadata = doc.getDocumentElement();
24✔
1287
            CPLXMLNode *domain =
1288
                SearchChildWithValue(metadata, "domain", "SUBDATASETS");
24✔
1289
            if (domain != nullptr)
24✔
1290
            {
1291
                CPLRemoveXMLChild(metadata, domain);
24✔
1292
                CPLDestroyXMLNode(domain);
24✔
1293
            }
1294
            // get metadata for this coverage from the capabilities XML
1295
            CPLXMLTreeCloser doc2(CPLParseXMLFile(capabilities.c_str()));
48✔
1296
            global->ParseCoverageCapabilities(doc2.getDocumentElement(),
24✔
1297
                                              coverage, metadata->psChild);
24✔
1298
            delete global;
24✔
1299
            std::string metadata_filename = filename + ".aux.xml";
24✔
1300
            CPLSerializeXMLTreeToFile(metadata, metadata_filename.c_str());
24✔
1301
            updated = true;
24✔
1302
        }
1303
        CPLFree(poOpenInfo->pszFilename);
72✔
1304
        poOpenInfo->pszFilename = CPLStrdup(filename.c_str());
72✔
1305
        updated = UpdateService(service, poOpenInfo) || updated;
72✔
1306
        if (updated || !cached)
72✔
1307
        {
1308
            CPLSerializeXMLTreeToFile(service, filename.c_str());
72✔
1309
        }
72✔
1310
    }
1311
    /* -------------------------------------------------------------------- */
1312
    /*      Is this a WCS_GDAL service description file or "in url"         */
1313
    /*      equivalent?                                                     */
1314
    /* -------------------------------------------------------------------- */
1315
    else if (poOpenInfo->nHeaderBytes == 0 &&
1✔
1316
             STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
×
1317
                            "<WCS_GDAL>"))
1318
    {
1319
        service = CPLParseXMLString(poOpenInfo->pszFilename);
×
1320
    }
1321
    else if (poOpenInfo->nHeaderBytes >= 10 &&
1✔
1322
             STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "<WCS_GDAL>"))
1✔
1323
    {
1324
        service = CPLParseXMLFile(poOpenInfo->pszFilename);
1✔
1325
    }
1326
    /* -------------------------------------------------------------------- */
1327
    /*      Is this apparently a subdataset?                                */
1328
    /* -------------------------------------------------------------------- */
1329
    else if (STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
×
1330
                            "WCS_SDS:") &&
×
1331
             poOpenInfo->nHeaderBytes == 0)
×
1332
    {
1333
        int iLast;
1334

1335
        papszModifiers = CSLTokenizeString2(poOpenInfo->pszFilename + 8, ",",
×
1336
                                            CSLT_HONOURSTRINGS);
1337

1338
        iLast = CSLCount(papszModifiers) - 1;
×
1339
        if (iLast >= 0)
×
1340
        {
1341
            service = CPLParseXMLFile(papszModifiers[iLast]);
×
1342
            CPLFree(papszModifiers[iLast]);
×
1343
            papszModifiers[iLast] = nullptr;
×
1344
        }
1345
    }
1346

1347
    /* -------------------------------------------------------------------- */
1348
    /*      Success so far?                                                 */
1349
    /* -------------------------------------------------------------------- */
1350
    if (service == nullptr)
73✔
1351
    {
1352
        CSLDestroy(papszModifiers);
×
1353
        return nullptr;
×
1354
    }
1355

1356
    /* -------------------------------------------------------------------- */
1357
    /*      Confirm the requested access is supported.                      */
1358
    /* -------------------------------------------------------------------- */
1359
    if (poOpenInfo->eAccess == GA_Update)
73✔
1360
    {
1361
        CSLDestroy(papszModifiers);
×
1362
        CPLDestroyXMLNode(service);
×
1363
        ReportUpdateNotSupportedByDriver("WCS");
×
1364
        return nullptr;
×
1365
    }
1366

1367
    /* -------------------------------------------------------------------- */
1368
    /*      Check for required minimum fields.                              */
1369
    /* -------------------------------------------------------------------- */
1370
    if (!CPLGetXMLValue(service, "ServiceURL", nullptr) ||
146✔
1371
        !CPLGetXMLValue(service, "CoverageName", nullptr))
73✔
1372
    {
1373
        CSLDestroy(papszModifiers);
×
1374
        CPLError(
×
1375
            CE_Failure, CPLE_OpenFailed,
1376
            "Missing one or both of ServiceURL and CoverageName elements.\n"
1377
            "See WCS driver documentation for details on service description "
1378
            "file format.");
1379

1380
        CPLDestroyXMLNode(service);
×
1381
        return nullptr;
×
1382
    }
1383

1384
    /* -------------------------------------------------------------------- */
1385
    /*      What version are we working with?                               */
1386
    /* -------------------------------------------------------------------- */
1387
    const char *pszVersion = CPLGetXMLValue(service, "Version", "1.0.0");
73✔
1388

1389
    int nVersion = WCSParseVersion(pszVersion);
73✔
1390

1391
    if (nVersion == 0)
73✔
1392
    {
1393
        CSLDestroy(papszModifiers);
×
1394
        CPLDestroyXMLNode(service);
×
1395
        return nullptr;
×
1396
    }
1397

1398
    /* -------------------------------------------------------------------- */
1399
    /*      Create a corresponding GDALDataset.                             */
1400
    /* -------------------------------------------------------------------- */
1401
    WCSDataset *poDS;
1402
    if (nVersion == 201)
73✔
1403
    {
1404
        poDS = new WCSDataset201(cache.c_str());
21✔
1405
    }
1406
    else if (nVersion / 10 == 11)
52✔
1407
    {
1408
        poDS = new WCSDataset110(nVersion, cache.c_str());
36✔
1409
    }
1410
    else
1411
    {
1412
        poDS = new WCSDataset100(cache.c_str());
16✔
1413
    }
1414

1415
    poDS->psService = service;
73✔
1416
    poDS->SetDescription(poOpenInfo->pszFilename);
73✔
1417
    poDS->papszSDSModifiers = papszModifiers;
73✔
1418
    // WCS:URL => basic metadata was already made
1419
    // Metadata is needed in ExtractGridInfo
1420
    poDS->TryLoadXML();
73✔
1421

1422
    /* -------------------------------------------------------------------- */
1423
    /*      Capture HTTP parameters.                                        */
1424
    /* -------------------------------------------------------------------- */
1425
    const char *pszParam;
1426

1427
    poDS->papszHttpOptions =
73✔
1428
        CSLSetNameValue(poDS->papszHttpOptions, "TIMEOUT",
73✔
1429
                        CPLGetXMLValue(service, "Timeout", "30"));
1430

1431
    pszParam = CPLGetXMLValue(service, "HTTPAUTH", nullptr);
73✔
1432
    if (pszParam)
73✔
1433
        poDS->papszHttpOptions =
×
1434
            CSLSetNameValue(poDS->papszHttpOptions, "HTTPAUTH", pszParam);
×
1435

1436
    pszParam = CPLGetXMLValue(service, "USERPWD", nullptr);
73✔
1437
    if (pszParam)
73✔
1438
        poDS->papszHttpOptions =
×
1439
            CSLSetNameValue(poDS->papszHttpOptions, "USERPWD", pszParam);
×
1440

1441
    /* -------------------------------------------------------------------- */
1442
    /*      If we don't have the DescribeCoverage result for this           */
1443
    /*      coverage, fetch it now.                                         */
1444
    /* -------------------------------------------------------------------- */
1445
    if (CPLGetXMLNode(service, "CoverageOffering") == nullptr &&
136✔
1446
        CPLGetXMLNode(service, "CoverageDescription") == nullptr)
63✔
1447
    {
1448
        if (!poDS->DescribeCoverage())
25✔
1449
        {
1450
            delete poDS;
1✔
1451
            return nullptr;
1✔
1452
        }
1453
    }
1454

1455
    /* -------------------------------------------------------------------- */
1456
    /*      Extract coordinate system, grid size, and geotransform from     */
1457
    /*      the coverage description and/or service description             */
1458
    /*      information.                                                    */
1459
    /* -------------------------------------------------------------------- */
1460
    if (!poDS->ExtractGridInfo())
72✔
1461
    {
1462
        delete poDS;
×
1463
        return nullptr;
×
1464
    }
1465

1466
    /* -------------------------------------------------------------------- */
1467
    /*      Leave now or there may be a GetCoverage call.                   */
1468
    /*                                                                      */
1469
    /* -------------------------------------------------------------------- */
1470
    int nBandCount = -1;
72✔
1471
    std::string sBandCount = CPLGetXMLValue(service, "BandCount", "");
144✔
1472
    if (sBandCount != "")
72✔
1473
    {
1474
        nBandCount = atoi(sBandCount.c_str());
61✔
1475
    }
1476
    if (CPLFetchBool(poOpenInfo->papszOpenOptions, "SKIP_GETCOVERAGE", false) ||
72✔
1477
        nBandCount == 0)
1478
    {
1479
        return poDS;
×
1480
    }
1481

1482
    /* -------------------------------------------------------------------- */
1483
    /*      Extract band count and type from a sample.                      */
1484
    /* -------------------------------------------------------------------- */
1485
    if (!poDS->EstablishRasterDetails())  // todo: do this only if missing info
72✔
1486
    {
1487
        delete poDS;
×
1488
        return nullptr;
×
1489
    }
1490

1491
    /* -------------------------------------------------------------------- */
1492
    /*      It is ok to not have bands. The user just needs to supply       */
1493
    /*      more information.                                               */
1494
    /* -------------------------------------------------------------------- */
1495
    nBandCount = atoi(CPLGetXMLValue(service, "BandCount", "0"));
72✔
1496
    if (nBandCount == 0)
72✔
1497
    {
1498
        return poDS;
×
1499
    }
1500

1501
    /* -------------------------------------------------------------------- */
1502
    /*      Create band information objects.                                */
1503
    /* -------------------------------------------------------------------- */
1504
    int iBand;
1505

1506
    if (!GDALCheckBandCount(nBandCount, FALSE))
72✔
1507
    {
1508
        delete poDS;
×
1509
        return nullptr;
×
1510
    }
1511

1512
    for (iBand = 0; iBand < nBandCount; iBand++)
207✔
1513
    {
1514
        WCSRasterBand *band = new WCSRasterBand(poDS, iBand + 1, -1);
135✔
1515
        // copy band specific metadata to the band
1516
        char **md_from = poDS->GetMetadata("");
135✔
1517
        char **md_to = nullptr;
135✔
1518
        if (md_from)
135✔
1519
        {
1520
            std::string our_key = CPLString().Printf("FIELD_%d_", iBand + 1);
270✔
1521
            for (char **from = md_from; *from != nullptr; ++from)
3,192✔
1522
            {
1523
                std::vector<std::string> kv = Split(*from, "=");
6,114✔
1524
                if (kv.size() > 1 &&
6,114✔
1525
                    STARTS_WITH(kv[0].c_str(), our_key.c_str()))
3,057✔
1526
                {
1527
                    std::string key = kv[0];
174✔
1528
                    std::string value = kv[1];
87✔
1529
                    key.erase(0, our_key.length());
87✔
1530
                    md_to = CSLSetNameValue(md_to, key.c_str(), value.c_str());
87✔
1531
                }
1532
            }
1533
        }
1534
        band->SetMetadata(md_to, "");
135✔
1535
        CSLDestroy(md_to);
135✔
1536
        poDS->SetBand(iBand + 1, band);
135✔
1537
    }
1538

1539
    /* -------------------------------------------------------------------- */
1540
    /*      Set time metadata on the dataset if we are selecting a          */
1541
    /*      temporal slice.                                                 */
1542
    /* -------------------------------------------------------------------- */
1543
    std::string osTime = CSLFetchNameValueDef(poDS->papszSDSModifiers, "time",
72✔
1544
                                              poDS->osDefaultTime.c_str());
72✔
1545

1546
    if (osTime != "")
72✔
1547
        poDS->GDALMajorObject::SetMetadataItem("TIME_POSITION", osTime.c_str());
×
1548

1549
    /* -------------------------------------------------------------------- */
1550
    /*      Do we have a band identifier to select only a subset of bands?  */
1551
    /* -------------------------------------------------------------------- */
1552
    poDS->osBandIdentifier = CPLGetXMLValue(service, "BandIdentifier", "");
72✔
1553

1554
    /* -------------------------------------------------------------------- */
1555
    /*      Do we have time based subdatasets?  If so, record them in       */
1556
    /*      metadata.  Note we don't do subdatasets if this is a            */
1557
    /*      subdataset or if this is an all-in-memory service.              */
1558
    /* -------------------------------------------------------------------- */
1559
    if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WCS_SDS:") &&
216✔
1560
        !STARTS_WITH_CI(poOpenInfo->pszFilename, "<WCS_GDAL>") &&
144✔
1561
        !poDS->aosTimePositions.empty())
72✔
1562
    {
1563
        char **papszSubdatasets = nullptr;
×
1564
        int iTime;
1565

1566
        for (iTime = 0; iTime < (int)poDS->aosTimePositions.size(); iTime++)
×
1567
        {
1568
            std::string osName;
×
1569
            std::string osValue;
×
1570

1571
            osName = CPLString().Printf("SUBDATASET_%d_NAME", iTime + 1);
×
1572
            osValue = CPLString().Printf("WCS_SDS:time=\"%s\",%s",
×
1573
                                         poDS->aosTimePositions[iTime].c_str(),
×
1574
                                         poOpenInfo->pszFilename);
×
1575
            papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
×
1576
                                               osValue.c_str());
1577

1578
            std::string osCoverage =
1579
                CPLGetXMLValue(poDS->psService, "CoverageName", "");
×
1580

1581
            osName = CPLString().Printf("SUBDATASET_%d_DESC", iTime + 1);
×
1582
            osValue =
1583
                CPLString().Printf("Coverage %s at time %s", osCoverage.c_str(),
×
1584
                                   poDS->aosTimePositions[iTime].c_str());
×
1585
            papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
×
1586
                                               osValue.c_str());
1587
        }
1588

1589
        poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS");
×
1590

1591
        CSLDestroy(papszSubdatasets);
×
1592
    }
1593

1594
    /* -------------------------------------------------------------------- */
1595
    /*      Initialize any PAM information.                                 */
1596
    /* -------------------------------------------------------------------- */
1597
    poDS->TryLoadXML();
72✔
1598
    return poDS;
72✔
1599
}
1600

1601
/************************************************************************/
1602
/*                          GetGeoTransform()                           */
1603
/************************************************************************/
1604

1605
CPLErr WCSDataset::GetGeoTransform(GDALGeoTransform &gt) const
72✔
1606

1607
{
1608
    gt = m_gt;
72✔
1609
    return CE_None;
72✔
1610
}
1611

1612
/************************************************************************/
1613
/*                          GetSpatialRef()                             */
1614
/************************************************************************/
1615

1616
const OGRSpatialReference *WCSDataset::GetSpatialRef() const
48✔
1617

1618
{
1619
    const auto poSRS = GDALPamDataset::GetSpatialRef();
48✔
1620
    if (poSRS)
48✔
1621
        return poSRS;
×
1622

1623
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
48✔
1624
}
1625

1626
/************************************************************************/
1627
/*                            GetFileList()                             */
1628
/************************************************************************/
1629

1630
char **WCSDataset::GetFileList()
×
1631

1632
{
1633
    char **papszFileList = GDALPamDataset::GetFileList();
×
1634

1635
/* -------------------------------------------------------------------- */
1636
/*      ESRI also wishes to include service urls in the file list       */
1637
/*      though this is not currently part of the general definition     */
1638
/*      of GetFileList() for GDAL.                                      */
1639
/* -------------------------------------------------------------------- */
1640
#ifdef ESRI_BUILD
1641
    std::string file;
1642
    file.Printf("%s%s", CPLGetXMLValue(psService, "ServiceURL", ""),
1643
                CPLGetXMLValue(psService, "CoverageName", ""));
1644
    papszFileList = CSLAddString(papszFileList, file.c_str());
1645
#endif /* def ESRI_BUILD */
1646

1647
    return papszFileList;
×
1648
}
1649

1650
/************************************************************************/
1651
/*                      GetMetadataDomainList()                         */
1652
/************************************************************************/
1653

1654
char **WCSDataset::GetMetadataDomainList()
×
1655
{
1656
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
×
1657
                                   TRUE, "xml:CoverageOffering", nullptr);
×
1658
}
1659

1660
/************************************************************************/
1661
/*                            GetMetadata()                             */
1662
/************************************************************************/
1663

1664
char **WCSDataset::GetMetadata(const char *pszDomain)
348✔
1665

1666
{
1667
    if (pszDomain == nullptr || !EQUAL(pszDomain, "xml:CoverageOffering"))
348✔
1668
        return GDALPamDataset::GetMetadata(pszDomain);
348✔
1669

1670
    CPLXMLNode *psNode = CPLGetXMLNode(psService, "CoverageOffering");
×
1671

1672
    if (psNode == nullptr)
×
1673
        psNode = CPLGetXMLNode(psService, "CoverageDescription");
×
1674

1675
    if (psNode == nullptr)
×
1676
        return nullptr;
×
1677

1678
    if (apszCoverageOfferingMD[0] == nullptr)
×
1679
    {
1680
        CPLXMLNode *psNext = psNode->psNext;
×
1681
        psNode->psNext = nullptr;
×
1682

1683
        apszCoverageOfferingMD[0] = CPLSerializeXMLTree(psNode);
×
1684

1685
        psNode->psNext = psNext;
×
1686
    }
1687

1688
    return apszCoverageOfferingMD;
×
1689
}
1690

1691
/************************************************************************/
1692
/*                          GDALRegister_WCS()                          */
1693
/************************************************************************/
1694

1695
void GDALRegister_WCS()
1,911✔
1696

1697
{
1698
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1,911✔
1699
        return;
282✔
1700

1701
    GDALDriver *poDriver = new GDALDriver();
1,629✔
1702
    WCSDriverSetCommonMetadata(poDriver);
1,629✔
1703

1704
    poDriver->pfnOpen = WCSDataset::Open;
1,629✔
1705

1706
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,629✔
1707
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc