Coverage Report

Created: 2025-12-31 08:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/wcs/wcsdataset.cpp
Line
Count
Source
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)
37
238
    : m_cache_dir(cache_dir), bServiceDirty(false), psService(nullptr),
38
238
      papszSDSModifiers(nullptr), m_Version(version), native_crs(true),
39
238
      axis_order_swap(false), pabySavedDataBuffer(nullptr),
40
238
      papszHttpOptions(nullptr), nMaxCols(-1), nMaxRows(-1)
41
238
{
42
238
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
43
44
238
    apszCoverageOfferingMD[0] = nullptr;
45
238
    apszCoverageOfferingMD[1] = nullptr;
46
238
}
47
48
/************************************************************************/
49
/*                            ~WCSDataset()                             */
50
/************************************************************************/
51
52
WCSDataset::~WCSDataset()
53
54
238
{
55
    // perhaps this should be moved into a FlushCache(bool bAtClosing) method.
56
238
    if (bServiceDirty && !STARTS_WITH_CI(GetDescription(), "<WCS_GDAL>"))
57
0
    {
58
0
        CPLSerializeXMLTreeToFile(psService, GetDescription());
59
0
        bServiceDirty = false;
60
0
    }
61
62
238
    CPLDestroyXMLNode(psService);
63
64
238
    CSLDestroy(papszHttpOptions);
65
238
    CSLDestroy(papszSDSModifiers);
66
67
238
    CPLFree(apszCoverageOfferingMD[0]);
68
69
238
    FlushMemoryResult();
70
238
}
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)
81
0
{
82
0
    osCRS = crs;
83
0
    char *pszProjection = nullptr;
84
0
    if (!CRSImpliesAxisOrderSwap(osCRS, axis_order_swap, &pszProjection))
85
0
    {
86
0
        return false;
87
0
    }
88
0
    m_oSRS.importFromWkt(pszProjection);
89
0
    CPLFree(pszProjection);
90
0
    native_crs = native;
91
0
    return true;
92
0
}
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,
102
                             const std::vector<double> &origin,
103
                             const std::vector<std::vector<double>> &offsets)
104
0
{
105
    // note that this method is not used by wcsdataset100.cpp
106
0
    nRasterXSize = size[0];
107
0
    nRasterYSize = size[1];
108
109
0
    m_gt[0] = origin[0];
110
0
    m_gt[1] = offsets[0][0];
111
0
    m_gt[2] = offsets[0].size() == 1 ? 0.0 : offsets[0][1];
112
0
    m_gt[3] = origin[1];
113
0
    m_gt[4] = offsets[1].size() == 1 ? 0.0 : offsets[1][0];
114
0
    m_gt[5] = offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
115
116
0
    if (!CPLGetXMLBoolean(psService, "OriginAtBoundary"))
117
0
    {
118
0
        m_gt[0] -= m_gt[1] * 0.5;
119
0
        m_gt[0] -= m_gt[2] * 0.5;
120
0
        m_gt[3] -= m_gt[4] * 0.5;
121
0
        m_gt[3] -= m_gt[5] * 0.5;
122
0
    }
123
0
}
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,
133
                               int nXSize, int nYSize, int nBufXSize,
134
                               int nBufYSize) const
135
0
{
136
0
    int bUseBlockedIO = bForceCachedIO;
137
138
0
    if (nYSize == 1 || nXSize * ((double)nYSize) < 100.0)
139
0
        bUseBlockedIO = TRUE;
140
141
0
    if (nBufYSize == 1 || nBufXSize * ((double)nBufYSize) < 100.0)
142
0
        bUseBlockedIO = TRUE;
143
144
0
    if (bUseBlockedIO &&
145
0
        CPLTestBool(CPLGetConfigOption("GDAL_ONE_BIG_READ", "NO")))
146
0
        bUseBlockedIO = FALSE;
147
148
0
    return bUseBlockedIO;
149
0
}
150
151
/************************************************************************/
152
/*                             IRasterIO()                              */
153
/************************************************************************/
154
155
CPLErr WCSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
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
0
{
164
0
    if ((nMaxCols > 0 && nMaxCols < nBufXSize) ||
165
0
        (nMaxRows > 0 && nMaxRows < nBufYSize))
166
0
        return CE_Failure;
167
168
    /* -------------------------------------------------------------------- */
169
    /*      We need various criteria to skip out to block based methods.    */
170
    /* -------------------------------------------------------------------- */
171
0
    if (TestUseBlockIO(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize))
172
0
        return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
173
0
                                         pData, nBufXSize, nBufYSize, eBufType,
174
0
                                         nBandCount, panBandMap, nPixelSpace,
175
0
                                         nLineSpace, nBandSpace, psExtraArg);
176
0
    else
177
0
        return DirectRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
178
0
                              nBufXSize, nBufYSize, eBufType, nBandCount,
179
0
                              panBandMap, nPixelSpace, nLineSpace, nBandSpace,
180
0
                              psExtraArg);
181
0
}
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,
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
0
{
197
0
    CPLDebug("WCS", "DirectRasterIO(%d,%d,%d,%d) -> (%d,%d) (%d bands)\n",
198
0
             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
0
    int band_count = nBandCount;
210
0
    if (EQUAL(CPLGetXMLValue(psService, "INTERLEAVE", ""), "PIXEL"))
211
0
    {
212
0
        band_count = 0;
213
0
    }
214
215
0
    CPLHTTPResult *psResult = nullptr;
216
0
    CPLErr eErr =
217
0
        GetCoverage(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
218
0
                    band_count, panBandMap, psExtraArg, &psResult);
219
220
0
    if (eErr != CE_None)
221
0
        return eErr;
222
223
    /* -------------------------------------------------------------------- */
224
    /*      Try and open result as a dataset.                               */
225
    /* -------------------------------------------------------------------- */
226
0
    GDALDataset *poTileDS = GDALOpenResult(psResult);
227
228
0
    if (poTileDS == nullptr)
229
0
        return CE_Failure;
230
231
    /* -------------------------------------------------------------------- */
232
    /*      Verify configuration.                                           */
233
    /* -------------------------------------------------------------------- */
234
0
    if (poTileDS->GetRasterXSize() != nBufXSize ||
235
0
        poTileDS->GetRasterYSize() != nBufYSize)
236
0
    {
237
0
        CPLError(CE_Failure, CPLE_AppDefined,
238
0
                 "Returned tile does not match expected configuration.\n"
239
0
                 "Got %dx%d instead of %dx%d.",
240
0
                 poTileDS->GetRasterXSize(), poTileDS->GetRasterYSize(),
241
0
                 nBufXSize, nBufYSize);
242
0
        delete poTileDS;
243
0
        return CE_Failure;
244
0
    }
245
246
0
    if (band_count != 0 && ((!osBandIdentifier.empty() &&
247
0
                             poTileDS->GetRasterCount() != nBandCount) ||
248
0
                            (osBandIdentifier.empty() &&
249
0
                             poTileDS->GetRasterCount() != GetRasterCount())))
250
0
    {
251
0
        CPLError(CE_Failure, CPLE_AppDefined,
252
0
                 "Returned tile does not match expected band count.");
253
0
        delete poTileDS;
254
0
        return CE_Failure;
255
0
    }
256
257
    /* -------------------------------------------------------------------- */
258
    /*      Pull requested bands from the downloaded dataset.               */
259
    /* -------------------------------------------------------------------- */
260
0
    eErr = CE_None;
261
262
0
    for (int iBand = 0; iBand < nBandCount && eErr == CE_None; iBand++)
263
0
    {
264
0
        GDALRasterBand *poTileBand = nullptr;
265
266
0
        if (!osBandIdentifier.empty())
267
0
            poTileBand = poTileDS->GetRasterBand(iBand + 1);
268
0
        else
269
0
            poTileBand = poTileDS->GetRasterBand(panBandMap[iBand]);
270
271
0
        eErr = poTileBand->RasterIO(GF_Read, 0, 0, nBufXSize, nBufYSize,
272
0
                                    ((GByte *)pData) + iBand * nBandSpace,
273
0
                                    nBufXSize, nBufYSize, eBufType, nPixelSpace,
274
0
                                    nLineSpace, nullptr);
275
0
    }
276
277
    /* -------------------------------------------------------------------- */
278
    /*      Cleanup                                                         */
279
    /* -------------------------------------------------------------------- */
280
0
    delete poTileDS;
281
282
0
    FlushMemoryResult();
283
284
0
    return eErr;
285
0
}
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,
297
                               int nBufXSize, int nBufYSize, int nBandCount,
298
                               const int *panBandList,
299
                               GDALRasterIOExtraArg *psExtraArg,
300
                               CPLHTTPResult **ppsResult)
