/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 | } |