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