301
302
0
{
303
    /* -------------------------------------------------------------------- */
304
    /*      Figure out the georeferenced extents.                           */
305
    /* -------------------------------------------------------------------- */
306
0
    std::vector<double> extent =
307
0
        GetNativeExtent(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
308
309
    /* -------------------------------------------------------------------- */
310
    /*      Build band list if we have the band identifier.                 */
311
    /* -------------------------------------------------------------------- */
312
0
    std::string osBandList;
313
314
0
    if (!osBandIdentifier.empty() && nBandCount > 0 && panBandList != nullptr)
315
0
    {
316
0
        int iBand;
317
318
0
        for (iBand = 0; iBand < nBandCount; iBand++)
319
0
        {
320
0
            if (iBand > 0)
321
0
                osBandList += ",";
322
0
            osBandList += CPLString().Printf("%d", panBandList[iBand]);
323
0
        }
324
0
    }
325
326
    /* -------------------------------------------------------------------- */
327
    /*      Construct a KVP GetCoverage request.                            */
328
    /* -------------------------------------------------------------------- */
329
0
    bool scaled = nBufXSize != nXSize || nBufYSize != nYSize;
330
0
    std::string osRequest =
331
0
        GetCoverageRequest(scaled, nBufXSize, nBufYSize, extent, osBandList);
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
0
    CPLErrorReset();
339
0
    if (psExtraArg && psExtraArg->pfnProgress != nullptr)
340
0
    {
341
0
        *ppsResult = CPLHTTPFetchEx(
342
0
            osRequest.c_str(), papszHttpOptions, psExtraArg->pfnProgress,
343
0
            psExtraArg->pProgressData, nullptr, nullptr);
344
0
    }
345
0
    else
346
0
    {
347
0
        *ppsResult = CPLHTTPFetch(osRequest.c_str(), papszHttpOptions);
348
0
    }
349
350
0
    if (ProcessError(*ppsResult))
351
0
        return CE_Failure;
352
0
    else
353
0
        return CE_None;
354
0
}
355
356
/************************************************************************/
357
/*                          DescribeCoverage()                          */
358
/*                                                                      */
359
/*      Fetch the DescribeCoverage result and attach it to the          */
360
/*      service description.                                            */
361
/************************************************************************/
362
363
int WCSDataset::DescribeCoverage()
364
365
0
{
366
0
    std::string osRequest;
367
368
    /* -------------------------------------------------------------------- */
369
    /*      Fetch coverage description for this coverage.                   */
370
    /* -------------------------------------------------------------------- */
371
372
0
    CPLXMLNode *psDC = nullptr;
373
374
    // if it is in cache, get it from there
375
0
    std::string dc_filename =
376
0
        this->GetDescription();  // the WCS_GDAL file (<basename>.xml)
377
0
    dc_filename.erase(dc_filename.length() - 4, 4);
378
0
    dc_filename += ".DC.xml";
379
0
    if (FileIsReadable(dc_filename))
380
0
    {
381
0
        psDC = CPLParseXMLFile(dc_filename.c_str());
382
0
    }
383
384
0
    if (!psDC)
385
0
    {
386
0
        osRequest = DescribeCoverageRequest();
387
0
        CPLErrorReset();
388
0
        CPLHTTPResult *psResult =
389
0
            CPLHTTPFetch(osRequest.c_str(), papszHttpOptions);
390
0
        if (ProcessError(psResult))
391
0
        {
392
0
            return FALSE;
393
0
        }
394
395
        /* --------------------------------------------------------------------
396
         */
397
        /*      Parse result. */
398
        /* --------------------------------------------------------------------
399
         */
400
0
        psDC = CPLParseXMLString((const char *)psResult->pabyData);
401
0
        CPLHTTPDestroyResult(psResult);
402
403
0
        if (psDC == nullptr)
404
0
        {
405
0
            return FALSE;
406
0
        }
407
408
        // if we have cache, put it there
409
0
        if (dc_filename != "")
410
0
        {
411
0
            CPLSerializeXMLTreeToFile(psDC, dc_filename.c_str());
412
0
        }
413
0
    }
414
415
0
    CPLStripXMLNamespace(psDC, nullptr, TRUE);
416
417
    /* -------------------------------------------------------------------- */
418
    /*      Did we get a CoverageOffering?                                  */
419
    /* -------------------------------------------------------------------- */
420
0
    CPLXMLNode *psCO = CoverageOffering(psDC);
421
422
0
    if (!psCO)
423
0
    {
424
0
        CPLDestroyXMLNode(psDC);
425
426
0
        CPLError(CE_Failure, CPLE_AppDefined,
427
0
                 "Failed to fetch a <CoverageOffering> back %s.",
428
0
                 osRequest.c_str());
429
0
        return FALSE;
430
0
    }
431
432
    /* -------------------------------------------------------------------- */
433
    /*      Duplicate the coverage offering, and insert into                */
434
    /* -------------------------------------------------------------------- */
435
0
    CPLXMLNode *psNext = psCO->psNext;
436
0
    psCO->psNext = nullptr;
437
438
0
    CPLAddXMLChild(psService, CPLCloneXMLTree(psCO));
439
0
    bServiceDirty = true;
440
441
0
    psCO->psNext = psNext;
442
443
0
    CPLDestroyXMLNode(psDC);
444
0
    return TRUE;
445
0
}
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)
456
457
166
{
458
    /* -------------------------------------------------------------------- */
459
    /*      There isn't much we can do in this case.  Hopefully an error    */
460
    /*      was already issued by CPLHTTPFetch()                            */
461
    /* -------------------------------------------------------------------- */
462
166
    if (psResult == nullptr || psResult->nDataLen == 0)
463
166
    {
464
166
        CPLHTTPDestroyResult(psResult);
465
166
        return TRUE;
466
166
    }
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
0
    if (psResult->pszContentType != nullptr &&
474
0
        strstr(psResult->pszContentType, "html") != nullptr)
475
0
    {
476
0
        std::string osErrorMsg = (char *)psResult->pabyData;
477
478
0
        if (osErrorMsg.size() > 2048)
479
0
            osErrorMsg.resize(2048);
480
481
0
        CPLError(CE_Failure, CPLE_AppDefined, "Malformed Result:\n%s",
482
0
                 osErrorMsg.c_str());
483
0
        CPLHTTPDestroyResult(psResult);
484
0
        return TRUE;
485
0
    }
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
0
    if (strstr((const char *)psResult->pabyData, "ExceptionReport"))
493
0
    {
494
0
        CPLXMLNode *psTree =
495
0
            CPLParseXMLString((const char *)psResult->pabyData);
496
0
        CPLStripXMLNamespace(psTree, nullptr, TRUE);
497
0
        std::string msg = CPLGetXMLValue(
498
0
            psTree, "=ServiceExceptionReport.ServiceException", "");
499
0
        if (msg == "")
500
0
        {
501
0
            msg = CPLGetXMLValue(
502
0
                psTree, "=ExceptionReport.Exception.exceptionCode", "");
503
0
            if (msg != "")
504
0
            {
505
0
                msg += ": ";
506
0
            }
507
0
            msg += CPLGetXMLValue(
508
0
                psTree, "=ExceptionReport.Exception.ExceptionText", "");
509
0
        }
510
0
        if (msg != "")
511
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
512
0
        else
513
0
            CPLError(CE_Failure, CPLE_AppDefined,
514
0
                     "Corrupt Service Exception:\n%s",
515
0
                     (const char *)psResult->pabyData);
516
0
        CPLDestroyXMLNode(psTree);
517
0
        CPLHTTPDestroyResult(psResult);
518
0
        return TRUE;
519
0
    }
520
521
    /* -------------------------------------------------------------------- */
522
    /*      Hopefully the error already issued by CPLHTTPFetch() is         */
523
    /*      sufficient.                                                     */
524
    /* -------------------------------------------------------------------- */
525
0
    if (CPLGetLastErrorNo() != 0)
526
0
    {
527
0
        CPLHTTPDestroyResult(psResult);
528
0
        return TRUE;
529
0
    }
530
531
0
    return false;
532
0
}
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()
542
543
0
{
544
0
    CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageOffering");
545
546
0
    const char *pszCols =
547
0
        CPLGetXMLValue(psCO, "dimensionLimit.columns", nullptr);
548
0
    const char *pszRows = CPLGetXMLValue(psCO, "dimensionLimit.rows", nullptr);
549
0
    if (pszCols && pszRows)
550
0
    {
551
0
        nMaxCols = atoi(pszCols);
552
0
        nMaxRows = atoi(pszRows);
553
0
        SetMetadataItem("MAXNCOLS", pszCols, "IMAGE_STRUCTURE");
554
0
        SetMetadataItem("MAXNROWS", pszRows, "IMAGE_STRUCTURE");
555
0
    }
556
557
    /* -------------------------------------------------------------------- */
558
    /*      Do we already have bandcount and pixel type settings?           */
559
    /* -------------------------------------------------------------------- */
560
0
    if (CPLGetXMLValue(psService, "BandCount", nullptr) != nullptr &&
561
0
        CPLGetXMLValue(psService, "BandType", nullptr) != nullptr)
562
0
        return TRUE;
563
564
    /* -------------------------------------------------------------------- */
565
    /*      Fetch a small block of raster data.                             */
566
    /* -------------------------------------------------------------------- */
567
0
    CPLHTTPResult *psResult = nullptr;
568
0
    CPLErr eErr;
569
570
0
    eErr = GetCoverage(0, 0, 2, 2, 2, 2, 0, nullptr, nullptr, &psResult);
571
0
    if (eErr != CE_None)
572
0
        return false;
573
574
    /* -------------------------------------------------------------------- */
575
    /*      Try and open result as a dataset.                               */
576
    /* -------------------------------------------------------------------- */
577
0
    GDALDataset *poDS = GDALOpenResult(psResult);
578
579
0
    if (poDS == nullptr)
580
0
        return false;
581
582
0
    const auto poSRS = poDS->GetSpatialRef();
583
0
    m_oSRS.Clear();
584
0
    if (poSRS)
585
0
        m_oSRS = *poSRS;
586
587
    /* -------------------------------------------------------------------- */
588
    /*      Record details.                                                 */
589
    /* -------------------------------------------------------------------- */
590
0
    if (poDS->GetRasterCount() < 1)
591
0
    {
592
0
        delete poDS;
593
0
        return false;
594
0
    }
595
596
0
    if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
597
0
        CPLCreateXMLElementAndValue(
598
0
            psService, "BandCount",
599
0
            CPLString().Printf("%d", poDS->GetRasterCount()));
600
601
0
    CPLCreateXMLElementAndValue(
602
0
        psService, "BandType",
603
0
        GDALGetDataTypeName(poDS->GetRasterBand(1)->GetRasterDataType()));
604
605
0
    bServiceDirty = true;
606
607
    /* -------------------------------------------------------------------- */
608
    /*      Cleanup                                                         */
609
    /* -------------------------------------------------------------------- */
610
0
    delete poDS;
611
612
0
    FlushMemoryResult();
613
614
0
    return TRUE;
615
0
}
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()
624
625
238
{
626
238
    if (!osResultFilename.empty())
627
0
    {
628
0
        VSIUnlink(osResultFilename.c_str());
629
0
        osResultFilename = "";
630
0
    }
631
632
238
    if (pabySavedDataBuffer)
633
0
    {
634
0
        CPLFree(pabySavedDataBuffer);
635
0
        pabySavedDataBuffer = nullptr;
636
0
    }
637
238
}
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)
651
652
0
{
653
0
    FlushMemoryResult();
654
655
0
    CPLDebug("WCS", "GDALOpenResult() on content-type: %s",
656
0
             psResult->pszContentType);
657
658
    /* -------------------------------------------------------------------- */
659
    /*      If this is multipart/related content type, we should search     */
660
    /*      for the second part.                                            */
661
    /* -------------------------------------------------------------------- */
662
0
    GByte *pabyData = psResult->pabyData;
663
0
    int nDataLen = psResult->nDataLen;
664
665
0
    if (psResult->pszContentType &&
666
0
        strstr(psResult->pszContentType, "multipart") &&
667
0
        CPLHTTPParseMultipartMime(psResult))
668
0
    {
669
0
        if (psResult->nMimePartCount > 1)
670
0
        {
671
0
            pabyData = psResult->pasMimePart[1].pabyData;
672
0
            nDataLen = psResult->pasMimePart[1].nDataLen;
673
674
0
            const char *pszContentTransferEncoding =
675
0
                CSLFetchNameValue(psResult->pasMimePart[1].papszHeaders,
676
0
                                  "Content-Transfer-Encoding");
677
0
            if (pszContentTransferEncoding &&
678
0
                EQUAL(pszContentTransferEncoding, "base64"))
679
0
            {
680
0
                nDataLen = CPLBase64DecodeInPlace(pabyData);
681
0
            }
682
0
        }
683
0
    }
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
0
    osResultFilename = VSIMemGenerateHiddenFilename("wcsresult.dat");
702
703
0
    VSILFILE *fp = VSIFileFromMemBuffer(osResultFilename.c_str(), pabyData,
704
0
                                        nDataLen, FALSE);
705
706
0
    if (fp == nullptr)
707
0
    {
708
0
        CPLHTTPDestroyResult(psResult);
709
0
        return nullptr;
710
0
    }
711
712
0
    VSIFCloseL(fp);
713
714
    /* -------------------------------------------------------------------- */
715
    /*      Try opening this result as a gdaldataset.                       */
716
    /* -------------------------------------------------------------------- */
717
0
    GDALDataset *poDS =
718
0
        (GDALDataset *)GDALOpen(osResultFilename.c_str(), GA_ReadOnly);
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
0
    if (poDS == nullptr)
725
0
    {
726
0
        std::string osTempFilename =
727
0
            CPLString().Printf("/tmp/%p_wcs.dat", this);
728
0
        VSILFILE *fpTemp = VSIFOpenL(osTempFilename.c_str(), "wb");
729
0
        if (fpTemp == nullptr)
730
0
        {
731
0
            CPLError(CE_Failure, CPLE_OpenFailed,
732
0
                     "Failed to create temporary file:%s",
733
0
                     osTempFilename.c_str());
734
0
        }
735
0
        else
736
0
        {
737
0
            if (VSIFWriteL(pabyData, nDataLen, 1, fpTemp) != 1)
738
0
            {
739
0
                CPLError(CE_Failure, CPLE_OpenFailed,
740
0
                         "Failed to write temporary file:%s",
741
0
                         osTempFilename.c_str());
742
0
                VSIFCloseL(fpTemp);
743
0
                VSIUnlink(osTempFilename.c_str());
744
0
            }
745
0
            else
746
0
            {
747
0
                VSIFCloseL(fpTemp);
748
0
                VSIUnlink(osResultFilename.c_str());
749
0
                osResultFilename = std::move(osTempFilename);
750
751
0
                poDS =
752
0
                    GDALDataset::Open(osResultFilename.c_str(),
753
0
                                      GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
754
0
            }
755
0
        }
756
0
    }
757
758
    /* -------------------------------------------------------------------- */
759
    /*      Steal the memory buffer from HTTP result.                       */
760
    /* -------------------------------------------------------------------- */
761
0
    pabySavedDataBuffer = psResult->pabyData;
762
763
0
    psResult->pabyData = nullptr;
764
765
0
    if (poDS == nullptr)
766
0
        FlushMemoryResult();
767
768
0
    CPLHTTPDestroyResult(psResult);
769
770
0
    return poDS;
771
0
}
772
773
/************************************************************************/
774
/*                            WCSParseVersion()                         */
775
/************************************************************************/
776
777
static int WCSParseVersion(const char *version)
778
166
{
779
166
    if (EQUAL(version, "2.0.1"))
780
0
        return 201;
781
166
    if (EQUAL(version, "1.1.2"))
782
0
        return 112;
783
166
    if (EQUAL(version, "1.1.1"))
784
11
        return 111;
785
155
    if (EQUAL(version, "1.1.0"))
786
0
        return 110;
787
155
    if (EQUAL(version, "1.0.0"))
788
0
        return 100;
789
155
    return 0;
790
155
}
791
792
/************************************************************************/
793
/*                             Version()                                */
794
/************************************************************************/
795
796
const char *WCSDataset::Version() const
797
0
{
798
0
    if (this->m_Version == 201)
799
0
        return "2.0.1";
800
0
    if (this->m_Version == 112)
801
0
        return "1.1.2";
802
0
    if (this->m_Version == 111)
803
0
        return "1.1.1";
804
0
    if (this->m_Version == 110)
805
0
        return "1.1.0";
806
0
    if (this->m_Version == 100)
807
0
        return "1.0.0";
808
0
    return "";
809
0
}
810
811
/************************************************************************/
812
/*                      FetchCapabilities()                             */
813
/************************************************************************/
814
815
166
#define WCS_HTTP_OPTIONS "TIMEOUT", "USERPWD", "HTTPAUTH"
816
817
static bool FetchCapabilities(GDALOpenInfo *poOpenInfo,
818
                              const std::string &urlIn, const std::string &path)
819
166
{
820
166
    std::string url = CPLURLAddKVP(urlIn.c_str(), "SERVICE", "WCS");
821
166
    url = CPLURLAddKVP(url.c_str(), "REQUEST", "GetCapabilities");
822
166
    std::string extra = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
823
166
                                             "GetCapabilitiesExtra", "");
824
166
    if (extra != "")
825
0
    {
826
0
        std::vector<std::string> pairs = Split(extra.c_str(), "&");
827
0
        for (unsigned int i = 0; i < pairs.size(); ++i)
828
0
        {
829
0
            std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
830
0
            url = CPLURLAddKVP(url.c_str(), pair[0].c_str(), pair[1].c_str());
831
0
        }
832
0
    }
833
166
    char **options = nullptr;
834
166
    const char *keys[] = {WCS_HTTP_OPTIONS};
835
664
    for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
836
498
    {
837
498
        std::string value =
838
498
            CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, keys[i], "");
839
498
        if (value != "")
840
0
        {
841
0
            options = CSLSetNameValue(options, keys[i], value.c_str());
842
0
        }
843
498
    }
