Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: WCS Client Driver
4 : * Purpose: Implementation of Dataset class for WCS 1.0.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2006, Frank Warmerdam
9 : * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10 : * Copyright (c) 2017, Ari Jolma
11 : * Copyright (c) 2017, Finnish Environment Institute
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include "cpl_string.h"
17 : #include "cpl_minixml.h"
18 : #include "cpl_http.h"
19 : #include "gmlutils.h"
20 : #include "gdal_frmts.h"
21 : #include "gdal_pam.h"
22 : #include "ogr_spatialref.h"
23 : #include "gmlcoverage.h"
24 :
25 : #include <algorithm>
26 :
27 : #include "wcsdataset.h"
28 : #include "wcsutils.h"
29 :
30 : using namespace WCSUtils;
31 :
32 : /************************************************************************/
33 : /* GetExtent() */
34 : /* */
35 : /************************************************************************/
36 :
37 15 : std::vector<double> WCSDataset100::GetExtent(int nXOff, int nYOff, int nXSize,
38 : int nYSize, CPL_UNUSED int,
39 : CPL_UNUSED int)
40 : {
41 15 : std::vector<double> extent;
42 : // WCS 1.0 extents are the outer edges of outer pixels.
43 15 : extent.push_back(adfGeoTransform[0] + (nXOff)*adfGeoTransform[1]);
44 0 : extent.push_back(adfGeoTransform[3] +
45 15 : (nYOff + nYSize) * adfGeoTransform[5]);
46 0 : extent.push_back(adfGeoTransform[0] +
47 15 : (nXOff + nXSize) * adfGeoTransform[1]);
48 15 : extent.push_back(adfGeoTransform[3] + (nYOff)*adfGeoTransform[5]);
49 15 : return extent;
50 : }
51 :
52 : /************************************************************************/
53 : /* GetCoverageRequest() */
54 : /* */
55 : /************************************************************************/
56 :
57 15 : std::string WCSDataset100::GetCoverageRequest(bool /* scaled */, int nBufXSize,
58 : int nBufYSize,
59 : const std::vector<double> &extent,
60 : const std::string &osBandList)
61 : {
62 :
63 : /* -------------------------------------------------------------------- */
64 : /* URL encode strings that could have questionable characters. */
65 : /* -------------------------------------------------------------------- */
66 30 : CPLString osCoverage = CPLGetXMLValue(psService, "CoverageName", "");
67 :
68 15 : char *pszEncoded = CPLEscapeString(osCoverage, -1, CPLES_URL);
69 15 : osCoverage = pszEncoded;
70 15 : CPLFree(pszEncoded);
71 :
72 30 : CPLString osFormat = CPLGetXMLValue(psService, "PreferredFormat", "");
73 :
74 15 : pszEncoded = CPLEscapeString(osFormat, -1, CPLES_URL);
75 15 : osFormat = pszEncoded;
76 15 : CPLFree(pszEncoded);
77 :
78 : /* -------------------------------------------------------------------- */
79 : /* Do we have a time we want to use? */
80 : /* -------------------------------------------------------------------- */
81 30 : CPLString osTime;
82 :
83 : osTime =
84 15 : CSLFetchNameValueDef(papszSDSModifiers, "time", osDefaultTime.c_str());
85 :
86 : /* -------------------------------------------------------------------- */
87 : /* Construct a "simple" GetCoverage request (WCS 1.0). */
88 : /* -------------------------------------------------------------------- */
89 15 : std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
90 15 : request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
91 15 : request = CPLURLAddKVP(request.c_str(), "REQUEST", "GetCoverage");
92 30 : request = CPLURLAddKVP(request.c_str(), "VERSION",
93 30 : CPLGetXMLValue(psService, "Version", "1.0.0"));
94 15 : request = CPLURLAddKVP(request.c_str(), "COVERAGE", osCoverage.c_str());
95 15 : request = CPLURLAddKVP(request.c_str(), "FORMAT", osFormat.c_str());
96 30 : request += CPLString().Printf(
97 15 : "&BBOX=%.15g,%.15g,%.15g,%.15g&WIDTH=%d&HEIGHT=%d&CRS=%s", extent[0],
98 15 : extent[1], extent[2], extent[3], nBufXSize, nBufYSize, osCRS.c_str());
99 30 : CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
100 15 : if (extra != "")
101 : {
102 30 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
103 30 : for (unsigned int i = 0; i < pairs.size(); ++i)
104 : {
105 15 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
106 : request =
107 15 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
108 : }
109 : }
110 15 : extra = CPLGetXMLValue(psService, "GetCoverageExtra", "");
111 15 : if (extra != "")
112 : {
113 30 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
114 30 : for (unsigned int i = 0; i < pairs.size(); ++i)
115 : {
116 15 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
117 : request =
118 15 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
119 : }
120 : }
121 :
122 30 : CPLString interpolation = CPLGetXMLValue(psService, "Interpolation", "");
123 15 : if (interpolation == "")
124 : {
125 : // old undocumented key for interpolation in service
126 15 : interpolation = CPLGetXMLValue(psService, "Resample", "");
127 : }
128 15 : if (interpolation != "")
129 : {
130 0 : request += "&INTERPOLATION=" + interpolation;
131 : }
132 :
133 15 : if (osTime != "")
134 : {
135 0 : request += "&time=";
136 0 : request += osTime;
137 : }
138 :
139 15 : if (osBandList != "")
140 : {
141 0 : request += CPLString().Printf("&%s=%s", osBandIdentifier.c_str(),
142 0 : osBandList.c_str());
143 : }
144 30 : return request;
145 : }
146 :
147 : /************************************************************************/
148 : /* DescribeCoverageRequest() */
149 : /* */
150 : /************************************************************************/
151 :
152 6 : std::string WCSDataset100::DescribeCoverageRequest()
153 : {
154 6 : std::string request = CPLGetXMLValue(psService, "ServiceURL", "");
155 6 : request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS");
156 6 : request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage");
157 12 : request = CPLURLAddKVP(request.c_str(), "VERSION",
158 12 : CPLGetXMLValue(psService, "Version", "1.0.0"));
159 12 : request = CPLURLAddKVP(request.c_str(), "COVERAGE",
160 12 : CPLGetXMLValue(psService, "CoverageName", ""));
161 12 : CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
162 6 : if (extra != "")
163 : {
164 10 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
165 10 : for (unsigned int i = 0; i < pairs.size(); ++i)
166 : {
167 5 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
168 : request =
169 5 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
170 : }
171 : }
172 6 : extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
173 6 : if (extra != "")
174 : {
175 0 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
176 0 : for (unsigned int i = 0; i < pairs.size(); ++i)
177 : {
178 0 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
179 : request =
180 0 : CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str());
181 : }
182 : }
183 12 : return request;
184 : }
185 :
186 : /************************************************************************/
187 : /* CoverageOffering() */
188 : /* */
189 : /************************************************************************/
190 :
191 5 : CPLXMLNode *WCSDataset100::CoverageOffering(CPLXMLNode *psDC)
192 : {
193 5 : return CPLGetXMLNode(psDC, "=CoverageDescription.CoverageOffering");
194 : }
195 :
196 : /************************************************************************/
197 : /* ExtractGridInfo() */
198 : /* */
199 : /* Collect info about grid from describe coverage for WCS 1.0.0 */
200 : /* and above. */
201 : /************************************************************************/
202 :
203 15 : bool WCSDataset100::ExtractGridInfo()
204 :
205 : {
206 15 : CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageOffering");
207 :
208 15 : if (psCO == nullptr)
209 0 : return FALSE;
210 :
211 : /* -------------------------------------------------------------------- */
212 : /* We need to strip off name spaces so it is easier to */
213 : /* searchfor plain gml names. */
214 : /* -------------------------------------------------------------------- */
215 15 : CPLStripXMLNamespace(psCO, nullptr, TRUE);
216 :
217 : /* -------------------------------------------------------------------- */
218 : /* Verify we have a Rectified Grid. */
219 : /* -------------------------------------------------------------------- */
220 : CPLXMLNode *psRG =
221 15 : CPLGetXMLNode(psCO, "domainSet.spatialDomain.RectifiedGrid");
222 :
223 15 : if (psRG == nullptr)
224 : {
225 0 : CPLError(CE_Failure, CPLE_AppDefined,
226 : "Unable to find RectifiedGrid in CoverageOffering,\n"
227 : "unable to process WCS Coverage.");
228 0 : return FALSE;
229 : }
230 :
231 : /* -------------------------------------------------------------------- */
232 : /* Extract size, geotransform and coordinate system. */
233 : /* Projection is, if it is, from Point.srsName */
234 : /* -------------------------------------------------------------------- */
235 15 : char *pszProjection = nullptr;
236 15 : if (WCSParseGMLCoverage(psRG, &nRasterXSize, &nRasterYSize, adfGeoTransform,
237 15 : &pszProjection) != CE_None)
238 : {
239 0 : CPLFree(pszProjection);
240 0 : return FALSE;
241 : }
242 15 : if (pszProjection)
243 0 : m_oSRS.SetFromUserInput(
244 : pszProjection,
245 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
246 15 : CPLFree(pszProjection);
247 :
248 : // MapServer have origin at pixel boundary
249 15 : if (CPLGetXMLBoolean(psService, "OriginAtBoundary"))
250 : {
251 3 : adfGeoTransform[0] += adfGeoTransform[1] * 0.5;
252 3 : adfGeoTransform[0] += adfGeoTransform[2] * 0.5;
253 3 : adfGeoTransform[3] += adfGeoTransform[4] * 0.5;
254 3 : adfGeoTransform[3] += adfGeoTransform[5] * 0.5;
255 : }
256 :
257 : /* -------------------------------------------------------------------- */
258 : /* Fallback to nativeCRSs declaration. */
259 : /* -------------------------------------------------------------------- */
260 : const char *pszNativeCRSs =
261 15 : CPLGetXMLValue(psCO, "supportedCRSs.nativeCRSs", nullptr);
262 :
263 15 : if (pszNativeCRSs == nullptr)
264 : pszNativeCRSs =
265 9 : CPLGetXMLValue(psCO, "supportedCRSs.requestResponseCRSs", nullptr);
266 :
267 15 : if (pszNativeCRSs == nullptr)
268 : pszNativeCRSs =
269 0 : CPLGetXMLValue(psCO, "supportedCRSs.requestCRSs", nullptr);
270 :
271 15 : if (pszNativeCRSs == nullptr)
272 : pszNativeCRSs =
273 0 : CPLGetXMLValue(psCO, "supportedCRSs.responseCRSs", nullptr);
274 :
275 15 : if (pszNativeCRSs != nullptr && m_oSRS.IsEmpty())
276 : {
277 15 : if (m_oSRS.SetFromUserInput(
278 : pszNativeCRSs,
279 15 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
280 : OGRERR_NONE)
281 : {
282 15 : CPLDebug("WCS", "<nativeCRSs> element contents not parsable:\n%s",
283 : pszNativeCRSs);
284 : }
285 : }
286 :
287 : // We should try to use the services name for the CRS if possible.
288 15 : if (pszNativeCRSs != nullptr &&
289 15 : (STARTS_WITH_CI(pszNativeCRSs, "EPSG:") ||
290 0 : STARTS_WITH_CI(pszNativeCRSs, "AUTO:") ||
291 0 : STARTS_WITH_CI(pszNativeCRSs, "Image ") ||
292 0 : STARTS_WITH_CI(pszNativeCRSs, "Engineering ") ||
293 0 : STARTS_WITH_CI(pszNativeCRSs, "OGC:")))
294 : {
295 15 : osCRS = pszNativeCRSs;
296 :
297 15 : size_t nDivider = osCRS.find(" ");
298 :
299 15 : if (nDivider != std::string::npos)
300 0 : osCRS.resize(nDivider - 1);
301 : }
302 :
303 : /* -------------------------------------------------------------------- */
304 : /* Do we have a coordinate system override? */
305 : /* -------------------------------------------------------------------- */
306 15 : const char *pszProjOverride = CPLGetXMLValue(psService, "SRS", nullptr);
307 :
308 15 : if (pszProjOverride)
309 : {
310 0 : if (m_oSRS.SetFromUserInput(
311 : pszProjOverride,
312 0 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
313 : OGRERR_NONE)
314 : {
315 0 : CPLError(CE_Failure, CPLE_AppDefined,
316 : "<SRS> element contents not parsable:\n%s",
317 : pszProjOverride);
318 0 : return FALSE;
319 : }
320 :
321 0 : if (STARTS_WITH_CI(pszProjOverride, "EPSG:") ||
322 0 : STARTS_WITH_CI(pszProjOverride, "AUTO:") ||
323 0 : STARTS_WITH_CI(pszProjOverride, "OGC:") ||
324 0 : STARTS_WITH_CI(pszProjOverride, "Image ") ||
325 0 : STARTS_WITH_CI(pszProjOverride, "Engineering "))
326 0 : osCRS = pszProjOverride;
327 : }
328 :
329 : /* -------------------------------------------------------------------- */
330 : /* Build CRS name to use. */
331 : /* -------------------------------------------------------------------- */
332 15 : if (!m_oSRS.IsEmpty() && osCRS == "")
333 : {
334 0 : const char *pszAuth = m_oSRS.GetAuthorityName(nullptr);
335 0 : if (pszAuth != nullptr && EQUAL(pszAuth, "EPSG"))
336 : {
337 0 : pszAuth = m_oSRS.GetAuthorityCode(nullptr);
338 0 : if (pszAuth)
339 : {
340 0 : osCRS = "EPSG:";
341 0 : osCRS += pszAuth;
342 : }
343 : else
344 : {
345 0 : CPLError(CE_Failure, CPLE_AppDefined,
346 : "Unable to define CRS to use.");
347 0 : return FALSE;
348 : }
349 : }
350 : }
351 :
352 : /* -------------------------------------------------------------------- */
353 : /* Pick a format type if we don't already have one selected. */
354 : /* */
355 : /* We will prefer anything that sounds like TIFF, otherwise */
356 : /* falling back to the first supported format. Should we */
357 : /* consider preferring the nativeFormat if available? */
358 : /* -------------------------------------------------------------------- */
359 15 : if (CPLGetXMLValue(psService, "PreferredFormat", nullptr) == nullptr)
360 : {
361 5 : CPLXMLNode *psSF = CPLGetXMLNode(psCO, "supportedFormats");
362 : CPLXMLNode *psNode;
363 5 : char **papszFormatList = nullptr;
364 5 : CPLString osPreferredFormat;
365 : int iFormat;
366 :
367 5 : if (psSF == nullptr)
368 : {
369 0 : CPLError(
370 : CE_Failure, CPLE_AppDefined,
371 : "No <PreferredFormat> tag in service definition file, and no\n"
372 : "<supportedFormats> in coverageOffering.");
373 0 : return FALSE;
374 : }
375 :
376 36 : for (psNode = psSF->psChild; psNode != nullptr; psNode = psNode->psNext)
377 : {
378 31 : if (psNode->eType == CXT_Element &&
379 27 : EQUAL(psNode->pszValue, "formats") &&
380 27 : psNode->psChild != nullptr &&
381 27 : psNode->psChild->eType == CXT_Text)
382 : {
383 : // This check is looking for deprecated WCS 1.0 capabilities
384 : // with multiple formats space delimited in a single <formats>
385 : // element per GDAL ticket 1748 (done by MapServer 4.10 and
386 : // earlier for instance).
387 27 : if (papszFormatList == nullptr && psNode->psNext == nullptr &&
388 1 : strstr(psNode->psChild->pszValue, " ") != nullptr &&
389 0 : strstr(psNode->psChild->pszValue, ";") == nullptr)
390 : {
391 : char **papszSubList =
392 0 : CSLTokenizeString(psNode->psChild->pszValue);
393 : papszFormatList =
394 0 : CSLInsertStrings(papszFormatList, -1, papszSubList);
395 0 : CSLDestroy(papszSubList);
396 : }
397 : else
398 : {
399 27 : papszFormatList = CSLAddString(papszFormatList,
400 27 : psNode->psChild->pszValue);
401 : }
402 : }
403 : }
404 :
405 7 : for (iFormat = 0;
406 7 : papszFormatList != nullptr && papszFormatList[iFormat] != nullptr;
407 : iFormat++)
408 : {
409 7 : if (osPreferredFormat.empty())
410 5 : osPreferredFormat = papszFormatList[iFormat];
411 :
412 7 : if (strstr(papszFormatList[iFormat], "tiff") != nullptr ||
413 7 : strstr(papszFormatList[iFormat], "TIFF") != nullptr ||
414 3 : strstr(papszFormatList[iFormat], "Tiff") != nullptr)
415 : {
416 5 : osPreferredFormat = papszFormatList[iFormat];
417 5 : break;
418 : }
419 : }
420 :
421 5 : CSLDestroy(papszFormatList);
422 :
423 5 : if (!osPreferredFormat.empty())
424 : {
425 5 : bServiceDirty = true;
426 5 : CPLCreateXMLElementAndValue(psService, "PreferredFormat",
427 : osPreferredFormat);
428 : }
429 : }
430 :
431 : /* -------------------------------------------------------------------- */
432 : /* Try to identify a nodata value. For now we only support the */
433 : /* singleValue mechanism. */
434 : /* -------------------------------------------------------------------- */
435 15 : if (CPLGetXMLValue(psService, "NoDataValue", nullptr) == nullptr)
436 : {
437 15 : const char *pszSV = CPLGetXMLValue(
438 : psCO, "rangeSet.RangeSet.nullValues.singleValue", nullptr);
439 :
440 15 : if (pszSV != nullptr && (CPLAtof(pszSV) != 0.0 || *pszSV == DIGIT_ZERO))
441 : {
442 0 : bServiceDirty = true;
443 0 : CPLCreateXMLElementAndValue(psService, "NoDataValue", pszSV);
444 : }
445 : }
446 :
447 : /* -------------------------------------------------------------------- */
448 : /* Do we have a Band range type. For now we look for a fairly */
449 : /* specific configuration. The rangeset my have one axis named */
450 : /* "Band", with a set of ascending numerical values. */
451 : /* -------------------------------------------------------------------- */
452 15 : osBandIdentifier = CPLGetXMLValue(psService, "BandIdentifier", "");
453 15 : CPLXMLNode *psAD = CPLGetXMLNode(
454 : psService,
455 : "CoverageOffering.rangeSet.RangeSet.axisDescription.AxisDescription");
456 : CPLXMLNode *psValues;
457 :
458 22 : if (osBandIdentifier.empty() && psAD != nullptr &&
459 7 : (EQUAL(CPLGetXMLValue(psAD, "name", ""), "Band") ||
460 23 : EQUAL(CPLGetXMLValue(psAD, "name", ""), "Bands")) &&
461 7 : ((psValues = CPLGetXMLNode(psAD, "values")) != nullptr))
462 : {
463 : CPLXMLNode *psSV;
464 : int iBand;
465 :
466 7 : osBandIdentifier = CPLGetXMLValue(psAD, "name", "");
467 :
468 13 : for (psSV = psValues->psChild, iBand = 1; psSV != nullptr;
469 6 : psSV = psSV->psNext, iBand++)
470 : {
471 9 : if (psSV->eType != CXT_Element ||
472 9 : !EQUAL(psSV->pszValue, "singleValue") ||
473 6 : psSV->psChild == nullptr || psSV->psChild->eType != CXT_Text ||
474 6 : atoi(psSV->psChild->pszValue) != iBand)
475 : {
476 3 : osBandIdentifier = "";
477 3 : break;
478 : }
479 : }
480 :
481 7 : if (!osBandIdentifier.empty())
482 : {
483 4 : bServiceDirty = true;
484 4 : CPLSetXMLValue(psService, "BandIdentifier",
485 : osBandIdentifier.c_str());
486 : }
487 : }
488 :
489 : /* -------------------------------------------------------------------- */
490 : /* Do we have a temporal domain? If so, try to identify a */
491 : /* default time value. */
492 : /* -------------------------------------------------------------------- */
493 15 : osDefaultTime = CPLGetXMLValue(psService, "DefaultTime", "");
494 : CPLXMLNode *psTD =
495 15 : CPLGetXMLNode(psService, "CoverageOffering.domainSet.temporalDomain");
496 30 : CPLString osServiceURL = CPLGetXMLValue(psService, "ServiceURL", "");
497 : CPLString osCoverageExtra =
498 15 : CPLGetXMLValue(psService, "GetCoverageExtra", "");
499 :
500 15 : if (psTD != nullptr)
501 : {
502 : CPLXMLNode *psTime;
503 :
504 : // collect all the allowed time positions.
505 :
506 0 : for (psTime = psTD->psChild; psTime != nullptr; psTime = psTime->psNext)
507 : {
508 0 : if (psTime->eType == CXT_Element &&
509 0 : EQUAL(psTime->pszValue, "timePosition") &&
510 0 : psTime->psChild != nullptr &&
511 0 : psTime->psChild->eType == CXT_Text)
512 0 : aosTimePositions.push_back(psTime->psChild->pszValue);
513 : }
514 :
515 : // we will default to the last - likely the most recent - entry.
516 :
517 0 : if (!aosTimePositions.empty() && osDefaultTime.empty() &&
518 0 : osServiceURL.ifind("time=") == std::string::npos &&
519 0 : osCoverageExtra.ifind("time=") == std::string::npos)
520 : {
521 0 : osDefaultTime = aosTimePositions.back();
522 0 : bServiceDirty = true;
523 0 : CPLCreateXMLElementAndValue(psService, "DefaultTime",
524 : osDefaultTime.c_str());
525 : }
526 : }
527 :
528 15 : return true;
529 : }
530 :
531 : /************************************************************************/
532 : /* ParseCapabilities() */
533 : /************************************************************************/
534 :
535 5 : CPLErr WCSDataset100::ParseCapabilities(CPLXMLNode *Capabilities,
536 : const std::string & /* url */)
537 : {
538 :
539 5 : CPLStripXMLNamespace(Capabilities, nullptr, TRUE);
540 :
541 5 : if (strcmp(Capabilities->pszValue, "WCS_Capabilities") != 0)
542 : {
543 0 : CPLError(CE_Failure, CPLE_AppDefined,
544 : "Error in capabilities document.\n");
545 0 : return CE_Failure;
546 : }
547 :
548 5 : char **metadata = nullptr;
549 10 : CPLString path = "WCS_GLOBAL#";
550 :
551 10 : CPLString key = path + "version";
552 5 : metadata = CSLSetNameValue(metadata, key, Version());
553 :
554 60 : for (CPLXMLNode *node = Capabilities->psChild; node != nullptr;
555 55 : node = node->psNext)
556 : {
557 55 : const char *attr = node->pszValue;
558 55 : if (node->eType == CXT_Attribute && EQUAL(attr, "updateSequence"))
559 : {
560 4 : key = path + "updateSequence";
561 4 : CPLString value = CPLGetXMLValue(node, nullptr, "");
562 4 : metadata = CSLSetNameValue(metadata, key, value);
563 : }
564 : }
565 :
566 : // identification metadata
567 10 : CPLString path2 = path;
568 30 : CPLXMLNode *service = AddSimpleMetaData(
569 : &metadata, Capabilities, path2, "Service",
570 25 : {"description", "name", "label", "fees", "accessConstraints"});
571 5 : if (service)
572 : {
573 10 : CPLString path3 = std::move(path2);
574 15 : CPLString kw = GetKeywords(service, "keywords", "keyword");
575 5 : if (kw != "")
576 : {
577 4 : CPLString name = path + "keywords";
578 4 : metadata = CSLSetNameValue(metadata, name, kw);
579 : }
580 20 : CPLXMLNode *party = AddSimpleMetaData(
581 : &metadata, service, path3, "responsibleParty",
582 15 : {"individualName", "organisationName", "positionName"});
583 5 : CPLXMLNode *info = CPLGetXMLNode(party, "contactInfo");
584 5 : if (party && info)
585 : {
586 10 : CPLString path4 = path3 + "contactInfo.";
587 5 : CPLString path5 = path4;
588 35 : AddSimpleMetaData(&metadata, info, path4, "address",
589 : {"deliveryPoint", "city", "administrativeArea",
590 : "postalCode", "country",
591 30 : "electronicMailAddress"});
592 15 : AddSimpleMetaData(&metadata, info, path5, "phone",
593 10 : {"voice", "facsimile"});
594 : }
595 : }
596 :
597 : // provider metadata
598 : // operations metadata
599 10 : CPLString DescribeCoverageURL;
600 : DescribeCoverageURL = CPLGetXMLValue(
601 5 : CPLGetXMLNode(
602 : CPLGetXMLNode(
603 : CPLSearchXMLNode(
604 : CPLSearchXMLNode(Capabilities, "DescribeCoverage"), "Get"),
605 : "OnlineResource"),
606 : "href"),
607 5 : nullptr, "");
608 : // if DescribeCoverageURL looks wrong (i.e. has localhost) should we change
609 : // it?
610 :
611 5 : this->SetMetadata(metadata, "");
612 5 : CSLDestroy(metadata);
613 5 : metadata = nullptr;
614 :
615 5 : if (CPLXMLNode *contents = CPLGetXMLNode(Capabilities, "ContentMetadata"))
616 : {
617 5 : int index = 1;
618 23 : for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
619 18 : summary = summary->psNext)
620 : {
621 18 : if (summary->eType != CXT_Element ||
622 18 : !EQUAL(summary->pszValue, "CoverageOfferingBrief"))
623 : {
624 0 : continue;
625 : }
626 18 : CPLString path3;
627 18 : path3.Printf("SUBDATASET_%d_", index);
628 18 : index += 1;
629 :
630 : // the name and description of the subdataset:
631 : // GDAL Data Model:
632 : // The value of the _NAME is a string that can be passed to
633 : // GDALOpen() to access the file.
634 :
635 18 : CPLXMLNode *node = CPLGetXMLNode(summary, "name");
636 18 : if (node)
637 : {
638 36 : CPLString key2 = path3 + "NAME";
639 36 : CPLString name = CPLGetXMLValue(node, nullptr, "");
640 18 : CPLString value = DescribeCoverageURL;
641 18 : value = CPLURLAddKVP(value, "VERSION", this->Version());
642 18 : value = CPLURLAddKVP(value, "COVERAGE", name);
643 18 : metadata = CSLSetNameValue(metadata, key2, value);
644 : }
645 : else
646 : {
647 0 : CSLDestroy(metadata);
648 0 : CPLError(CE_Failure, CPLE_AppDefined,
649 : "Error in capabilities document.\n");
650 0 : return CE_Failure;
651 : }
652 :
653 18 : node = CPLGetXMLNode(summary, "label");
654 18 : if (node)
655 : {
656 18 : CPLString key2 = path3 + "DESC";
657 18 : metadata = CSLSetNameValue(metadata, key2,
658 : CPLGetXMLValue(node, nullptr, ""));
659 : }
660 : else
661 : {
662 0 : CSLDestroy(metadata);
663 0 : CPLError(CE_Failure, CPLE_AppDefined,
664 : "Error in capabilities document.\n");
665 0 : return CE_Failure;
666 : }
667 :
668 : // todo: compose global bounding box from lonLatEnvelope
669 :
670 : // further subdataset (coverage) parameters are parsed in
671 : // ParseCoverageCapabilities
672 : }
673 : }
674 5 : this->SetMetadata(metadata, "SUBDATASETS");
675 5 : CSLDestroy(metadata);
676 5 : return CE_None;
677 : }
678 :
679 5 : void WCSDataset100::ParseCoverageCapabilities(CPLXMLNode *capabilities,
680 : const std::string &coverage,
681 : CPLXMLNode *metadata)
682 : {
683 5 : CPLStripXMLNamespace(capabilities, nullptr, TRUE);
684 5 : if (CPLXMLNode *contents = CPLGetXMLNode(capabilities, "ContentMetadata"))
685 : {
686 23 : for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
687 18 : summary = summary->psNext)
688 : {
689 18 : if (summary->eType != CXT_Element ||
690 18 : !EQUAL(summary->pszValue, "CoverageOfferingBrief"))
691 : {
692 13 : continue;
693 : }
694 :
695 18 : CPLXMLNode *node = CPLGetXMLNode(summary, "name");
696 18 : if (node)
697 : {
698 18 : CPLString name = CPLGetXMLValue(node, nullptr, "");
699 18 : if (name != coverage)
700 : {
701 13 : continue;
702 : }
703 : }
704 :
705 5 : XMLCopyMetadata(summary, metadata, "label");
706 5 : XMLCopyMetadata(summary, metadata, "description");
707 :
708 15 : CPLString kw = GetKeywords(summary, "keywords", "keyword");
709 5 : CPLAddXMLAttributeAndValue(
710 : CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
711 : "keywords");
712 :
713 : // skip metadataLink
714 : }
715 : }
716 5 : }
|