Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: WCS Client Driver
4 : * Purpose: Implementation of Dataset class for WCS 1.1.
5 : * Author: Frank Warmerdam, warmerdam@pobox.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 "gmlutils.h"
20 : #include "gdal_frmts.h"
21 : #include "gdal_pam.h"
22 : #include "ogr_spatialref.h"
23 : #include "gmlcoverage.h"
24 :
25 : #include <algorithm>
26 :
27 : #include "wcsdataset.h"
28 : #include "wcsutils.h"
29 :
30 : using namespace WCSUtils;
31 :
32 : /************************************************************************/
33 : /* GetExtent() */
34 : /* */
35 : /************************************************************************/
36 :
37 36 : std::vector<double> WCSDataset110::GetExtent(int nXOff, int nYOff, int nXSize,
38 : int nYSize,
39 : CPL_UNUSED int nBufXSize,
40 : CPL_UNUSED int nBufYSize)
41 : {
42 36 : std::vector<double> extent;
43 :
44 : // outer edges of outer pixels.
45 36 : extent.push_back(adfGeoTransform[0] + (nXOff)*adfGeoTransform[1]);
46 0 : extent.push_back(adfGeoTransform[3] +
47 36 : (nYOff + nYSize) * adfGeoTransform[5]);
48 0 : extent.push_back(adfGeoTransform[0] +
49 36 : (nXOff + nXSize) * adfGeoTransform[1]);
50 36 : extent.push_back(adfGeoTransform[3] + (nYOff)*adfGeoTransform[5]);
51 :
52 36 : bool no_shrink = CPLGetXMLBoolean(psService, "OuterExtents");
53 :
54 : // WCS 1.1 extents are centers of outer pixels.
55 36 : if (!no_shrink)
56 : {
57 18 : extent[2] -= adfGeoTransform[1] * 0.5;
58 18 : extent[0] += adfGeoTransform[1] * 0.5;
59 18 : extent[1] -= adfGeoTransform[5] * 0.5;
60 18 : extent[3] += adfGeoTransform[5] * 0.5;
61 : }
62 :
63 : double dfXStep, dfYStep;
64 :
65 36 : if (!no_shrink)
66 : {
67 18 : dfXStep = (nXSize / (double)nBufXSize) * adfGeoTransform[1];
68 18 : dfYStep = (nYSize / (double)nBufYSize) * adfGeoTransform[5];
69 : // Carefully adjust bounds for pixel centered values at new
70 : // sampling density.
71 18 : if (nBufXSize != nXSize || nBufYSize != nYSize)
72 : {
73 6 : dfXStep = (nXSize / (double)nBufXSize) * adfGeoTransform[1];
74 6 : dfYStep = (nYSize / (double)nBufYSize) * adfGeoTransform[5];
75 :
76 12 : extent[0] =
77 6 : nXOff * adfGeoTransform[1] + adfGeoTransform[0] + dfXStep * 0.5;
78 6 : extent[2] = extent[0] + (nBufXSize - 1) * dfXStep;
79 :
80 12 : extent[3] =
81 6 : nYOff * adfGeoTransform[5] + adfGeoTransform[3] + dfYStep * 0.5;
82 6 : extent[1] = extent[3] + (nBufYSize - 1) * dfYStep;
83 : }
84 : }
85 : else
86 : {
87 : double adjust =
88 18 : CPLAtof(CPLGetXMLValue(psService, "BufSizeAdjust", "0.0"));
89 18 : dfXStep = (nXSize / ((double)nBufXSize + adjust)) * adfGeoTransform[1];
90 18 : dfYStep = (nYSize / ((double)nBufYSize + adjust)) * adfGeoTransform[5];
91 : }
92 :
93 36 : extent.push_back(dfXStep);
94 36 : extent.push_back(dfYStep);
95 :
96 72 : return extent;
97 : }
98 :
99 : /************************************************************************/
100 : /* GetCoverageRequest() */
101 : /* */
102 : /************************************************************************/
103 :
104 36 : std::string WCSDataset110::GetCoverageRequest(bool scaled, int /* nBufXSize */,
105 : int /* nBufYSize */,
106 : const std::vector<double> &extent,
107 : const std::string &osBandList)
108 : {
109 72 : CPLString osRequest;
110 :
111 : /* -------------------------------------------------------------------- */
112 : /* URL encode strings that could have questionable characters. */
113 : /* -------------------------------------------------------------------- */
114 72 : CPLString osCoverage = CPLGetXMLValue(psService, "CoverageName", "");
115 :
116 36 : char *pszEncoded = CPLEscapeString(osCoverage, -1, CPLES_URL);
117 36 : osCoverage = pszEncoded;
118 36 : CPLFree(pszEncoded);
119 :
120 72 : CPLString osFormat = CPLGetXMLValue(psService, "PreferredFormat", "");
121 :
122 36 : pszEncoded = CPLEscapeString(osFormat, -1, CPLES_URL);
123 36 : osFormat = pszEncoded;
124 36 : CPLFree(pszEncoded);
125 :
126 72 : CPLString osRangeSubset = CPLGetXMLValue(psService, "FieldName", "");
127 :
128 : // todo: MapServer seems to require interpolation
129 :
130 72 : CPLString interpolation = CPLGetXMLValue(psService, "Interpolation", "");
131 36 : if (interpolation == "")
132 : {
133 : // old undocumented key for interpolation in service
134 36 : interpolation = CPLGetXMLValue(psService, "Resample", "");
135 : }
136 36 : if (interpolation != "")
137 : {
138 0 : osRangeSubset += ":" + interpolation;
139 : }
140 :
141 36 : if (osBandList != "")
142 : {
143 0 : if (osBandIdentifier != "")
144 : {
145 0 : osRangeSubset += CPLString().Printf(
146 0 : "[%s[%s]]", osBandIdentifier.c_str(), osBandList.c_str());
147 : }
148 : }
149 :
150 36 : osRangeSubset = "&RangeSubset=" + URLEncode(osRangeSubset);
151 :
152 36 : double bbox_0 = extent[0], // min X
153 36 : bbox_1 = extent[1], // min Y
154 36 : bbox_2 = extent[2], // max X
155 36 : bbox_3 = extent[3]; // max Y
156 :
157 36 : if (axis_order_swap)
158 : {
159 21 : bbox_0 = extent[1]; // min Y
160 21 : bbox_1 = extent[0]; // min X
161 21 : bbox_2 = extent[3]; // max Y
162 21 : bbox_3 = extent[2]; // max X
163 : }
164 36 : std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
165 36 : request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
166 72 : request += CPLString().Printf(
167 : "&VERSION=%s&REQUEST=GetCoverage&IDENTIFIER=%s"
168 : "&FORMAT=%s&BOUNDINGBOX=%.15g,%.15g,%.15g,%.15g,%s%s",
169 36 : CPLGetXMLValue(psService, "Version", ""), osCoverage.c_str(),
170 : osFormat.c_str(), bbox_0, bbox_1, bbox_2, bbox_3, osCRS.c_str(),
171 36 : osRangeSubset.c_str());
172 36 : double origin_1 = extent[0], // min X
173 36 : origin_2 = extent[3], // max Y
174 36 : offset_1 = extent[4], // dX
175 36 : offset_2 = extent[5]; // dY
176 :
177 36 : if (axis_order_swap)
178 : {
179 21 : origin_1 = extent[3]; // max Y
180 21 : origin_2 = extent[0]; // min X
181 21 : offset_1 = extent[5]; // dY
182 21 : offset_2 = extent[4]; // dX
183 : }
184 72 : CPLString offsets;
185 36 : if (CPLGetXMLBoolean(psService, "OffsetsPositive"))
186 : {
187 9 : offset_1 = fabs(offset_1);
188 9 : offset_2 = fabs(offset_2);
189 : }
190 36 : if (EQUAL(CPLGetXMLValue(psService, "NrOffsets", "4"), "2"))
191 : {
192 18 : offsets = CPLString().Printf("%.15g,%.15g", offset_1, offset_2);
193 : }
194 : else
195 : {
196 18 : if (axis_order_swap)
197 : {
198 : // Only tested with GeoServer but this is the correct offset(?)
199 12 : offsets = CPLString().Printf("0,%.15g,%.15g,0", offset_2, offset_1);
200 : }
201 : else
202 : {
203 6 : offsets = CPLString().Printf("%.15g,0,0,%.15g", offset_1, offset_2);
204 : }
205 : }
206 : bool do_not_include =
207 36 : CPLGetXMLBoolean(psService, "GridCRSOptional") && !scaled;
208 36 : if (!do_not_include)
209 : {
210 72 : request += CPLString().Printf(
211 : "&GridBaseCRS=%s"
212 : "&GridCS=urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS"
213 : "&GridType=urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs"
214 : "&GridOrigin=%.15g,%.15g"
215 : "&GridOffsets=%s",
216 36 : osCRS.c_str(), origin_1, origin_2, offsets.c_str());
217 : }
218 72 : CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
219 36 : if (extra != "")
220 : {
221 72 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
222 72 : for (unsigned int i = 0; i < pairs.size(); ++i)
223 : {
224 36 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
225 : request =
226 36 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
227 : }
228 : }
229 36 : extra = CPLGetXMLValue(psService, "GetCoverageExtra", "");
230 36 : if (extra != "")
231 : {
232 72 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
233 72 : for (unsigned int i = 0; i < pairs.size(); ++i)
234 : {
235 36 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
236 : request =
237 36 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
238 : }
239 : }
240 36 : CPLDebug("WCS", "Requesting %s", request.c_str());
241 72 : return request;
242 : }
243 :
244 : /************************************************************************/
245 : /* DescribeCoverageRequest() */
246 : /* */
247 : /************************************************************************/
248 :
249 12 : std::string WCSDataset110::DescribeCoverageRequest()
250 : {
251 12 : std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
252 12 : request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
253 12 : request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
254 24 : request = CPLURLAddKVP(request.c_str(), "VERSION",
255 24 : CPLGetXMLValue(psService, "Version", "1.1.0"));
256 24 : request = CPLURLAddKVP(request.c_str(), "IDENTIFIERS",
257 24 : CPLGetXMLValue(psService, "CoverageName", ""));
258 24 : CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
259 12 : if (extra != "")
260 : {
261 24 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
262 24 : for (unsigned int i = 0; i < pairs.size(); ++i)
263 : {
264 12 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
265 : request =
266 12 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
267 : }
268 : }
269 12 : extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
270 12 : if (extra != "")
271 : {
272 0 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
273 0 : for (unsigned int i = 0; i < pairs.size(); ++i)
274 : {
275 0 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
276 : request =
277 0 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
278 : }
279 : }
280 24 : return request;
281 : }
282 :
283 : /************************************************************************/
284 : /* CoverageOffering() */
285 : /* */
286 : /************************************************************************/
287 :
288 19 : CPLXMLNode *WCSDataset110::CoverageOffering(CPLXMLNode *psDC)
289 : {
290 19 : return CPLGetXMLNode(psDC, "=CoverageDescriptions.CoverageDescription");
291 : }
292 :
293 : /************************************************************************/
294 : /* ExtractGridInfo() */
295 : /* */
296 : /* Collect info about grid from describe coverage for WCS 1.1. */
297 : /* */
298 : /************************************************************************/
299 :
300 36 : bool WCSDataset110::ExtractGridInfo()
301 :
302 : {
303 36 : CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageDescription");
304 :
305 36 : if (psCO == nullptr)
306 0 : return false;
307 :
308 : /* -------------------------------------------------------------------- */
309 : /* We need to strip off name spaces so it is easier to */
310 : /* searchfor plain gml names. */
311 : /* -------------------------------------------------------------------- */
312 36 : CPLStripXMLNamespace(psCO, nullptr, TRUE);
313 :
314 : /* -------------------------------------------------------------------- */
315 : /* Verify we have a SpatialDomain and GridCRS. */
316 : /* -------------------------------------------------------------------- */
317 36 : CPLXMLNode *psSD = CPLGetXMLNode(psCO, "Domain.SpatialDomain");
318 36 : CPLXMLNode *psGCRS = CPLGetXMLNode(psSD, "GridCRS");
319 :
320 36 : if (psSD == nullptr || psGCRS == nullptr)
321 : {
322 0 : CPLError(CE_Failure, CPLE_AppDefined,
323 : "Unable to find GridCRS in CoverageDescription,\n"
324 : "unable to process WCS Coverage.");
325 0 : return false;
326 : }
327 :
328 : /* -------------------------------------------------------------------- */
329 : /* Establish our coordinate system. */
330 : /* This is needed before geometry since we may have axis order swap. */
331 : /* -------------------------------------------------------------------- */
332 72 : CPLString crs = ParseCRS(psGCRS);
333 :
334 36 : if (crs.empty())
335 : {
336 0 : CPLError(CE_Failure, CPLE_AppDefined,
337 : "Unable to find GridCRS.GridBaseCRS");
338 0 : return false;
339 : }
340 :
341 : // SetCRS should fail only if the CRS is really unknown to GDAL
342 36 : if (!SetCRS(crs, true))
343 : {
344 0 : CPLError(CE_Failure, CPLE_AppDefined,
345 : "Unable to interpret GridBaseCRS '%s'.", crs.c_str());
346 0 : return false;
347 : }
348 :
349 : /* -------------------------------------------------------------------- */
350 : /* Collect size, origin, and offsets for SetGeometry() */
351 : /* */
352 : /* Extract Geotransform from GridCRS. */
353 : /* */
354 : /* -------------------------------------------------------------------- */
355 36 : const char *pszGridType = CPLGetXMLValue(
356 : psGCRS, "GridType", "urn:ogc:def:method:WCS::2dSimpleGrid");
357 : bool swap =
358 36 : axis_order_swap && !CPLGetXMLBoolean(psService, "NoGridAxisSwap");
359 : std::vector<double> origin =
360 72 : Flist(Split(CPLGetXMLValue(psGCRS, "GridOrigin", ""), " ", swap));
361 :
362 : std::vector<std::string> offset_1 =
363 72 : Split(CPLGetXMLValue(psGCRS, "GridOffsets", ""), " ");
364 72 : std::vector<std::string> offset_2;
365 36 : size_t n = offset_1.size();
366 36 : if (n % 2 != 0)
367 : {
368 0 : CPLError(CE_Failure, CPLE_AppDefined,
369 : "GridOffsets has incorrect amount of coefficients.\n"
370 : "Unable to process WCS coverage.");
371 0 : return false;
372 : }
373 90 : for (unsigned int i = 0; i < n / 2; ++i)
374 : {
375 54 : CPLString s = offset_1.back();
376 54 : offset_1.erase(offset_1.end() - 1);
377 : #if defined(__GNUC__)
378 : #pragma GCC diagnostic push
379 : #pragma GCC diagnostic ignored "-Wnull-dereference"
380 : #endif
381 54 : offset_2.insert(offset_2.begin(), s);
382 : #if defined(__GNUC__)
383 : #pragma GCC diagnostic pop
384 : #endif
385 : }
386 72 : std::vector<std::vector<double>> offsets;
387 36 : if (swap)
388 : {
389 0 : offsets.push_back(Flist(offset_2));
390 0 : offsets.push_back(Flist(offset_1));
391 : }
392 : else
393 : {
394 36 : offsets.push_back(Flist(offset_1));
395 36 : offsets.push_back(Flist(offset_2));
396 : }
397 :
398 36 : if (strstr(pszGridType, ":2dGridIn2dCrs") ||
399 18 : strstr(pszGridType, ":2dGridin2dCrs"))
400 : {
401 18 : if (!(offset_1.size() == 2 && origin.size() == 2))
402 : {
403 0 : CPLError(CE_Failure, CPLE_AppDefined,
404 : "2dGridIn2dCrs does not have expected GridOrigin or\n"
405 : "GridOffsets values - unable to process WCS coverage.");
406 0 : return false;
407 : }
408 : }
409 :
410 18 : else if (strstr(pszGridType, ":2dGridIn3dCrs"))
411 : {
412 0 : if (!(offset_1.size() == 3 && origin.size() == 3))
413 : {
414 0 : CPLError(CE_Failure, CPLE_AppDefined,
415 : "2dGridIn3dCrs does not have expected GridOrigin or\n"
416 : "GridOffsets values - unable to process WCS coverage.");
417 0 : return false;
418 : }
419 : }
420 :
421 18 : else if (strstr(pszGridType, ":2dSimpleGrid"))
422 : {
423 18 : if (!(offset_1.size() == 1 && origin.size() == 2))
424 : {
425 0 : CPLError(CE_Failure, CPLE_AppDefined,
426 : "2dSimpleGrid does not have expected GridOrigin or\n"
427 : "GridOffsets values - unable to process WCS coverage.");
428 0 : return false;
429 : }
430 : }
431 :
432 : else
433 : {
434 0 : CPLError(CE_Failure, CPLE_AppDefined,
435 : "Unrecognized GridCRS.GridType value '%s',\n"
436 : "unable to process WCS coverage.",
437 : pszGridType);
438 0 : return false;
439 : }
440 :
441 : /* -------------------------------------------------------------------- */
442 : /* Search for an ImageCRS for raster size. */
443 : /* -------------------------------------------------------------------- */
444 72 : std::vector<int> size;
445 : CPLXMLNode *psNode;
446 :
447 108 : for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0;
448 72 : psNode = psNode->psNext)
449 : {
450 72 : if (psNode->eType != CXT_Element ||
451 72 : !EQUAL(psNode->pszValue, "BoundingBox"))
452 18 : continue;
453 :
454 108 : CPLString osBBCRS = ParseCRS(psNode);
455 54 : if (strstr(osBBCRS, ":imageCRS"))
456 : {
457 36 : std::vector<std::string> bbox = ParseBoundingBox(psNode);
458 18 : if (bbox.size() >= 2)
459 : {
460 36 : std::vector<int> low = Ilist(Split(bbox[0].c_str(), " "), 0, 2);
461 : std::vector<int> high =
462 36 : Ilist(Split(bbox[1].c_str(), " "), 0, 2);
463 18 : if (low[0] == 0 && low[1] == 0)
464 : {
465 18 : size.push_back(high[0]);
466 18 : size.push_back(high[1]);
467 : }
468 : }
469 : }
470 : }
471 :
472 : /* -------------------------------------------------------------------- */
473 : /* Otherwise we search for a bounding box in our coordinate */
474 : /* system and derive the size from that. */
475 : /* -------------------------------------------------------------------- */
476 72 : for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0;
477 36 : psNode = psNode->psNext)
478 : {
479 36 : if (psNode->eType != CXT_Element ||
480 36 : !EQUAL(psNode->pszValue, "BoundingBox"))
481 0 : continue;
482 :
483 72 : CPLString osBBCRS = ParseCRS(psNode);
484 36 : if (osBBCRS == osCRS)
485 : {
486 36 : std::vector<std::string> bbox = ParseBoundingBox(psNode);
487 : bool not_rot =
488 36 : (offsets[0].size() == 1 && offsets[1].size() == 1) ||
489 0 : ((swap && offsets[0][0] == 0.0 && offsets[1][1] == 0.0) ||
490 18 : (!swap && offsets[0][1] == 0.0 && offsets[1][0] == 0.0));
491 18 : if (bbox.size() >= 2 && not_rot)
492 : {
493 : std::vector<double> low =
494 36 : Flist(Split(bbox[0].c_str(), " ", axis_order_swap), 0, 2);
495 : std::vector<double> high =
496 18 : Flist(Split(bbox[1].c_str(), " ", axis_order_swap), 0, 2);
497 18 : double c1 = offsets[0][0];
498 : double c2 =
499 18 : offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
500 18 : size.push_back((int)((high[0] - low[0]) / c1 + 1.01));
501 18 : size.push_back((int)((high[1] - low[1]) / fabs(c2) + 1.01));
502 : }
503 : }
504 : }
505 :
506 36 : if (size.size() < 2)
507 : {
508 0 : CPLError(CE_Failure, CPLE_AppDefined,
509 : "Could not determine the size of the grid.");
510 0 : return false;
511 : }
512 :
513 36 : SetGeometry(size, origin, offsets);
514 :
515 : /* -------------------------------------------------------------------- */
516 : /* Do we have a coordinate system override? */
517 : /* -------------------------------------------------------------------- */
518 36 : const char *pszProjOverride = CPLGetXMLValue(psService, "SRS", nullptr);
519 :
520 36 : if (pszProjOverride)
521 : {
522 0 : if (m_oSRS.SetFromUserInput(
523 : pszProjOverride,
524 0 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
525 : OGRERR_NONE)
526 : {
527 0 : CPLError(CE_Failure, CPLE_AppDefined,
528 : "<SRS> element contents not parsable:\n%s",
529 : pszProjOverride);
530 0 : return false;
531 : }
532 : }
533 :
534 : /* -------------------------------------------------------------------- */
535 : /* Pick a format type if we don't already have one selected. */
536 : /* */
537 : /* We will prefer anything that sounds like TIFF, otherwise */
538 : /* falling back to the first supported format. Should we */
539 : /* consider preferring the nativeFormat if available? */
540 : /* -------------------------------------------------------------------- */
541 36 : if (CPLGetXMLValue(psService, "PreferredFormat", nullptr) == nullptr)
542 : {
543 24 : CPLString osPreferredFormat;
544 :
545 125 : for (psNode = psCO->psChild; psNode != nullptr; psNode = psNode->psNext)
546 : {
547 125 : if (psNode->eType == CXT_Element &&
548 125 : EQUAL(psNode->pszValue, "SupportedFormat") && psNode->psChild &&
549 20 : psNode->psChild->eType == CXT_Text)
550 : {
551 20 : if (osPreferredFormat.empty())
552 12 : osPreferredFormat = psNode->psChild->pszValue;
553 :
554 20 : if (strstr(psNode->psChild->pszValue, "tiff") != nullptr ||
555 11 : strstr(psNode->psChild->pszValue, "TIFF") != nullptr ||
556 8 : strstr(psNode->psChild->pszValue, "Tiff") != nullptr)
557 : {
558 12 : osPreferredFormat = psNode->psChild->pszValue;
559 12 : break;
560 : }
561 : }
562 : }
563 :
564 12 : if (!osPreferredFormat.empty())
565 : {
566 12 : bServiceDirty = true;
567 12 : CPLCreateXMLElementAndValue(psService, "PreferredFormat",
568 : osPreferredFormat);
569 : }
570 : }
571 :
572 : /* -------------------------------------------------------------------- */
573 : /* Try to identify a nodata value. For now we only support the */
574 : /* singleValue mechanism. */
575 : /* -------------------------------------------------------------------- */
576 36 : if (CPLGetXMLValue(psService, "NoDataValue", nullptr) == nullptr)
577 : {
578 : const char *pszSV =
579 24 : CPLGetXMLValue(psCO, "Range.Field.NullValue", nullptr);
580 :
581 24 : if (pszSV != nullptr && (CPLAtof(pszSV) != 0.0 || *pszSV == DIGIT_ZERO))
582 : {
583 6 : bServiceDirty = true;
584 6 : CPLCreateXMLElementAndValue(psService, "NoDataValue", pszSV);
585 : }
586 : }
587 :
588 : /* -------------------------------------------------------------------- */
589 : /* Grab the field name, if possible. */
590 : /* -------------------------------------------------------------------- */
591 36 : if (CPLGetXMLValue(psService, "FieldName", nullptr) == nullptr)
592 : {
593 : CPLString osFieldName =
594 12 : CPLGetXMLValue(psCO, "Range.Field.Identifier", "");
595 :
596 12 : if (!osFieldName.empty())
597 : {
598 12 : bServiceDirty = true;
599 12 : CPLCreateXMLElementAndValue(psService, "FieldName", osFieldName);
600 : }
601 : else
602 : {
603 0 : CPLError(
604 : CE_Failure, CPLE_AppDefined,
605 : "Unable to find required Identifier name %s for Range Field.",
606 : osCRS.c_str());
607 0 : return false;
608 : }
609 : }
610 :
611 : /* -------------------------------------------------------------------- */
612 : /* Do we have a "Band" axis? If so try to grab the bandcount */
613 : /* and data type from it. */
614 : /* -------------------------------------------------------------------- */
615 36 : osBandIdentifier = CPLGetXMLValue(psService, "BandIdentifier", "");
616 : CPLXMLNode *psAxis =
617 36 : CPLGetXMLNode(psService, "CoverageDescription.Range.Field.Axis");
618 :
619 36 : if (osBandIdentifier.empty() &&
620 24 : (EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Band") ||
621 81 : EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Bands")) &&
622 24 : CPLGetXMLNode(psAxis, "AvailableKeys") != nullptr)
623 : {
624 24 : osBandIdentifier = CPLGetXMLValue(psAxis, "Identifier", "");
625 :
626 : // verify keys are ascending starting at 1
627 24 : CPLXMLNode *psValues = CPLGetXMLNode(psAxis, "AvailableKeys");
628 : CPLXMLNode *psSV;
629 : int iBand;
630 :
631 36 : for (psSV = psValues->psChild, iBand = 1; psSV != nullptr;
632 12 : psSV = psSV->psNext, iBand++)
633 : {
634 30 : if (psSV->eType != CXT_Element || !EQUAL(psSV->pszValue, "Key") ||
635 30 : psSV->psChild == nullptr || psSV->psChild->eType != CXT_Text ||
636 30 : atoi(psSV->psChild->pszValue) != iBand)
637 : {
638 18 : osBandIdentifier = "";
639 18 : break;
640 : }
641 : }
642 :
643 24 : if (!osBandIdentifier.empty())
644 : {
645 6 : if (CPLGetXMLValue(psService, "BandIdentifier", nullptr) == nullptr)
646 : {
647 6 : bServiceDirty = true;
648 6 : CPLSetXMLValue(psService, "BandIdentifier",
649 : osBandIdentifier.c_str());
650 : }
651 :
652 6 : if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
653 : {
654 6 : bServiceDirty = true;
655 6 : CPLSetXMLValue(psService, "BandCount",
656 12 : CPLString().Printf("%d", iBand - 1));
657 : }
658 : }
659 :
660 : // Is this an ESRI server returning a GDAL recognised data type?
661 48 : CPLString osDataType = CPLGetXMLValue(psAxis, "DataType", "");
662 24 : if (GDALGetDataTypeByName(osDataType) != GDT_Unknown &&
663 0 : CPLGetXMLValue(psService, "BandType", nullptr) == nullptr)
664 : {
665 0 : bServiceDirty = true;
666 0 : CPLCreateXMLElementAndValue(psService, "BandType", osDataType);
667 : }
668 : }
669 :
670 36 : return true;
671 : }
672 :
673 : /************************************************************************/
674 : /* ParseCapabilities() */
675 : /************************************************************************/
676 :
677 19 : CPLErr WCSDataset110::ParseCapabilities(CPLXMLNode *Capabilities,
678 : const std::string &url)
679 : {
680 19 : CPLStripXMLNamespace(Capabilities, nullptr, TRUE);
681 :
682 : // make sure this is a capabilities document
683 19 : if (strcmp(Capabilities->pszValue, "Capabilities") != 0)
684 : {
685 0 : CPLError(CE_Failure, CPLE_AppDefined,
686 : "Error in capabilities document.\n");
687 0 : return CE_Failure;
688 : }
689 :
690 19 : char **metadata = nullptr;
691 38 : std::string path = "WCS_GLOBAL#";
692 :
693 38 : CPLString key = path + "version";
694 19 : metadata = CSLSetNameValue(metadata, key, Version());
695 :
696 271 : for (CPLXMLNode *node = Capabilities->psChild; node != nullptr;
697 252 : node = node->psNext)
698 : {
699 252 : const char *attr = node->pszValue;
700 252 : if (node->eType == CXT_Attribute && EQUAL(attr, "updateSequence"))
701 : {
702 13 : key = path + "updateSequence";
703 13 : CPLString value = CPLGetXMLValue(node, nullptr, "");
704 13 : metadata = CSLSetNameValue(metadata, key, value);
705 : }
706 : }
707 :
708 : // identification metadata
709 38 : std::string path2 = path;
710 95 : CPLXMLNode *service = AddSimpleMetaData(
711 : &metadata, Capabilities, path2, "ServiceIdentification",
712 76 : {"Title", "Abstract", "Fees", "AccessConstraints"});
713 57 : CPLString kw = GetKeywords(service, "Keywords", "Keyword");
714 19 : if (kw != "")
715 : {
716 13 : CPLString name = path + "Keywords";
717 13 : metadata = CSLSetNameValue(metadata, name, kw);
718 : }
719 57 : CPLString profiles = GetKeywords(service, "", "Profile");
720 19 : if (profiles != "")
721 : {
722 7 : CPLString name = path + "Profiles";
723 7 : metadata = CSLSetNameValue(metadata, name, profiles);
724 : }
725 :
726 : // provider metadata
727 19 : path2 = path;
728 38 : CPLXMLNode *provider = AddSimpleMetaData(
729 19 : &metadata, Capabilities, path2, "ServiceProvider", {"ProviderName"});
730 19 : if (provider)
731 : {
732 19 : CPLXMLNode *site = CPLGetXMLNode(provider, "ProviderSite");
733 19 : if (site)
734 : {
735 30 : std::string path3 = path2 + "ProviderSite";
736 : CPLString value =
737 15 : CPLGetXMLValue(CPLGetXMLNode(site, "href"), nullptr, "");
738 15 : metadata = CSLSetNameValue(metadata, path3.c_str(), value);
739 : }
740 38 : std::string path3 = std::move(path2);
741 : CPLXMLNode *contact =
742 76 : AddSimpleMetaData(&metadata, provider, path3, "ServiceContact",
743 57 : {"IndividualName", "PositionName", "Role"});
744 19 : if (contact)
745 : {
746 38 : std::string path4 = std::move(path3);
747 : CPLXMLNode *info =
748 57 : AddSimpleMetaData(&metadata, contact, path4, "ContactInfo",
749 38 : {"HoursOfService", "ContactInstructions"});
750 19 : if (info)
751 : {
752 38 : std::string path5 = path4;
753 38 : std::string path6 = path4;
754 133 : AddSimpleMetaData(&metadata, info, path5, "Address",
755 : {"DeliveryPoint", "City",
756 : "AdministrativeArea", "PostalCode",
757 114 : "Country", "ElectronicMailAddress"});
758 57 : AddSimpleMetaData(&metadata, info, path6, "Phone",
759 38 : {"Voice", "Facsimile"});
760 19 : CPL_IGNORE_RET_VAL(path4);
761 : }
762 : }
763 : }
764 :
765 : // operations metadata
766 38 : CPLString DescribeCoverageURL = "";
767 19 : CPLXMLNode *service2 = CPLGetXMLNode(Capabilities, "OperationsMetadata");
768 19 : if (service2)
769 : {
770 91 : for (CPLXMLNode *operation = service2->psChild; operation != nullptr;
771 72 : operation = operation->psNext)
772 : {
773 72 : if (operation->eType != CXT_Element ||
774 68 : !EQUAL(operation->pszValue, "Operation"))
775 : {
776 15 : continue;
777 : }
778 57 : if (EQUAL(CPLGetXMLValue(CPLGetXMLNode(operation, "name"), nullptr,
779 : ""),
780 : "DescribeCoverage"))
781 : {
782 : DescribeCoverageURL = CPLGetXMLValue(
783 19 : CPLGetXMLNode(CPLSearchXMLNode(operation, "Get"), "href"),
784 19 : nullptr, "");
785 : }
786 : }
787 : }
788 : // if DescribeCoverageURL looks wrong, we change it
789 19 : if (DescribeCoverageURL.find("localhost") != std::string::npos)
790 : {
791 2 : DescribeCoverageURL = URLRemoveKey(url.c_str(), "request");
792 : }
793 :
794 : // service metadata (in 2.0)
795 38 : CPLString ext = "ServiceMetadata";
796 57 : CPLString formats = GetKeywords(Capabilities, ext, "formatSupported");
797 19 : if (formats != "")
798 : {
799 7 : CPLString name = path + "formatSupported";
800 7 : metadata = CSLSetNameValue(metadata, name, formats);
801 : }
802 : // wcs:Extensions: interpolation, CRS, others?
803 19 : ext += ".Extension";
804 : CPLString interpolation =
805 57 : GetKeywords(Capabilities, ext, "interpolationSupported");
806 19 : if (interpolation == "")
807 : {
808 : interpolation =
809 30 : GetKeywords(Capabilities, ext + ".InterpolationMetadata",
810 15 : "InterpolationSupported");
811 : }
812 19 : if (interpolation != "")
813 : {
814 7 : CPLString name = path + "InterpolationSupported";
815 7 : metadata = CSLSetNameValue(metadata, name, interpolation);
816 : }
817 57 : CPLString crs = GetKeywords(Capabilities, ext, "crsSupported");
818 19 : if (crs == "")
819 : {
820 15 : crs = GetKeywords(Capabilities, ext + ".CrsMetadata", "crsSupported");
821 : }
822 19 : if (crs != "")
823 : {
824 5 : CPLString name = path + "crsSupported";
825 5 : metadata = CSLSetNameValue(metadata, name, crs);
826 : }
827 :
828 19 : this->SetMetadata(metadata, "");
829 19 : CSLDestroy(metadata);
830 19 : metadata = nullptr;
831 :
832 : // contents metadata
833 19 : CPLXMLNode *contents = CPLGetXMLNode(Capabilities, "Contents");
834 19 : if (contents)
835 : {
836 19 : int index = 1;
837 150 : for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
838 131 : summary = summary->psNext)
839 : {
840 131 : if (summary->eType != CXT_Element ||
841 129 : !EQUAL(summary->pszValue, "CoverageSummary"))
842 : {
843 26 : continue;
844 : }
845 105 : CPLString path3;
846 105 : path3.Printf("SUBDATASET_%d_", index);
847 105 : index += 1;
848 :
849 : // the name and description of the subdataset:
850 : // GDAL Data Model:
851 : // The value of the _NAME is a string that can be passed to
852 : // GDALOpen() to access the file.
853 :
854 105 : CPLString key2 = path3 + "NAME";
855 :
856 105 : CPLString name = DescribeCoverageURL;
857 105 : name = CPLURLAddKVP(name, "version", this->Version());
858 :
859 105 : CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId");
860 105 : std::string id;
861 105 : if (node)
862 : {
863 60 : id = CPLGetXMLValue(node, nullptr, "");
864 : }
865 : else
866 : {
867 45 : node = CPLGetXMLNode(summary, "Identifier");
868 45 : if (node)
869 : {
870 45 : id = CPLGetXMLValue(node, nullptr, "");
871 : }
872 : else
873 : {
874 : // todo: maybe not an error since CoverageSummary may be
875 : // within CoverageSummary (07-067r5 Fig4)
876 0 : CSLDestroy(metadata);
877 0 : CPLError(CE_Failure, CPLE_AppDefined,
878 : "Error in capabilities document.\n");
879 0 : return CE_Failure;
880 : }
881 : }
882 105 : name = CPLURLAddKVP(name, "coverage", id.c_str());
883 105 : name = "WCS:" + name;
884 105 : metadata = CSLSetNameValue(metadata, key2, name);
885 :
886 105 : key2 = path3 + "DESC";
887 :
888 105 : node = CPLGetXMLNode(summary, "Title");
889 105 : if (node)
890 : {
891 45 : metadata = CSLSetNameValue(metadata, key2,
892 : CPLGetXMLValue(node, nullptr, ""));
893 : }
894 : else
895 : {
896 60 : metadata = CSLSetNameValue(metadata, key2, id.c_str());
897 : }
898 :
899 : // todo: compose global bounding box from WGS84BoundingBox and
900 : // BoundingBox
901 :
902 : // further subdataset (coverage) parameters are parsed in
903 : // ParseCoverageCapabilities
904 : }
905 : }
906 19 : this->SetMetadata(metadata, "SUBDATASETS");
907 19 : CSLDestroy(metadata);
908 19 : return CE_None;
909 : }
910 :
911 19 : void WCSDataset110::ParseCoverageCapabilities(CPLXMLNode *capabilities,
912 : const std::string &coverage,
913 : CPLXMLNode *metadata)
914 : {
915 19 : CPLStripXMLNamespace(capabilities, nullptr, TRUE);
916 19 : CPLXMLNode *contents = CPLGetXMLNode(capabilities, "Contents");
917 19 : if (contents)
918 : {
919 150 : for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
920 131 : summary = summary->psNext)
921 : {
922 131 : if (summary->eType != CXT_Element ||
923 129 : !EQUAL(summary->pszValue, "CoverageSummary"))
924 : {
925 112 : continue;
926 : }
927 105 : CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId");
928 105 : CPLString id;
929 105 : if (node)
930 : {
931 60 : id = CPLGetXMLValue(node, nullptr, "");
932 : }
933 : else
934 : {
935 45 : node = CPLGetXMLNode(summary, "Identifier");
936 45 : if (node)
937 : {
938 45 : id = CPLGetXMLValue(node, nullptr, "");
939 : }
940 : else
941 : {
942 0 : id = "";
943 : }
944 : }
945 105 : if (id != coverage)
946 : {
947 86 : continue;
948 : }
949 :
950 : // Description
951 : // todo: there could be Title and Abstract for each supported
952 : // language
953 19 : XMLCopyMetadata(summary, metadata, "Title");
954 19 : XMLCopyMetadata(summary, metadata, "Abstract");
955 :
956 : // 2.0.1 stuff
957 19 : XMLCopyMetadata(summary, metadata, "CoverageSubtype");
958 :
959 : // Keywords
960 57 : CPLString kw = GetKeywords(summary, "Keywords", "Keyword");
961 19 : CPLAddXMLAttributeAndValue(
962 : CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
963 : "Keywords");
964 :
965 : // WCSContents
966 19 : const char *tags[] = {"SupportedCRS", "SupportedFormat",
967 : "OtherSource"};
968 76 : for (unsigned int i = 0; i < CPL_ARRAYSIZE(tags); i++)
969 : {
970 57 : kw = GetKeywords(summary, "", tags[i]);
971 57 : CPLAddXMLAttributeAndValue(
972 : CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
973 : tags[i]);
974 : }
975 :
976 : // skipping WGS84BoundingBox, BoundingBox, Metadata, Extension
977 : // since those we'll get from coverage description
978 : }
979 : }
980 19 : }
|