844
166
    CPLHTTPResult *psResult = CPLHTTPFetch(url.c_str(), options);
845
166
    CSLDestroy(options);
846
166
    if (ProcessError(psResult))
847
166
    {
848
166
        return false;
849
166
    }
850
0
    CPLXMLTreeCloser doc(CPLParseXMLString((const char *)psResult->pabyData));
851
0
    CPLHTTPDestroyResult(psResult);
852
0
    if (doc.get() == nullptr)
853
0
    {
854
0
        return false;
855
0
    }
856
0
    CPLXMLNode *capabilities = doc.get();
857
0
    CPLSerializeXMLTreeToFile(capabilities, path.c_str());
858
0
    return true;
859
0
}
860
861
/************************************************************************/
862
/*                      CreateFromCapabilities()                        */
863
/************************************************************************/
864
865
WCSDataset *WCSDataset::CreateFromCapabilities(const std::string &cache,
866
                                               const std::string &path,
867
                                               const std::string &url)
868
0
{
869
0
    CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
870
0
    if (doc.get() == nullptr)
871
0
    {
872
0
        return nullptr;
873
0
    }
874
0
    CPLXMLNode *capabilities = doc.getDocumentElement();
875
0
    if (capabilities == nullptr)
876
0
    {
877
0
        return nullptr;
878
0
    }
879
    // get version, this version will overwrite the user's request
880
0
    int version_from_server =
881
0
        WCSParseVersion(CPLGetXMLValue(capabilities, "version", ""));
882
0
    if (version_from_server == 0)
883
0
    {
884
        // broken server, assume 1.0.0
885
0
        version_from_server = 100;
886
0
    }
887
0
    WCSDataset *poDS;
888
0
    if (version_from_server == 201)
889
0
    {
890
0
        poDS = new WCSDataset201(cache.c_str());
891
0
    }
892
0
    else if (version_from_server / 10 == 11)
893
0
    {
894
0
        poDS = new WCSDataset110(version_from_server, cache.c_str());
895
0
    }
896
0
    else
897
0
    {
898
0
        poDS = new WCSDataset100(cache.c_str());
899
0
    }
900
0
    if (poDS->ParseCapabilities(capabilities, url) != CE_None)
901
0
    {
902
0
        delete poDS;
903
0
        return nullptr;
904
0
    }
905
0
    poDS->SetDescription(RemoveExt(path).c_str());
906
0
    poDS->TrySaveXML();
907
0
    return poDS;
908
0
}
909
910
/************************************************************************/
911
/*                        CreateFromMetadata()                          */
912
/************************************************************************/
913
914
WCSDataset *WCSDataset::CreateFromMetadata(const std::string &cache,
915
                                           const std::string &path)
