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