LCOV - code coverage report
Current view: top level - frmts/wcs - wcsdataset201.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 357 493 72.4 %
Date: 2024-11-21 22:18:42 Functions: 11 12 91.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  WCS Client Driver
       4             :  * Purpose:  Implementation of Dataset class for WCS 2.0.
       5             :  * Author:   Ari Jolma <ari dot jolma at gmail dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2006, Frank Warmerdam
       9             :  * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  * Copyright (c) 2017, Ari Jolma
      11             :  * Copyright (c) 2017, Finnish Environment Institute
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include "cpl_string.h"
      17             : #include "cpl_minixml.h"
      18             : #include "cpl_http.h"
      19             : #include "cpl_conv.h"
      20             : #include "gmlutils.h"
      21             : #include "gdal_frmts.h"
      22             : #include "gdal_pam.h"
      23             : #include "ogr_spatialref.h"
      24             : #include "gmlcoverage.h"
      25             : 
      26             : #include <algorithm>
      27             : 
      28             : #include "wcsdataset.h"
      29             : #include "wcsutils.h"
      30             : 
      31             : using namespace WCSUtils;
      32             : 
      33             : /************************************************************************/
      34             : /*                         CoverageSubtype()                            */
      35             : /*                                                                      */
      36             : /************************************************************************/
      37             : 
      38          21 : static std::string CoverageSubtype(CPLXMLNode *coverage)
      39             : {
      40             :     std::string subtype =
      41          21 :         CPLGetXMLValue(coverage, "ServiceParameters.CoverageSubtype", "");
      42          21 :     size_t pos = subtype.find("Coverage");
      43          21 :     if (pos != std::string::npos)
      44             :     {
      45          21 :         subtype.erase(pos);
      46             :     }
      47          21 :     return subtype;
      48             : }
      49             : 
      50             : /************************************************************************/
      51             : /*                         GetGridNode()                                */
      52             : /*                                                                      */
      53             : /************************************************************************/
      54             : 
      55          21 : static CPLXMLNode *GetGridNode(CPLXMLNode *coverage, const std::string &subtype)
      56             : {
      57          21 :     CPLXMLNode *grid = nullptr;
      58             :     // Construct the name of the node that we look under domainSet.
      59             :     // For now we can handle RectifiedGrid and ReferenceableGridByVectors.
      60             :     // Note that if this is called at GetCoverage stage, the grid should not be
      61             :     // NULL.
      62          21 :     std::string path = "domainSet";
      63          21 :     if (subtype == "RectifiedGrid")
      64             :     {
      65          18 :         grid = CPLGetXMLNode(coverage, (path + "." + subtype).c_str());
      66             :     }
      67           3 :     else if (subtype == "ReferenceableGrid")
      68             :     {
      69           3 :         grid = CPLGetXMLNode(coverage,
      70           6 :                              (path + "." + subtype + "ByVectors").c_str());
      71             :     }
      72          21 :     if (!grid)
      73             :     {
      74           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      75             :                  "Can't handle coverages of type '%s'.", subtype.c_str());
      76             :     }
      77          42 :     return grid;
      78             : }
      79             : 
      80             : /************************************************************************/
      81             : /*                         ParseParameters()                            */
      82             : /*                                                                      */
      83             : /************************************************************************/
      84             : 
      85          42 : static void ParseParameters(CPLXMLNode *service,
      86             :                             std::vector<std::string> &dimensions,
      87             :                             std::string &range,
      88             :                             std::vector<std::vector<std::string>> &others)
      89             : {
      90             :     std::vector<std::string> parameters =
      91          84 :         Split(CPLGetXMLValue(service, "Parameters", ""), "&");
      92          84 :     for (unsigned int i = 0; i < parameters.size(); ++i)
      93             :     {
      94          42 :         std::vector<std::string> kv = Split(parameters[i].c_str(), "=");
      95          42 :         if (kv.size() < 2)
      96             :         {
      97           0 :             continue;
      98             :         }
      99          42 :         kv[0] = CPLString(kv[0]).toupper();
     100          42 :         if (kv[0] == "RANGESUBSET")
     101             :         {
     102           0 :             range = kv[1];
     103             :         }
     104          42 :         else if (kv[0] == "SUBSET")
     105             :         {
     106           0 :             dimensions = Split(kv[1].c_str(), ";");
     107             :         }
     108             :         else
     109             :         {
     110          84 :             std::vector<std::string> kv2;
     111          42 :             kv2.push_back(kv[0]);
     112          42 :             kv2.push_back(kv[1]);
     113          42 :             others.push_back(kv2);
     114             :         }
     115             :     }
     116             :     // fallback to service values, if any
     117          42 :     if (range == "")
     118             :     {
     119          42 :         range = CPLGetXMLValue(service, "RangeSubset", "");
     120             :     }
     121          42 :     if (dimensions.size() == 0)
     122             :     {
     123          42 :         dimensions = Split(CPLGetXMLValue(service, "Subset", ""), ";");
     124             :     }
     125          42 : }
     126             : 
     127             : /************************************************************************/
     128             : /*                         GetExtent()                                  */
     129             : /*                                                                      */
     130             : /************************************************************************/
     131             : 
     132          21 : std::vector<double> WCSDataset201::GetExtent(int nXOff, int nYOff, int nXSize,
     133             :                                              int nYSize,
     134             :                                              CPL_UNUSED int nBufXSize,
     135             :                                              CPL_UNUSED int nBufYSize)
     136             : {
     137          21 :     std::vector<double> extent;
     138             :     // WCS 2.0 extents are the outer edges of outer pixels.
     139          21 :     extent.push_back(adfGeoTransform[0] + (nXOff)*adfGeoTransform[1]);
     140           0 :     extent.push_back(adfGeoTransform[3] +
     141          21 :                      (nYOff + nYSize) * adfGeoTransform[5]);
     142           0 :     extent.push_back(adfGeoTransform[0] +
     143          21 :                      (nXOff + nXSize) * adfGeoTransform[1]);
     144          21 :     extent.push_back(adfGeoTransform[3] + (nYOff)*adfGeoTransform[5]);
     145          21 :     return extent;
     146             : }
     147             : 
     148             : /************************************************************************/
     149             : /*                        GetCoverageRequest()                          */
     150             : /*                                                                      */
     151             : /************************************************************************/
     152             : 
     153             : std::string
     154          21 : WCSDataset201::GetCoverageRequest(bool scaled, int nBufXSize, int nBufYSize,
     155             :                                   const std::vector<double> &extent,
     156             :                                   const std::string & /*osBandList*/)
     157             : {
     158          21 :     std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
     159          21 :     request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
     160          21 :     request += "&REQUEST=GetCoverage";
     161             :     request +=
     162          21 :         "&VERSION=" + std::string(CPLGetXMLValue(psService, "Version", ""));
     163          42 :     request += "&COVERAGEID=" +
     164          63 :                URLEncode(CPLGetXMLValue(psService, "CoverageName", ""));
     165             : 
     166             :     // note: native_crs is not really supported
     167          21 :     if (!native_crs)
     168             :     {
     169           0 :         std::string crs = URLEncode(CPLGetXMLValue(psService, "SRS", ""));
     170           0 :         request += "&OUTPUTCRS=" + crs;
     171           0 :         request += "&SUBSETTINGCRS=" + crs;
     172             :     }
     173             : 
     174             :     std::vector<std::string> domain =
     175          42 :         Split(CPLGetXMLValue(psService, "Domain", ""), ",");
     176          21 :     if (domain.size() < 2)
     177             :     {
     178             :         // eek!
     179           0 :         domain.push_back("E");
     180           0 :         domain.push_back("N");
     181             :     }
     182          21 :     const char *x = domain[0].c_str();
     183          21 :     const char *y = domain[1].c_str();
     184          21 :     if (CPLGetXMLBoolean(psService, "SubsetAxisSwap"))
     185             :     {
     186           6 :         const char *tmp = x;
     187           6 :         x = y;
     188           6 :         y = tmp;
     189             :     }
     190             : 
     191             :     std::vector<std::string> low =
     192          42 :         Split(CPLGetXMLValue(psService, "Low", ""), ",");
     193             :     std::vector<std::string> high =
     194          42 :         Split(CPLGetXMLValue(psService, "High", ""), ",");
     195          42 :     std::string a = CPLString().Printf("%.17g", extent[0]);
     196          21 :     if (low.size() > 1 && CPLAtof(low[0].c_str()) > extent[0])
     197             :     {
     198           0 :         a = low[0];
     199             :     }
     200          42 :     std::string b = CPLString().Printf("%.17g", extent[2]);
     201          21 :     if (high.size() > 1 && CPLAtof(high[0].c_str()) < extent[2])
     202             :     {
     203           1 :         b = high[0];
     204             :     }
     205             :     /*
     206             :     std::string a = CPLString().Printf(
     207             :         "%.17g", MAX(adfGeoTransform[0], extent[0]));
     208             :     std::string b = CPLString().Printf(
     209             :         "%.17g", MIN(adfGeoTransform[0] + nRasterXSize * adfGeoTransform[1],
     210             :     extent[2]));
     211             :     */
     212             : 
     213             :     // 09-147 KVP Protocol: subset keys must be unique
     214             :     // GeoServer: seems to require plain SUBSET for x and y
     215             : 
     216             :     request +=
     217          21 :         CPLString().Printf("&SUBSET=%s%%28%s,%s%%29", x, a.c_str(), b.c_str());
     218             : 
     219          21 :     a = CPLString().Printf("%.17g", extent[1]);
     220          21 :     if (low.size() > 1 && CPLAtof(low[1].c_str()) > extent[1])
     221             :     {
     222           0 :         a = low[1];
     223             :     }
     224          21 :     b = CPLString().Printf("%.17g", extent[3]);
     225          21 :     if (high.size() > 1 && CPLAtof(high[1].c_str()) < extent[3])
     226             :     {
     227           0 :         b = high[1];
     228             :     }
     229             :     /*
     230             :     a = CPLString().Printf(
     231             :         "%.17g", MAX(adfGeoTransform[3] + nRasterYSize * adfGeoTransform[5],
     232             :     extent[1])); b = CPLString().Printf(
     233             :         "%.17g", MIN(adfGeoTransform[3], extent[3]));
     234             :     */
     235             : 
     236             :     request +=
     237          21 :         CPLString().Printf("&SUBSET=%s%%28%s,%s%%29", y, a.c_str(), b.c_str());
     238             : 
     239             :     // Dimension and range parameters:
     240          42 :     std::vector<std::string> dimensions;
     241          42 :     std::string range;
     242          42 :     std::vector<std::vector<std::string>> others;
     243          21 :     ParseParameters(psService, dimensions, range, others);
     244             : 
     245             :     // set subsets for axis other than x/y
     246          24 :     for (unsigned int i = 0; i < dimensions.size(); ++i)
     247             :     {
     248           3 :         size_t pos = dimensions[i].find("(");
     249           3 :         std::string dim = dimensions[i].substr(0, pos);
     250           3 :         if (IndexOf(dim, domain) != -1)
     251             :         {
     252           0 :             continue;
     253             :         }
     254             :         std::vector<std::string> params =
     255           6 :             Split(FromParenthesis(dimensions[i]).c_str(), ",");
     256             :         request +=
     257           3 :             "&SUBSET" + CPLString().Printf("%i", i) + "=" + dim + "%28";  // (
     258           6 :         for (unsigned int j = 0; j < params.size(); ++j)
     259             :         {
     260             :             // todo: %22 (") should be used only for non-numbers
     261           3 :             request += "%22" + params[j] + "%22";
     262             :         }
     263           3 :         request += "%29";  // )
     264             :     }
     265             : 
     266          21 :     if (scaled)
     267             :     {
     268          14 :         CPLString tmp;
     269             :         // scaling is expressed in grid axes
     270           7 :         if (CPLGetXMLBoolean(psService, "UseScaleFactor"))
     271             :         {
     272           1 :             double fx = fabs((extent[2] - extent[0]) / adfGeoTransform[1] /
     273           1 :                              ((double)nBufXSize + 0.5));
     274           1 :             double fy = fabs((extent[3] - extent[1]) / adfGeoTransform[5] /
     275           1 :                              ((double)nBufYSize + 0.5));
     276           1 :             tmp.Printf("&SCALEFACTOR=%.15g", MIN(fx, fy));
     277             :         }
     278             :         else
     279             :         {
     280             :             std::vector<std::string> grid_axes =
     281          12 :                 Split(CPLGetXMLValue(psService, "GridAxes", ""), ",");
     282           6 :             if (grid_axes.size() < 2)
     283             :             {
     284             :                 // eek!
     285           0 :                 grid_axes.push_back("E");
     286           0 :                 grid_axes.push_back("N");
     287             :             }
     288             :             tmp.Printf("&SCALESIZE=%s%%28%i%%29,%s%%28%i%%29",
     289          12 :                        grid_axes[0].c_str(), nBufXSize, grid_axes[1].c_str(),
     290          12 :                        nBufYSize);
     291             :         }
     292           7 :         request += tmp;
     293             :     }
     294             : 
     295          21 :     if (range != "" && range != "*")
     296             :     {
     297           0 :         request += "&RANGESUBSET=" + range;
     298             :     }
     299             : 
     300             :     // other parameters may come from
     301             :     // 1) URL (others)
     302             :     // 2) Service file
     303          21 :     const char *keys[] = {WCS_URL_PARAMETERS};
     304         231 :     for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
     305             :     {
     306         420 :         std::string value;
     307         210 :         int ix = IndexOf(CPLString(keys[i]).toupper(), others);
     308         210 :         if (ix >= 0)
     309             :         {
     310           0 :             value = others[ix][1];
     311             :         }
     312             :         else
     313             :         {
     314         210 :             value = CPLGetXMLValue(psService, keys[i], "");
     315             :         }
     316         210 :         if (value != "")
     317             :         {
     318          21 :             request = CPLURLAddKVP(request.c_str(), keys[i], value.c_str());
     319             :         }
     320             :     }
     321             :     // add extra parameters
     322          42 :     std::string extra = CPLGetXMLValue(psService, "Parameters", "");
     323          21 :     if (extra != "")
     324             :     {
     325          42 :         std::vector<std::string> pairs = Split(extra.c_str(), "&");
     326          42 :         for (unsigned int i = 0; i < pairs.size(); ++i)
     327             :         {
     328          21 :             std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
     329             :             request =
     330          21 :                 CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
     331             :         }
     332             :     }
     333             :     std::vector<std::string> pairs =
     334          42 :         Split(CPLGetXMLValue(psService, "GetCoverageExtra", ""), "&");
     335          42 :     for (unsigned int i = 0; i < pairs.size(); ++i)
     336             :     {
     337          42 :         std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
     338          21 :         if (pair.size() > 1)
     339             :         {
     340             :             request =
     341          21 :                 CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
     342             :         }
     343             :     }
     344             : 
     345          21 :     CPLDebug("WCS", "Requesting %s", request.c_str());
     346          42 :     return request;
     347             : }
     348             : 
     349             : /************************************************************************/
     350             : /*                        DescribeCoverageRequest()                     */
     351             : /*                                                                      */
     352             : /************************************************************************/
     353             : 
     354           7 : std::string WCSDataset201::DescribeCoverageRequest()
     355             : {
     356           7 :     std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
     357           7 :     request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
     358           7 :     request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
     359          14 :     request = CPLURLAddKVP(request.c_str(), "VERSION",
     360          14 :                            CPLGetXMLValue(psService, "Version", "2.0.1"));
     361          14 :     request = CPLURLAddKVP(request.c_str(), "COVERAGEID",
     362          14 :                            CPLGetXMLValue(psService, "CoverageName", ""));
     363          14 :     std::string extra = CPLGetXMLValue(psService, "Parameters", "");
     364           7 :     if (extra != "")
     365             :     {
     366          14 :         std::vector<std::string> pairs = Split(extra.c_str(), "&");
     367          14 :         for (unsigned int i = 0; i < pairs.size(); ++i)
     368             :         {
     369           7 :             std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
     370             :             request =
     371           7 :                 CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
     372             :         }
     373             :     }
     374           7 :     extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
     375           7 :     if (extra != "")
     376             :     {
     377           0 :         std::vector<std::string> pairs = Split(extra.c_str(), "&");
     378           0 :         for (unsigned int i = 0; i < pairs.size(); ++i)
     379             :         {
     380           0 :             std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
     381             :             request =
     382           0 :                 CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
     383             :         }
     384             :     }
     385           7 :     CPLDebug("WCS", "Requesting %s", request.c_str());
     386          14 :     return request;
     387             : }
     388             : 
     389             : /************************************************************************/
     390             : /*                             GridOffsets()                            */
     391             : /*                                                                      */
     392             : /************************************************************************/
     393             : 
     394          21 : bool WCSDataset201::GridOffsets(CPLXMLNode *grid, const std::string &subtype,
     395             :                                 bool swap_grid_axis,
     396             :                                 std::vector<double> &origin,
     397             :                                 std::vector<std::vector<double>> &offset,
     398             :                                 std::vector<std::string> axes, char ***metadata)
     399             : {
     400             :     // todo: use domain_index
     401             : 
     402             :     // origin position, center of cell
     403          21 :     CPLXMLNode *point = CPLGetXMLNode(grid, "origin.Point.pos");
     404          42 :     origin = Flist(
     405          63 :         Split(CPLGetXMLValue(point, nullptr, ""), " ", axis_order_swap), 0, 2);
     406             : 
     407             :     // offsets = coefficients of affine transformation from cell coords to
     408             :     // CRS coords, (1,2) and (4,5)
     409             : 
     410          21 :     if (subtype == "RectifiedGrid")
     411             :     {
     412             : 
     413             :         // for rectified grid the geo transform is from origin and offsetVectors
     414          18 :         int i = 0;
     415         126 :         for (CPLXMLNode *node = grid->psChild; node != nullptr;
     416         108 :              node = node->psNext)
     417             :         {
     418         126 :             if (node->eType != CXT_Element ||
     419          90 :                 !EQUAL(node->pszValue, "offsetVector"))
     420             :             {
     421          90 :                 continue;
     422             :             }
     423          36 :             offset.push_back(Flist(
     424          72 :                 Split(CPLGetXMLValue(node, nullptr, ""), " ", axis_order_swap),
     425             :                 0, 2));
     426          36 :             if (i == 1)
     427             :             {
     428          18 :                 break;
     429             :             }
     430          18 :             i++;
     431             :         }
     432          18 :         if (offset.size() < 2)
     433             :         {
     434             :             // error or not?
     435           0 :             std::vector<double> x;
     436           0 :             x.push_back(1);
     437           0 :             x.push_back(0);
     438           0 :             std::vector<double> y;
     439           0 :             y.push_back(0);
     440           0 :             y.push_back(1);
     441           0 :             offset.push_back(x);
     442           0 :             offset.push_back(y);
     443             :         }
     444             :         // if axis_order_swap
     445             :         // the offset order should be swapped
     446             :         // Rasdaman does it
     447             :         // MapServer and GeoServer not
     448          18 :         if (swap_grid_axis)
     449             :         {
     450           3 :             std::swap(offset[0], offset[1]);
     451             :         }
     452             :     }
     453             :     else
     454             :     {  // if (coverage_type == "ReferenceableGrid"(ByVector)) {
     455             : 
     456             :         // for vector referenceable grid the geo transform is from
     457             :         // offsetVector, coefficients, gridAxesSpanned, sequenceRule
     458             :         // in generalGridAxis.GeneralGridAxis
     459          33 :         for (CPLXMLNode *node = grid->psChild; node != nullptr;
     460          30 :              node = node->psNext)
     461             :         {
     462          30 :             CPLXMLNode *axis = CPLGetXMLNode(node, "GeneralGridAxis");
     463          30 :             if (!axis)
     464             :             {
     465          21 :                 continue;
     466             :             }
     467           9 :             std::string spanned = CPLGetXMLValue(axis, "gridAxesSpanned", "");
     468           9 :             int index = IndexOf(spanned, axes);
     469           9 :             if (index == -1)
     470             :             {
     471           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     472             :                          "This is not a rectilinear grid(?).");
     473           0 :                 return false;
     474             :             }
     475           9 :             std::string coeffs = CPLGetXMLValue(axis, "coefficients", "");
     476           9 :             if (coeffs != "")
     477             :             {
     478           6 :                 *metadata = CSLSetNameValue(
     479             :                     *metadata,
     480           6 :                     CPLString().Printf("DIMENSION_%i_COEFFS", index).c_str(),
     481             :                     coeffs.c_str());
     482             :             }
     483             :             std::string order =
     484           9 :                 CPLGetXMLValue(axis, "sequenceRule.axisOrder", "");
     485           9 :             std::string rule = CPLGetXMLValue(axis, "sequenceRule", "");
     486           9 :             if (!(order == "+1" && rule == "Linear"))
     487             :             {
     488           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     489             :                          "Grids with sequence rule '%s' and axis order '%s' "
     490             :                          "are not supported.",
     491             :                          rule.c_str(), order.c_str());
     492           0 :                 return false;
     493             :             }
     494           9 :             CPLXMLNode *offset_node = CPLGetXMLNode(axis, "offsetVector");
     495           9 :             if (offset_node)
     496             :             {
     497           9 :                 offset.push_back(
     498          18 :                     Flist(Split(CPLGetXMLValue(offset_node, nullptr, ""), " ",
     499           9 :                                 axis_order_swap),
     500             :                           0, 2));
     501             :             }
     502             :             else
     503             :             {
     504           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     505             :                          "Missing offset vector in grid axis.");
     506           0 :                 return false;
     507             :             }
     508             :         }
     509             :         // todo: make sure offset order is the same as the axes order but see
     510             :         // above
     511             :     }
     512          21 :     if (origin.size() < 2 || offset.size() < 2)
     513             :     {
     514           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     515             :                  "Could not parse origin or offset vectors from grid.");
     516           0 :         return false;
     517             :     }
     518          21 :     return true;
     519             : }
     520             : 
     521             : /************************************************************************/
     522             : /*                             GetSubdataset()                          */
     523             : /*                                                                      */
     524             : /************************************************************************/
     525             : 
     526           0 : std::string WCSDataset201::GetSubdataset(const std::string &coverage)
     527             : {
     528           0 :     char **metadata = GDALPamDataset::GetMetadata("SUBDATASETS");
     529           0 :     std::string subdataset;
     530           0 :     if (metadata != nullptr)
     531             :     {
     532           0 :         for (int i = 0; metadata[i] != nullptr; ++i)
     533             :         {
     534             :             char *key;
     535           0 :             std::string url = CPLParseNameValue(metadata[i], &key);
     536           0 :             if (key != nullptr && strstr(key, "SUBDATASET_") &&
     537           0 :                 strstr(key, "_NAME"))
     538             :             {
     539           0 :                 if (coverage == CPLURLGetValue(url.c_str(), "coverageId"))
     540             :                 {
     541           0 :                     subdataset = key;
     542           0 :                     subdataset.erase(subdataset.find("_NAME"), 5);
     543           0 :                     CPLFree(key);
     544           0 :                     break;
     545             :                 }
     546             :             }
     547           0 :             CPLFree(key);
     548             :         }
     549             :     }
     550           0 :     return subdataset;
     551             : }
     552             : 
     553             : /************************************************************************/
     554             : /*                             SetFormat()                              */
     555             : /*                                                                      */
     556             : /************************************************************************/
     557             : 
     558          21 : bool WCSDataset201::SetFormat(CPLXMLNode *coverage)
     559             : {
     560             :     // set the Format value in service,
     561             :     // unless it is set by the user
     562          42 :     std::string format = CPLGetXMLValue(psService, "Format", "");
     563             : 
     564             :     // todo: check the value against list of supported formats?
     565          21 :     if (format != "")
     566             :     {
     567          14 :         return true;
     568             :     }
     569             : 
     570             :     /*      We will prefer anything that sounds like TIFF, otherwise        */
     571             :     /*      falling back to the first supported format.  Should we          */
     572             :     /*      consider preferring the nativeFormat if available?              */
     573             : 
     574           7 :     char **metadata = GDALPamDataset::GetMetadata(nullptr);
     575             :     const char *value =
     576           7 :         CSLFetchNameValue(metadata, "WCS_GLOBAL#formatSupported");
     577           7 :     if (value == nullptr)
     578             :     {
     579           0 :         format = CPLGetXMLValue(coverage, "ServiceParameters.nativeFormat", "");
     580             :     }
     581             :     else
     582             :     {
     583          14 :         std::vector<std::string> format_list = Split(value, ",");
     584          32 :         for (unsigned j = 0; j < format_list.size(); ++j)
     585             :         {
     586          32 :             if (CPLString(format_list[j]).ifind("tiff") != std::string::npos)
     587             :             {
     588           7 :                 format = format_list[j];
     589           7 :                 break;
     590             :             }
     591             :         }
     592           7 :         if (format == "" && format_list.size() > 0)
     593             :         {
     594           0 :             format = format_list[0];
     595             :         }
     596             :     }
     597           7 :     if (format != "")
     598             :     {
     599           7 :         CPLSetXMLValue(psService, "Format", format.c_str());
     600           7 :         bServiceDirty = true;
     601           7 :         return true;
     602             :     }
     603             :     else
     604             :     {
     605           0 :         return false;
     606             :     }
     607             : }
     608             : 
     609             : /************************************************************************/
     610             : /*                         ParseGridFunction()                          */
     611             : /*                                                                      */
     612             : /************************************************************************/
     613             : 
     614          21 : bool WCSDataset201::ParseGridFunction(CPLXMLNode *coverage,
     615             :                                       std::vector<int> &axisOrder)
     616             : {
     617             :     CPLXMLNode *function =
     618          21 :         CPLGetXMLNode(coverage, "coverageFunction.GridFunction");
     619          21 :     if (function)
     620             :     {
     621          15 :         std::string path = "sequenceRule";
     622          15 :         std::string sequenceRule = CPLGetXMLValue(function, path.c_str(), "");
     623          15 :         path += ".axisOrder";
     624             :         axisOrder =
     625          15 :             Ilist(Split(CPLGetXMLValue(function, path.c_str(), ""), " "));
     626             :         // for now require simple
     627          15 :         if (sequenceRule != "Linear")
     628             :         {
     629           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     630             :                      "Can't handle '%s' coverages.", sequenceRule.c_str());
     631           0 :             return false;
     632             :         }
     633             :     }
     634          21 :     return true;
     635             : }
     636             : 
     637             : /************************************************************************/
     638             : /*                             ParseRange()                             */
     639             : /*                                                                      */
     640             : /************************************************************************/
     641             : 
     642          21 : int WCSDataset201::ParseRange(CPLXMLNode *coverage,
     643             :                               const std::string &range_subset, char ***metadata)
     644             : {
     645          21 :     int fields = 0;
     646             :     // Default is to include all (types permitting?)
     647             :     // Can also be controlled with Range parameter
     648             : 
     649             :     // The contents of a rangeType is a swe:DataRecord
     650          21 :     const char *path = "rangeType.DataRecord";
     651          21 :     CPLXMLNode *record = CPLGetXMLNode(coverage, path);
     652          21 :     if (!record)
     653             :     {
     654           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     655             :                  "Attributes are not defined in a DataRecord, giving up.");
     656           0 :         return 0;
     657             :     }
     658             : 
     659             :     // mapserver does not like field names, it wants indexes
     660             :     // so we should be able to give those
     661             : 
     662             :     // if Range is set remove those not in it
     663          42 :     std::vector<std::string> range = Split(range_subset.c_str(), ",");
     664             :     // todo: add check for range subsetting profile existence in server metadata
     665             :     // here
     666          21 :     unsigned int range_index = 0;  // index for reading from range
     667          21 :     bool in_band_range = false;
     668             : 
     669          21 :     unsigned int field_index = 1;
     670          21 :     std::vector<std::string> nodata_array;
     671             : 
     672          63 :     for (CPLXMLNode *field = record->psChild; field != nullptr;
     673          42 :          field = field->psNext)
     674             :     {
     675          42 :         if (field->eType != CXT_Element || !EQUAL(field->pszValue, "field"))
     676             :         {
     677           0 :             continue;
     678             :         }
     679          42 :         std::string fname = CPLGetXMLValue(field, "name", "");
     680          42 :         bool include = true;
     681             : 
     682          42 :         if (range.size() > 0)
     683             :         {
     684           0 :             include = false;
     685           0 :             if (range_index < range.size())
     686             :             {
     687           0 :                 std::string current_range = range[range_index];
     688           0 :                 std::string fname_test;
     689             : 
     690           0 :                 if (atoi(current_range.c_str()) != 0)
     691             :                 {
     692           0 :                     fname_test = CPLString().Printf("%i", field_index);
     693             :                 }
     694             :                 else
     695             :                 {
     696           0 :                     fname_test = fname;
     697             :                 }
     698             : 
     699           0 :                 if (current_range == "*")
     700             :                 {
     701           0 :                     include = true;
     702             :                 }
     703           0 :                 else if (current_range == fname_test)
     704             :                 {
     705           0 :                     include = true;
     706           0 :                     range_index += 1;
     707             :                 }
     708           0 :                 else if (current_range.find(fname_test + ":") !=
     709             :                          std::string::npos)
     710             :                 {
     711           0 :                     include = true;
     712           0 :                     in_band_range = true;
     713             :                 }
     714           0 :                 else if (current_range.find(":" + fname_test) !=
     715             :                          std::string::npos)
     716             :                 {
     717           0 :                     include = true;
     718           0 :                     in_band_range = false;
     719           0 :                     range_index += 1;
     720             :                 }
     721           0 :                 else if (in_band_range)
     722             :                 {
     723           0 :                     include = true;
     724             :                 }
     725             :             }
     726             :         }
     727             : 
     728          42 :         if (include)
     729             :         {
     730             :             const std::string key =
     731          84 :                 CPLString().Printf("FIELD_%i_", field_index);
     732          42 :             *metadata = CSLSetNameValue(*metadata, (key + "NAME").c_str(),
     733             :                                         fname.c_str());
     734             : 
     735             :             std::string nodata =
     736          84 :                 CPLGetXMLValue(field, "Quantity.nilValues.NilValue", "");
     737          42 :             if (nodata != "")
     738             :             {
     739           0 :                 *metadata = CSLSetNameValue(*metadata, (key + "NODATA").c_str(),
     740             :                                             nodata.c_str());
     741             :             }
     742             : 
     743             :             std::string descr =
     744          84 :                 CPLGetXMLValue(field, "Quantity.description", "");
     745          42 :             if (descr != "")
     746             :             {
     747          18 :                 *metadata = CSLSetNameValue(*metadata, (key + "DESCR").c_str(),
     748             :                                             descr.c_str());
     749             :             }
     750             : 
     751          42 :             path = "Quantity.constraint.AllowedValues.interval";
     752          42 :             std::string interval = CPLGetXMLValue(field, path, "");
     753          42 :             if (interval != "")
     754             :             {
     755          54 :                 *metadata = CSLSetNameValue(
     756          54 :                     *metadata, (key + "INTERVAL").c_str(), interval.c_str());
     757             :             }
     758             : 
     759          42 :             nodata_array.push_back(nodata);
     760          42 :             fields += 1;
     761             :         }
     762             : 
     763          42 :         field_index += 1;
     764             :     }
     765             : 
     766          21 :     if (fields == 0)
     767             :     {
     768           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     769             :                  "No data fields found (bad Range?).");
     770             :     }
     771             :     else
     772             :     {
     773             :         // todo: default to the first one?
     774          21 :         bServiceDirty = CPLUpdateXML(psService, "NoDataValue",
     775          59 :                                      Join(nodata_array, ",").c_str()) ||
     776          17 :                         bServiceDirty;
     777             :     }
     778             : 
     779          21 :     return fields;
     780             : }
     781             : 
     782             : /************************************************************************/
     783             : /*                          ExtractGridInfo()                           */
     784             : /*                                                                      */
     785             : /*      Collect info about grid from describe coverage for WCS 2.0.     */
     786             : /*                                                                      */
     787             : /************************************************************************/
     788             : 
     789          21 : bool WCSDataset201::ExtractGridInfo()
     790             : {
     791             :     // this is for checking what's in service and for filling in empty slots in
     792             :     // it if the service file can be considered ready for use, this could be
     793             :     // skipped
     794             : 
     795          21 :     CPLXMLNode *coverage = CPLGetXMLNode(psService, "CoverageDescription");
     796             : 
     797          21 :     if (coverage == nullptr)
     798             :     {
     799           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     800             :                  "CoverageDescription missing from service.");
     801           0 :         return false;
     802             :     }
     803             : 
     804          42 :     std::string subtype = CoverageSubtype(coverage);
     805             : 
     806             :     // get CRS from boundedBy.Envelope and set the native flag to true
     807             :     // below we may set the CRS again but that won't be native (however, non
     808             :     // native CRS is not yet supported) also axis order swap is set
     809          42 :     std::string path = "boundedBy.Envelope";
     810          21 :     CPLXMLNode *envelope = CPLGetXMLNode(coverage, path.c_str());
     811          21 :     if (envelope == nullptr)
     812             :     {
     813           0 :         path = "boundedBy.EnvelopeWithTimePeriod";
     814           0 :         envelope = CPLGetXMLNode(coverage, path.c_str());
     815           0 :         if (envelope == nullptr)
     816             :         {
     817           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Missing boundedBy.Envelope");
     818           0 :             return false;
     819             :         }
     820             :     }
     821          42 :     std::vector<std::string> bbox = ParseBoundingBox(envelope);
     822          21 :     if (!SetCRS(ParseCRS(envelope), true) || bbox.size() < 2)
     823             :     {
     824           0 :         return false;
     825             :     }
     826             : 
     827             :     // has the user set the domain?
     828             :     std::vector<std::string> domain =
     829          42 :         Split(CPLGetXMLValue(psService, "Domain", ""), ",");
     830             : 
     831             :     // names of axes
     832             :     std::vector<std::string> axes =
     833           0 :         Split(CPLGetXMLValue(coverage, (path + ".axisLabels").c_str(), ""), " ",
     834          42 :               axis_order_swap);
     835             :     std::vector<std::string> uoms =
     836           0 :         Split(CPLGetXMLValue(coverage, (path + ".uomLabels").c_str(), ""), " ",
     837          42 :               axis_order_swap);
     838             : 
     839          21 :     if (axes.size() < 2)
     840             :     {
     841           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     842             :                  "The coverage has less than 2 dimensions or no axisLabels.");
     843           0 :         return false;
     844             :     }
     845             : 
     846          42 :     std::vector<int> domain_indexes = IndexOf(domain, axes);
     847          21 :     if (Contains(domain_indexes, -1))
     848             :     {
     849           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     850             :                  "Axis in given domain does not exist in coverage.");
     851           0 :         return false;
     852             :     }
     853          21 :     if (domain_indexes.size() == 0)
     854             :     {  // default is the first two
     855           7 :         domain_indexes.push_back(0);
     856           7 :         domain_indexes.push_back(1);
     857             :     }
     858          21 :     if (domain.size() == 0)
     859             :     {
     860           7 :         domain.push_back(axes[0]);
     861           7 :         domain.push_back(axes[1]);
     862           7 :         CPLSetXMLValue(psService, "Domain", Join(domain, ",").c_str());
     863           7 :         bServiceDirty = true;
     864             :     }
     865             : 
     866             :     // GridFunction (is optional)
     867             :     // We support only linear grid functions.
     868             :     // axisOrder determines how data is arranged in the grid <order><axis
     869             :     // number> specifically: +2 +1 => swap grid envelope and the order of the
     870             :     // offsets
     871          42 :     std::vector<int> axisOrder;
     872          21 :     if (!ParseGridFunction(coverage, axisOrder))
     873             :     {
     874           0 :         return false;
     875             :     }
     876             : 
     877          21 :     const char *md_domain = "";
     878          63 :     char **metadata = CSLDuplicate(
     879          21 :         GetMetadata(md_domain));  // coverage metadata to be added/updated
     880             : 
     881          21 :     metadata = CSLSetNameValue(metadata, "DOMAIN", Join(domain, ",").c_str());
     882             : 
     883             :     // add coverage metadata: GeoServer TimeDomain
     884             : 
     885             :     CPLXMLNode *timedomain =
     886          21 :         CPLGetXMLNode(coverage, "metadata.Extension.TimeDomain");
     887          21 :     if (timedomain)
     888             :     {
     889           0 :         std::vector<std::string> timePositions;
     890             :         // "//timePosition"
     891           0 :         for (CPLXMLNode *node = timedomain->psChild; node != nullptr;
     892           0 :              node = node->psNext)
     893             :         {
     894           0 :             if (node->eType != CXT_Element ||
     895           0 :                 strcmp(node->pszValue, "TimeInstant") != 0)
     896             :             {
     897           0 :                 continue;
     898             :             }
     899           0 :             for (CPLXMLNode *node2 = node->psChild; node2 != nullptr;
     900           0 :                  node2 = node2->psNext)
     901             :             {
     902           0 :                 if (node2->eType != CXT_Element ||
     903           0 :                     strcmp(node2->pszValue, "timePosition") != 0)
     904             :                 {
     905           0 :                     continue;
     906             :                 }
     907           0 :                 timePositions.push_back(CPLGetXMLValue(node2, "", ""));
     908             :             }
     909             :         }
     910           0 :         metadata = CSLSetNameValue(metadata, "TimeDomain",
     911           0 :                                    Join(timePositions, ",").c_str());
     912             :     }
     913             : 
     914             :     // dimension metadata
     915             : 
     916             :     std::vector<std::string> slow =
     917          42 :         Split(bbox[0].c_str(), " ", axis_order_swap);
     918             :     std::vector<std::string> shigh =
     919          42 :         Split(bbox[1].c_str(), " ", axis_order_swap);
     920          35 :     bServiceDirty = CPLUpdateXML(psService, "Low", Join(slow, ",").c_str()) ||
     921          14 :                     bServiceDirty;
     922          35 :     bServiceDirty = CPLUpdateXML(psService, "High", Join(shigh, ",").c_str()) ||
     923          14 :                     bServiceDirty;
     924          21 :     if (slow.size() < 2 || shigh.size() < 2)
     925             :     {
     926           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     927             :                  "The coverage has less than 2 dimensions.");
     928           0 :         CSLDestroy(metadata);
     929           0 :         return false;
     930             :     }
     931             :     // todo: if our x,y domain is not the first two? use domain_indexes?
     932          42 :     std::vector<double> low = Flist(slow, 0, 2);
     933          42 :     std::vector<double> high = Flist(shigh, 0, 2);
     934          42 :     std::vector<double> env;
     935          21 :     env.insert(env.end(), low.begin(), low.begin() + 2);
     936          21 :     env.insert(env.end(), high.begin(), high.begin() + 2);
     937             : 
     938          66 :     for (unsigned int i = 0; i < axes.size(); ++i)
     939             :     {
     940          90 :         const std::string key = CPLString().Printf("DIMENSION_%i_", i);
     941          45 :         metadata =
     942          45 :             CSLSetNameValue(metadata, (key + "AXIS").c_str(), axes[i].c_str());
     943          45 :         if (i < uoms.size())
     944             :         {
     945          39 :             metadata = CSLSetNameValue(metadata, (key + "UOM").c_str(),
     946          39 :                                        uoms[i].c_str());
     947             :         }
     948          45 :         if (i < 2)
     949             :         {
     950          42 :             metadata = CSLSetNameValue(
     951          84 :                 metadata, (key + "INTERVAL").c_str(),
     952          84 :                 CPLString().Printf("%.15g,%.15g", low[i], high[i]));
     953             :         }
     954           3 :         else if (i < slow.size() && i < shigh.size())
     955             :         {
     956           3 :             metadata = CSLSetNameValue(
     957           6 :                 metadata, (key + "INTERVAL").c_str(),
     958           6 :                 CPLString().Printf("%s,%s", slow[i].c_str(), shigh[i].c_str()));
     959             :         }
     960           0 :         else if (i < bbox.size())
     961             :         {
     962           0 :             metadata = CSLSetNameValue(metadata, (key + "INTERVAL").c_str(),
     963           0 :                                        bbox[i].c_str());
     964             :         }
     965             :     }
     966             : 
     967             :     // domainSet
     968             :     // requirement 23: the srsName here _shall_ be the same as in boundedBy
     969             :     // => we ignore it
     970             :     // the CRS of this dataset is from boundedBy (unless it is overridden)
     971             :     // this is the size of this dataset
     972             :     // this gives the geotransform of this dataset (unless there is CRS
     973             :     // override)
     974             : 
     975          21 :     CPLXMLNode *grid = GetGridNode(coverage, subtype);
     976          21 :     if (!grid)
     977             :     {
     978           0 :         CSLDestroy(metadata);
     979           0 :         return false;
     980             :     }
     981             : 
     982             :     //
     983          21 :     bool swap_grid_axis = false;
     984          30 :     if (axisOrder.size() >= 2 && axisOrder[domain_indexes[0]] == 2 &&
     985           9 :         axisOrder[domain_indexes[1]] == 1)
     986             :     {
     987           9 :         swap_grid_axis = !CPLGetXMLBoolean(psService, "NoGridAxisSwap");
     988             :     }
     989          21 :     path = "limits.GridEnvelope";
     990             :     std::vector<std::vector<int>> size =
     991          42 :         ParseGridEnvelope(CPLGetXMLNode(grid, path.c_str()), swap_grid_axis);
     992          42 :     std::vector<int> grid_size;
     993          21 :     if (size.size() < 2)
     994             :     {
     995           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Can't parse the grid envelope.");
     996           0 :         CSLDestroy(metadata);
     997           0 :         return false;
     998             :     }
     999             : 
    1000          21 :     grid_size.push_back(size[1][domain_indexes[0]] -
    1001          21 :                         size[0][domain_indexes[0]] + 1);
    1002          21 :     grid_size.push_back(size[1][domain_indexes[1]] -
    1003          21 :                         size[0][domain_indexes[1]] + 1);
    1004             : 
    1005          21 :     path = "axisLabels";
    1006             :     bool swap_grid_axis_labels =
    1007          21 :         swap_grid_axis || CPLGetXMLBoolean(psService, "GridAxisLabelSwap");
    1008             :     std::vector<std::string> grid_axes = Split(
    1009          42 :         CPLGetXMLValue(grid, path.c_str(), ""), " ", swap_grid_axis_labels);
    1010             :     // autocorrect MapServer thing
    1011          24 :     if (grid_axes.size() >= 2 && grid_axes[0] == "lat" &&
    1012           3 :         grid_axes[1] == "long")
    1013             :     {
    1014           3 :         grid_axes[0] = "long";
    1015           3 :         grid_axes[1] = "lat";
    1016             :     }
    1017          21 :     bServiceDirty =
    1018          35 :         CPLUpdateXML(psService, "GridAxes", Join(grid_axes, ",").c_str()) ||
    1019          14 :         bServiceDirty;
    1020             : 
    1021          42 :     std::vector<double> origin;
    1022          42 :     std::vector<std::vector<double>> offsets;
    1023          21 :     if (!GridOffsets(grid, subtype, swap_grid_axis, origin, offsets, axes,
    1024             :                      &metadata))
    1025             :     {
    1026           0 :         CSLDestroy(metadata);
    1027           0 :         return false;
    1028             :     }
    1029             : 
    1030          21 :     SetGeometry(grid_size, origin, offsets);
    1031             : 
    1032             :     // subsetting and dimension to bands
    1033          42 :     std::vector<std::string> dimensions;
    1034          42 :     std::string range;
    1035          42 :     std::vector<std::vector<std::string>> others;
    1036          21 :     ParseParameters(psService, dimensions, range, others);
    1037             : 
    1038             :     // it is ok to have trimming or even slicing for x/y, it just affects our
    1039             :     // bounding box but that is a todo item todo: BoundGeometry(domain_trim) if
    1040             :     // domain_trim.size() > 0
    1041          42 :     std::vector<std::vector<double>> domain_trim;
    1042             : 
    1043             :     // are all dimensions that are not x/y domain sliced?
    1044             :     // if not, bands can't be defined, see below
    1045          21 :     bool dimensions_are_ok = true;
    1046          66 :     for (unsigned int i = 0; i < axes.size(); ++i)
    1047             :     {
    1048          45 :         std::vector<std::string> params;
    1049          51 :         for (unsigned int j = 0; j < dimensions.size(); ++j)
    1050             :         {
    1051           9 :             if (dimensions[j].find(axes[i] + "(") != std::string::npos)
    1052             :             {
    1053           3 :                 params = Split(FromParenthesis(dimensions[j]).c_str(), ",");
    1054           3 :                 break;
    1055             :             }
    1056             :         }
    1057          45 :         int domain_index = IndexOf(axes[i], domain);
    1058          45 :         if (domain_index != -1)
    1059             :         {
    1060          84 :             std::vector<double> trim = Flist(params, 0, 2);
    1061          42 :             domain_trim.push_back(trim);
    1062          42 :             continue;
    1063             :         }
    1064             :         // size == 1 => sliced
    1065           3 :         if (params.size() != 1)
    1066             :         {
    1067           0 :             dimensions_are_ok = false;
    1068             :         }
    1069             :     }
    1070             :     // todo: add metadata: note: no bands, you need to subset to get data
    1071             : 
    1072             :     // check for CRS override
    1073          42 :     std::string crs = CPLGetXMLValue(psService, "SRS", "");
    1074          21 :     if (crs != "" && crs != osCRS)
    1075             :     {
    1076           0 :         if (!SetCRS(crs, false))
    1077             :         {
    1078           0 :             CSLDestroy(metadata);
    1079           0 :             return false;
    1080             :         }
    1081             :         // todo: support CRS override, it requires warping the grid to the new
    1082             :         // CRS SetGeometry(grid_size, origin, offsets);
    1083           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1084             :                  "CRS override not yet supported.");
    1085           0 :         CSLDestroy(metadata);
    1086           0 :         return false;
    1087             :     }
    1088             : 
    1089             :     // todo: ElevationDomain, DimensionDomain
    1090             : 
    1091             :     // rangeType
    1092             : 
    1093             :     // get the field metadata
    1094             :     // get the count of fields
    1095             :     // if Range is set in service that may limit the fields
    1096          21 :     int fields = ParseRange(coverage, range, &metadata);
    1097             :     // if fields is 0 an error message has been emitted
    1098             :     // but we let this go on since the user may be experimenting
    1099             :     // and she wants to see the resulting metadata and not just an error message
    1100             :     // situation is ~the same when bands == 0 when we exit here
    1101             : 
    1102             :     // todo: do this only if metadata is dirty
    1103          21 :     this->SetMetadata(metadata, md_domain);
    1104          21 :     CSLDestroy(metadata);
    1105          21 :     TrySaveXML();
    1106             : 
    1107             :     // determine the band count
    1108          21 :     int bands = 0;
    1109          21 :     if (dimensions_are_ok)
    1110             :     {
    1111          21 :         bands = fields;
    1112             :     }
    1113          21 :     bServiceDirty =
    1114          35 :         CPLUpdateXML(psService, "BandCount", CPLString().Printf("%d", bands)) ||
    1115          14 :         bServiceDirty;
    1116             : 
    1117             :     // set the Format value in service, unless it is set
    1118             :     // by the user, either through direct edit or options
    1119          21 :     if (!SetFormat(coverage))
    1120             :     {
    1121             :         // all attempts to find a format have failed...
    1122           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1123             :                  "All attempts to find a format have failed, giving up.");
    1124           0 :         return false;
    1125             :     }
    1126             : 
    1127          21 :     return true;
    1128             : }

Generated by: LCOV version 1.14