916
0
{
917
0
    WCSDataset *poDS;
918
0
    if (FileIsReadable(path))
919
0
    {
920
0
        CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
921
0
        CPLXMLNode *metadata = doc.get();
922
0
        if (metadata == nullptr)
923
0
        {
924
0
            return nullptr;
925
0
        }
926
0
        int version_from_metadata = WCSParseVersion(CPLGetXMLValue(
927
0
            SearchChildWithValue(SearchChildWithValue(metadata, "domain", ""),
928
0
                                 "key", "WCS_GLOBAL#version"),
929
0
            nullptr, ""));
930
0
        if (version_from_metadata == 201)
931
0
        {
932
0
            poDS = new WCSDataset201(cache.c_str());
933
0
        }
934
0
        else if (version_from_metadata / 10 == 11)
935
0
        {
936
0
            poDS = new WCSDataset110(version_from_metadata, cache.c_str());
937
0
        }
938
0
        else if (version_from_metadata / 10 == 10)
939
0
        {
940
0
            poDS = new WCSDataset100(cache.c_str());
941
0
        }
942
0
        else
943
0
        {
944
0
            CPLError(CE_Failure, CPLE_AppDefined,
945
0
                     "The metadata does not contain version. RECREATE_META?");
946
0
            return nullptr;
947
0
        }
948
0
        std::string modifiedPath = RemoveExt(RemoveExt(path));
949
0
        poDS->SetDescription(modifiedPath.c_str());
950
0
        poDS->TryLoadXML();  // todo: avoid reload
951
0
    }
952
0
    else
953
0
    {
954
        // obviously there was an error
955
        // processing the Capabilities file
956
        // so we show it to the user
957
0
        GByte *pabyOut = nullptr;
958
0
        std::string modifiedPath = RemoveExt(RemoveExt(path)) + ".xml";
959
0
        if (!VSIIngestFile(nullptr, modifiedPath.c_str(), &pabyOut, nullptr,
960
0
                           -1))
961
0
            return nullptr;
962
0
        std::string error = reinterpret_cast<char *>(pabyOut);
963
0
        if (error.size() > 2048)
964
0
        {
965
0
            error.resize(2048);
966
0
        }
967
0
        CPLError(CE_Failure, CPLE_AppDefined, "Error:\n%s", error.c_str());
968
0
        CPLFree(pabyOut);
969
0
        return nullptr;
970
0
    }
971
0
    return poDS;
972
0
}
973
974
/************************************************************************/
975
/*                        BootstrapGlobal()                             */
976
/************************************************************************/
977
978
static WCSDataset *BootstrapGlobal(GDALOpenInfo *poOpenInfo,
979
                                   const std::string &cache,
980
                                   const std::string &url)
