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