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