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