981
166
{
982
    // do we have the capabilities file
983
166
    std::string filename;
984
166
    bool cached;
985
166
    if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
986
0
    {
987
0
        return nullptr;  // error in cache
988
0
    }
989
166
    if (!cached)
990
166
    {
991
166
        filename = "XXXXX";
992
166
        if (AddEntryToCache(cache, url, filename, ".xml") != CE_None)
993
0
        {
994
0
            return nullptr;  // error in cache
995
0
        }
996
166
        if (!FetchCapabilities(poOpenInfo, url, filename))
997
166
        {
998
166
            DeleteEntryFromCache(cache, "", url);
999
166
            return nullptr;
1000
166
        }
1001
0
        return WCSDataset::CreateFromCapabilities(cache, filename, url);
1002
166
    }
1003
0
    std::string metadata = RemoveExt(filename) + ".aux.xml";
1004
0
    bool recreate_meta =
1005
0
        CPLFetchBool(poOpenInfo->papszOpenOptions, "RECREATE_META", false);
1006
0
    if (FileIsReadable(metadata) && !recreate_meta)
1007
0
    {
1008
0
        return WCSDataset::CreateFromMetadata(cache, metadata);
1009
0
    }
1010
    // we have capabilities but not meta
1011
0
    return WCSDataset::CreateFromCapabilities(cache, filename, url);
1012
0
}
1013
1014
/************************************************************************/
1015
/*                          CreateService()                             */
1016
/************************************************************************/
1017
1018
static CPLXMLNode *CreateService(const std::string &base_url,
1019
                                 const std::string &version,
1020
                                 const std::string &coverage,
1021
                                 const std::string &parameters)
1022
0
{
1023
    // construct WCS_GDAL XML into psService
1024
0
    std::string xml = "<WCS_GDAL>";
1025
0
    xml += "<ServiceURL>" + base_url + "</ServiceURL>";
1026
0
    xml += "<Version>" + version + "</Version>";
1027
0
    xml += "<CoverageName>" + coverage + "</CoverageName>";
1028
0
    xml += "<Parameters>" + parameters + "</Parameters>";
1029
0
    xml += "</WCS_GDAL>";
1030
0
    CPLXMLNode *psService = CPLParseXMLString(xml.c_str());
1031
0
    return psService;
1032
0
}
1033
1034
/************************************************************************/
1035
/*                          UpdateService()                             */
1036
/************************************************************************/
1037
1038
#define WCS_SERVICE_OPTIONS                                                    \
1039
0
    "PreferredFormat", "NoDataValue", "BlockXSize", "BlockYSize",              \
1040
0
        "OverviewCount", "GetCoverageExtra", "DescribeCoverageExtra",          \
1041
0
        "Domain", "BandCount", "BandType", "DefaultTime", "CRS"
1042
1043
#define WCS_TWEAK_OPTIONS                                                      \
1044
0
    "OriginAtBoundary", "OuterExtents", "BufSizeAdjust", "OffsetsPositive",    \
1045
0
        "NrOffsets", "GridCRSOptional", "NoGridAxisSwap", "GridAxisLabelSwap", \
1046
0
        "SubsetAxisSwap", "UseScaleFactor", "INTERLEAVE"
1047
1048
static bool UpdateService(CPLXMLNode *service, GDALOpenInfo *poOpenInfo)
1049
0
{
1050
0
    bool updated = false;
1051
    // descriptions in frmt_wcs.html
1052
0
    const char *keys[] = {"Subset",
1053
0
                          "RangeSubsetting",
1054
0
                          WCS_URL_PARAMETERS,
1055
0
                          WCS_SERVICE_OPTIONS,
1056
0
                          WCS_TWEAK_OPTIONS,
1057
0
                          WCS_HTTP_OPTIONS
1058
#ifdef DEBUG_WCS
1059
                          ,
1060
                          "filename"
1061
#endif
1062
0
    };
1063
0
    for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
1064
0
    {
1065
0
        const char *value;
1066
0
        if (CSLFindString(poOpenInfo->papszOpenOptions, keys[i]) != -1)
1067
0
        {
1068
0
            value = "TRUE";
1069
0
        }
1070
0
        else
1071
0
        {
1072
0
            value = CSLFetchNameValue(poOpenInfo->papszOpenOptions, keys[i]);
1073
0
            if (value == nullptr)
1074
0
            {
1075
0
                continue;
1076
0
            }
1077
0
        }
1078
0
        updated = CPLUpdateXML(service, keys[i], value) || updated;
1079
0
    }
1080
0
    return updated;
1081
0
}
1082
1083
/************************************************************************/
1084
/*                          CreateFromCache()                           */
1085
/************************************************************************/
1086
1087
static WCSDataset *CreateFromCache(const std::string &cache)
1088
238
{
1089
238
    WCSDataset *ds = new WCSDataset201(cache.c_str());
1090
238
    if (!ds)
1091
0
    {
1092
0
        return nullptr;
1093
0
    }
1094
238
    char **metadata = nullptr;
1095
238
    std::vector<std::string> contents = ReadCache(cache);
1096
238
    std::string path = "SUBDATASET_";
1097
238
    unsigned int index = 1;
1098
45.2k
    for (unsigned int i = 0; i < contents.size(); ++i)
1099
44.9k
    {
1100
44.9k
        std::string name = path + CPLString().Printf("%d_", index) + "NAME";
1101
44.9k
        std::string value = "WCS:" + contents[i];
1102
44.9k
        metadata = CSLSetNameValue(metadata, name.c_str(), value.c_str());
1103
44.9k
        index += 1;
1104
44.9k
    }
1105
238
    ds->SetMetadata(metadata, "SUBDATASETS");
1106
238
    CSLDestroy(metadata);
1107
238
    return ds;
1108
238
}
1109
1110
/************************************************************************/
1111
/*                              ParseURL()                              */
1112
/************************************************************************/
1113
1114
static void ParseURL(std::string &url, std::string &version,
1115
                     std::string &coverage, std::string &parameters)
