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