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

Generated by: LCOV version 1.14