LCOV - code coverage report
Current view: top level - frmts/wcs - wcsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 441 658 67.0 %
Date: 2025-06-28 21:28:23 Functions: 27 31 87.1 %

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

Generated by: LCOV version 1.14