1116
166
{
1117
166
    version = CPLURLGetValue(url.c_str(), "version");
1118
166
    url = URLRemoveKey(url.c_str(), "version");
1119
    // the default version, the aim is to have version explicitly in cache keys
1120
166
    if (WCSParseVersion(version.c_str()) == 0)
1121
155
    {
1122
155
        version = "2.0.1";
1123
155
    }
1124
166
    coverage = CPLURLGetValue(url.c_str(), "coverageid");  // 2.0
1125
166
    if (coverage == "")
1126
166
    {
1127
166
        coverage = CPLURLGetValue(url.c_str(), "identifiers");  // 1.1
1128
166
        if (coverage == "")
1129
166
        {
1130
166
            coverage = CPLURLGetValue(url.c_str(), "coverage");  // 1.0
1131
166
            url = URLRemoveKey(url.c_str(), "coverage");
1132
166
        }
1133
0
        else
1134
0
        {
1135
0
            url = URLRemoveKey(url.c_str(), "identifiers");
1136
0
        }
1137
166
    }
1138
0
    else
1139
0
    {
1140
0
        url = URLRemoveKey(url.c_str(), "coverageid");
1141
0
    }
1142
166
    size_t pos = url.find("?");
1143
166
    if (pos == std::string::npos)
1144
36
    {
1145
36
        url += "?";
1146
36
        return;
1147
36
    }
1148
130
    parameters = url.substr(pos + 1, std::string::npos);
1149
130
    url.erase(pos + 1, std::string::npos);
1150
130
}
1151
1152
/************************************************************************/
1153
/*                                Open()                                */
1154
/************************************************************************/
1155
1156
GDALDataset *WCSDataset::Open(GDALOpenInfo *poOpenInfo)
1157
1158
406
{
1159
406
    if (!WCSDriverIdentify(poOpenInfo))
1160
0
    {
1161
0
        return nullptr;
1162
0
    }
1163
1164
406
    std::string cache =
1165
406
        CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "");
1166
406
    if (!SetupCache(cache, CPLFetchBool(poOpenInfo->papszOpenOptions,
1167
406
                                        "CLEAR_CACHE", false)))
1168
0
    {
1169
0
        return nullptr;
1170
0
    }
1171
406
    CPLXMLNode *service = nullptr;
1172
406
    char **papszModifiers = nullptr;
1173
1174
406
    if (poOpenInfo->nHeaderBytes == 0 &&
1175
406
        STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "WCS:"))
1176
404
    {
1177
        /* --------------------------------------------------------------------
1178
         */
1179
        /*      Filename is WCS:URL */
1180
        /* --------------------------------------------------------------------
1181
         */
1182
404
        std::string url = (const char *)(poOpenInfo->pszFilename + 4);
1183
1184
404
        const char *del = CSLFetchNameValue(poOpenInfo->papszOpenOptions,
1185
404
                                            "DELETE_FROM_CACHE");
1186
404
        if (del != nullptr)
1187
0
        {
1188
0
            int k = atoi(del);
1189
0
            std::vector<std::string> contents = ReadCache(cache);
1190
0
            if (k > 0 && k <= (int)contents.size())
1191
0
            {
1192
0
                DeleteEntryFromCache(cache, "", contents[k - 1]);
1193
0
            }
1194
0
        }
1195
1196
404
        if (url == "")
1197
238
        {
1198
238
            return CreateFromCache(cache);
1199
238
        }
1200
1201
166
        if (CPLFetchBool(poOpenInfo->papszOpenOptions, "REFRESH_CACHE", false))
1202
0
        {
1203
0
            DeleteEntryFromCache(cache, "", url);
1204
0
        }
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
166
        std::string filename;
1215
166
        bool cached;
1216
166
        if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
1217
0
        {
1218
0
            return nullptr;  // error in cache
1219
0
        }
1220
1221
166
        std::string full_url = url, version, coverage, parameters;
1222
166
        ParseURL(url, version, coverage, parameters);
1223
1224
        // The goal is to get the service XML and a filename for it
1225
1226
166
        bool updated = false;
1227
166
        if (cached)
1228
0
        {
1229
            /* --------------------------------------------------------------------
1230
             */
1231
            /*          The fast route, service file is in cache. */
1232
            /* --------------------------------------------------------------------
1233
             */
1234
0
            if (coverage == "")
1235
0
            {
1236
0
                std::string url2 =
1237
0
                    CPLURLAddKVP(url.c_str(), "version", version.c_str());
1238
0
                WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
1239
0
                return global;
1240
0
            }
1241
0
            service = CPLParseXMLFile(filename.c_str());
1242
0
        }
1243
166
        else
1244
166
        {
1245
            /* --------------------------------------------------------------------
1246
             */
1247
            /*          Get capabilities. */
1248
            /* --------------------------------------------------------------------
1249
             */
1250
166
            std::string url2 =
1251
166
                CPLURLAddKVP(url.c_str(), "version", version.c_str());
1252
166
            if (parameters != "")
1253
128
            {
1254
128
                url2 += "&" + parameters;
1255
128
            }
1256
166
            WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
1257
166
            if (!global)
1258
166
            {
1259
166
                return nullptr;
1260
166
            }
1261
0
            if (coverage == "")
1262
0
            {
1263
0
                return global;
1264
0
            }
1265
0
            if (version == "")
1266
0
            {
1267
0
                version = global->Version();
1268
0
            }
1269
0
            service = CreateService(url, version, coverage, parameters);
1270
            /* --------------------------------------------------------------------
1271
             */
1272
            /*          The filename for the new service file. */
1273
            /* --------------------------------------------------------------------
1274
             */
1275
0
            filename = "XXXXX";
1276
0
            if (AddEntryToCache(cache, full_url, filename, ".xml") != CE_None)
1277
0
            {
1278
0
                return nullptr;  // error in cache
1279
0
            }
1280
            // Create basic service metadata
1281
            // copy global metadata (not SUBDATASETS metadata)
1282
0
            std::string global_base = std::string(global->GetDescription());
1283
0
            std::string global_meta = global_base + ".aux.xml";
1284
0
            std::string capabilities = global_base + ".xml";
1285
0
            CPLXMLTreeCloser doc(CPLParseXMLFile(global_meta.c_str()));
1286
0
            CPLXMLNode *metadata = doc.getDocumentElement();
1287
0
            CPLXMLNode *domain =
1288
0
                SearchChildWithValue(metadata, "domain", "SUBDATASETS");
1289
0
            if (domain != nullptr)
1290
0
            {
1291
0
                CPLRemoveXMLChild(metadata, domain);
1292
0
                CPLDestroyXMLNode(domain);
1293
0
            }
1294
            // get metadata for this coverage from the capabilities XML
1295
0
            CPLXMLTreeCloser doc2(CPLParseXMLFile(capabilities.c_str()));
1296
0
            global->ParseCoverageCapabilities(doc2.getDocumentElement(),
1297
0
                                              coverage, metadata->psChild);
1298
0
            delete global;
1299
0
            std::string metadata_filename = filename + ".aux.xml";
1300
0
            CPLSerializeXMLTreeToFile(metadata, metadata_filename.c_str());
1301
0
            updated = true;
1302
0
        }
1303
0
        CPLFree(poOpenInfo->pszFilename);
1304
0
        poOpenInfo->pszFilename = CPLStrdup(filename.c_str());
1305
0
        updated = UpdateService(service, poOpenInfo) || updated;
1306
0
        if (updated || !cached)
1307
0
        {
1308
0
            CPLSerializeXMLTreeToFile(service, filename.c_str());
1309
0
        }
1310
0
    }
