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