1311
    /* -------------------------------------------------------------------- */
1312
    /*      Is this a WCS_GDAL service description file or "in url"         */
1313
    /*      equivalent?                                                     */
1314
    /* -------------------------------------------------------------------- */
1315
2
    else if (poOpenInfo->nHeaderBytes == 0 &&
1316
2
             STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
1317
2
                            "<WCS_GDAL>"))
1318
2
    {
1319
2
        service = CPLParseXMLString(poOpenInfo->pszFilename);
1320
2
    }
1321
0
    else if (poOpenInfo->nHeaderBytes >= 10 &&
1322
0
             STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "<WCS_GDAL>"))
1323
0
    {
1324
0
        service = CPLParseXMLFile(poOpenInfo->pszFilename);
1325
0
    }
1326
    /* -------------------------------------------------------------------- */
1327
    /*      Is this apparently a subdataset?                                */
1328
    /* -------------------------------------------------------------------- */
1329
0
    else if (STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
1330
0
                            "WCS_SDS:") &&
1331
0
             poOpenInfo->nHeaderBytes == 0)
1332
0
    {
1333
0
        int iLast;
1334
1335
0
        papszModifiers = CSLTokenizeString2(poOpenInfo->pszFilename + 8, ",",
1336
0
                                            CSLT_HONOURSTRINGS);
1337
1338
0
        iLast = CSLCount(papszModifiers) - 1;
1339
0
        if (iLast >= 0)
1340
0
        {
1341
0
            service = CPLParseXMLFile(papszModifiers[iLast]);
1342
0
            CPLFree(papszModifiers[iLast]);
1343
0
            papszModifiers[iLast] = nullptr;
1344
0
        }
1345
0
    }
1346
1347
    /* -------------------------------------------------------------------- */
1348
    /*      Success so far?                                                 */
1349
    /* -------------------------------------------------------------------- */
1350
2
    if (service == nullptr)
1351
2
    {
1352
2
        CSLDestroy(papszModifiers);
1353
2
        return nullptr;
1354
2
    }
1355
1356
    /* -------------------------------------------------------------------- */
1357
    /*      Confirm the requested access is supported.                      */
1358
    /* -------------------------------------------------------------------- */
1359
0
    if (poOpenInfo->eAccess == GA_Update)
1360
0
    {
1361
0
        CSLDestroy(papszModifiers);
1362
0
        CPLDestroyXMLNode(service);
1363
0
        ReportUpdateNotSupportedByDriver("WCS");
1364
0
        return nullptr;
1365
0
    }
1366
1367
    /* -------------------------------------------------------------------- */
1368
    /*      Check for required minimum fields.                              */
1369
    /* -------------------------------------------------------------------- */
1370
0
    if (!CPLGetXMLValue(service, "ServiceURL", nullptr) ||
1371
0
        !CPLGetXMLValue(service, "CoverageName", nullptr))
1372
0
    {
1373
0
        CSLDestroy(papszModifiers);
1374
0
        CPLError(
1375
0
            CE_Failure, CPLE_OpenFailed,
1376
0
            "Missing one or both of ServiceURL and CoverageName elements.\n"
1377
0
            "See WCS driver documentation for details on service description "
1378
0
            "file format.");
1379
1380
0
        CPLDestroyXMLNode(service);
1381
0
        return nullptr;
1382
0
    }
1383
1384
    /* -------------------------------------------------------------------- */
1385
    /*      What version are we working with?                               */
1386
    /* -------------------------------------------------------------------- */
1387
0
    const char *pszVersion = CPLGetXMLValue(service, "Version", "1.0.0");
1388
1389
0
    int nVersion = WCSParseVersion(pszVersion);
1390
1391
0
    if (nVersion == 0)
1392
0
    {
1393
0
        CSLDestroy(papszModifiers);
1394
0
        CPLDestroyXMLNode(service);
1395
0
        return nullptr;
1396
0
    }
1397
1398
    /* -------------------------------------------------------------------- */
1399
    /*      Create a corresponding GDALDataset.                             */
1400
    /* -------------------------------------------------------------------- */
1401
0
    WCSDataset *poDS;
1402
0
    if (nVersion == 201)
1403
0
    {
1404
0
        poDS = new WCSDataset201(cache.c_str());
1405
0
    }
1406
0
    else if (nVersion / 10 == 11)
1407
0
    {
1408
0
        poDS = new WCSDataset110(nVersion, cache.c_str());
1409
0
    }
1410
0
    else
1411
0
    {
1412
0
        poDS = new WCSDataset100(cache.c_str());
1413
0
    }
1414
1415
0
    poDS->psService = service;
1416
0
    poDS->SetDescription(poOpenInfo->pszFilename);
1417
0
    poDS->papszSDSModifiers = papszModifiers;
1418
    // WCS:URL => basic metadata was already made
1419
    // Metadata is needed in ExtractGridInfo
1420
0
    poDS->TryLoadXML();
1421
1422
    /* -------------------------------------------------------------------- */
1423
    /*      Capture HTTP parameters.                                        */
1424
    /* -------------------------------------------------------------------- */
1425
0
    const char *pszParam;
1426
1427
0
    poDS->papszHttpOptions =
1428
0
        CSLSetNameValue(poDS->papszHttpOptions, "TIMEOUT",
1429
0
                        CPLGetXMLValue(service, "Timeout", "30"));
1430
1431
0
    pszParam = CPLGetXMLValue(service, "HTTPAUTH", nullptr);
1432
0
    if (pszParam)
1433
0
        poDS->papszHttpOptions =
1434
0
            CSLSetNameValue(poDS->papszHttpOptions, "HTTPAUTH", pszParam);
1435
1436
0
    pszParam = CPLGetXMLValue(service, "USERPWD", nullptr);
1437
0
    if (pszParam)
1438
0
        poDS->papszHttpOptions =
1439
0
            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
0
    if (CPLGetXMLNode(service, "CoverageOffering") == nullptr &&
1446
0
        CPLGetXMLNode(service, "CoverageDescription") == nullptr)
1447
0
    {
1448
0
        if (!poDS->DescribeCoverage())
1449
0
        {
1450
0
            delete poDS;
1451
0
            return nullptr;
1452
0
        }
1453
0
    }
1454
1455
    /* -------------------------------------------------------------------- */
1456
    /*      Extract coordinate system, grid size, and geotransform from     */
1457
    /*      the coverage description and/or service description             */
1458
    /*      information.                                                    */
1459
    /* -------------------------------------------------------------------- */
1460
0
    if (!poDS->ExtractGridInfo())
1461
0
    {
1462
0
        delete poDS;
1463
0
        return nullptr;
1464
0
    }
1465
1466
    /* -------------------------------------------------------------------- */
1467
    /*      Leave now or there may be a GetCoverage call.                   */
1468
    /*                                                                      */
1469
    /* -------------------------------------------------------------------- */
1470
0
    int nBandCount = -1;
1471
0
    std::string sBandCount = CPLGetXMLValue(service, "BandCount", "");
1472
0
    if (sBandCount != "")
1473
0
    {
1474
0
        nBandCount = atoi(sBandCount.c_str());
1475
0
    }
1476
0
    if (CPLFetchBool(poOpenInfo->papszOpenOptions, "SKIP_GETCOVERAGE", false) ||
1477
0
        nBandCount == 0)
1478
0
    {
1479
0
        return poDS;
1480
0
    }
1481
1482
    /* -------------------------------------------------------------------- */
1483
    /*      Extract band count and type from a sample.                      */
1484
    /* -------------------------------------------------------------------- */
1485
0
    if (!poDS->EstablishRasterDetails())  // todo: do this only if missing info
1486
0
    {
1487
0
        delete poDS;
1488
0
        return nullptr;
1489
0
    }
1490
1491
    /* -------------------------------------------------------------------- */
1492
    /*      It is ok to not have bands. The user just needs to supply       */
1493
    /*      more information.                                               */
1494
    /* -------------------------------------------------------------------- */
1495
0
    nBandCount = atoi(CPLGetXMLValue(service, "BandCount", "0"));
1496
0
    if (nBandCount == 0)
1497
0
    {
1498
0
        return poDS;
1499
0
    }
1500
1501
    /* -------------------------------------------------------------------- */
1502
    /*      Create band information objects.                                */
1503
    /* -------------------------------------------------------------------- */
1504
0
    int iBand;
1505
1506
0
    if (!GDALCheckBandCount(nBandCount, FALSE))
1507
0
    {
1508
0
        delete poDS;
1509
0
        return nullptr;
1510
0
    }
1511
1512
0
    for (iBand = 0; iBand < nBandCount; iBand++)
1513
0
    {
1514
0
        WCSRasterBand *band = new WCSRasterBand(poDS, iBand + 1, -1);
1515
        // copy band specific metadata to the band
1516
0
        char **md_from = poDS->GetMetadata("");
1517
0
        char **md_to = nullptr;
1518
0
        if (md_from)
1519
0
        {
1520
0
            std::string our_key = CPLString().Printf("FIELD_%d_", iBand + 1);
1521
0
            for (char **from = md_from; *from != nullptr; ++from)
1522
0
            {
1523
0
                std::vector<std::string> kv = Split(*from, "=");
1524
0
                if (kv.size() > 1 &&
1525
0
                    STARTS_WITH(kv[0].c_str(), our_key.c_str()))
1526
0
                {
1527
0
                    std::string key = kv[0];
1528
0
                    std::string value = kv[1];
1529
0
                    key.erase(0, our_key.length());
1530
0
                    md_to = CSLSetNameValue(md_to, key.c_str(), value.c_str());
1531
0
                }
1532
0
            }
1533
0
        }
1534
0
        band->SetMetadata(md_to, "");
1535
0
        CSLDestroy(md_to);
1536
0
        poDS->SetBand(iBand + 1, band);
1537
0
    }
1538
1539
    /* -------------------------------------------------------------------- */
1540
    /*      Set time metadata on the dataset if we are selecting a          */
1541
    /*      temporal slice.                                                 */
1542
    /* -------------------------------------------------------------------- */
1543
0
    std::string osTime = CSLFetchNameValueDef(poDS->papszSDSModifiers, "time",
1544
0
                                              poDS->osDefaultTime.c_str());
1545
1546
0
    if (osTime != "")
1547
0
        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
0
    poDS->osBandIdentifier = CPLGetXMLValue(service, "BandIdentifier", "");
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
0
    if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WCS_SDS:") &&
1560
0
        !STARTS_WITH_CI(poOpenInfo->pszFilename, "<WCS_GDAL>") &&
1561
0
        !poDS->aosTimePositions.empty())
1562
0
    {
1563
0
        char **papszSubdatasets = nullptr;
1564
0
        int iTime;
1565
1566
0
        for (iTime = 0; iTime < (int)poDS->aosTimePositions.size(); iTime++)
1567
0
        {
1568
0
            std::string osName;
1569
0
            std::string osValue;
1570
1571
0
            osName = CPLString().Printf("SUBDATASET_%d_NAME", iTime + 1);
1572
0
            osValue = CPLString().Printf("WCS_SDS:time=\"%s\",%s",
1573
0
                                         poDS->aosTimePositions[iTime].c_str(),
1574
0
                                         poOpenInfo->pszFilename);
1575
0
            papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
1576
0
                                               osValue.c_str());
1577
1578
0
            std::string osCoverage =
1579
0
                CPLGetXMLValue(poDS->psService, "CoverageName", "");
1580
1581
0
            osName = CPLString().Printf("SUBDATASET_%d_DESC", iTime + 1);
1582
0
            osValue =
1583
0
                CPLString().Printf("Coverage %s at time %s", osCoverage.c_str(),
1584
0
                                   poDS->aosTimePositions[iTime].c_str());
1585
0
            papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
1586
0
                                               osValue.c_str());
1587
0
        }
1588
1589
0
        poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS");
1590
1591
0
        CSLDestroy(papszSubdatasets);
1592
0
    }
1593
1594
    /* -------------------------------------------------------------------- */
1595
    /*      Initialize any PAM information.                                 */
1596
    /* -------------------------------------------------------------------- */
1597
0
    poDS->TryLoadXML();
1598
0
    return poDS;
1599
0
}
1600
1601
/************************************************************************/
1602
/*                          GetGeoTransform()                           */
1603
/************************************************************************/
1604
1605
CPLErr WCSDataset::GetGeoTransform(GDALGeoTransform &gt) const
1606
1607
0
{
1608
0
    gt = m_gt;
1609
0
    return CE_None;
1610
0
}
1611
1612
/************************************************************************/
1613
/*                          GetSpatialRef()                             */
1614
/************************************************************************/
1615
1616
const OGRSpatialReference *WCSDataset::GetSpatialRef() const
1617
1618
0
{
1619
0
    const auto poSRS = GDALPamDataset::GetSpatialRef();
1620
0
    if (poSRS)
1621
0
        return poSRS;
1622
1623
0
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1624
0
}
1625
1626
/************************************************************************/
1627
/*                            GetFileList()                             */
1628
/************************************************************************/
1629
1630
char **WCSDataset::GetFileList()
1631
1632
0
{
1633
0
    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
0
    return papszFileList;
1648
0
}
1649
1650
/************************************************************************/
1651
/*                      GetMetadataDomainList()                         */
1652
/************************************************************************/
1653
1654
char **WCSDataset::GetMetadataDomainList()
1655
0
{
1656
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
1657
0
                                   TRUE, "xml:CoverageOffering", nullptr);
1658
0
}
1659
1660
/************************************************************************/
1661
/*                            GetMetadata()                             */
1662
/************************************************************************/
1663
1664
char **WCSDataset::GetMetadata(const char *pszDomain)
1665
1666
0
{
1667
0
    if (pszDomain == nullptr || !EQUAL(pszDomain, "xml:CoverageOffering"))
1668
0
        return GDALPamDataset::GetMetadata(pszDomain);
1669
1670
0
    CPLXMLNode *psNode = CPLGetXMLNode(psService, "CoverageOffering");
1671
1672
0
    if (psNode == nullptr)
1673
0
        psNode = CPLGetXMLNode(psService, "CoverageDescription");
1674
1675
0
    if (psNode == nullptr)
1676
0
        return nullptr;
1677
1678
0
    if (apszCoverageOfferingMD[0] == nullptr)
1679
0
    {
1680
0
        CPLXMLNode *psNext = psNode->psNext;
1681
0
        psNode->psNext = nullptr;
1682
1683
0
        apszCoverageOfferingMD[0] = CPLSerializeXMLTree(psNode);
1684
1685
0
        psNode->psNext = psNext;
1686
0
    }
1687
1688
0
    return apszCoverageOfferingMD;
1689
0
}
1690
1691
/************************************************************************/
1692
/*                          GDALRegister_WCS()                          */
1693
/************************************************************************/
1694
1695
void GDALRegister_WCS()
1696
1697
22
{
1698
22
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1699
0
        return;
1700
1701
22
    GDALDriver *poDriver = new GDALDriver();
1702
22
    WCSDriverSetCommonMetadata(poDriver);
1703
1704
22
    poDriver->pfnOpen = WCSDataset::Open;
1705
1706
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1707
22
}