Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: WCS Client Driver
4 : * Purpose: Implementation of Dataset and RasterBand classes for WCS.
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 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_string.h"
15 : #include "cpl_minixml.h"
16 : #include "cpl_http.h"
17 : #include "gmlutils.h"
18 : #include "gdal_frmts.h"
19 : #include "gdal_pam.h"
20 : #include "ogr_spatialref.h"
21 : #include "gmlcoverage.h"
22 :
23 : #include <algorithm>
24 :
25 : #include "wcsdataset.h"
26 : #include "wcsrasterband.h"
27 : #include "wcsutils.h"
28 : #include "wcsdrivercore.h"
29 :
30 : using namespace WCSUtils;
31 :
32 : /************************************************************************/
33 : /* WCSDataset() */
34 : /************************************************************************/
35 :
36 121 : WCSDataset::WCSDataset(int version, const char *cache_dir)
37 : : m_cache_dir(cache_dir), bServiceDirty(false), psService(nullptr),
38 : papszSDSModifiers(nullptr), m_Version(version), native_crs(true),
39 : axis_order_swap(false), pabySavedDataBuffer(nullptr),
40 121 : papszHttpOptions(nullptr), nMaxCols(-1), nMaxRows(-1)
41 : {
42 121 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
43 :
44 121 : apszCoverageOfferingMD[0] = nullptr;
45 121 : apszCoverageOfferingMD[1] = nullptr;
46 121 : }
47 :
48 : /************************************************************************/
49 : /* ~WCSDataset() */
50 : /************************************************************************/
51 :
52 121 : WCSDataset::~WCSDataset()
53 :
54 : {
55 : // perhaps this should be moved into a FlushCache(bool bAtClosing) method.
56 121 : if (bServiceDirty && !STARTS_WITH_CI(GetDescription(), "<WCS_GDAL>"))
57 : {
58 24 : CPLSerializeXMLTreeToFile(psService, GetDescription());
59 24 : bServiceDirty = false;
60 : }
61 :
62 121 : CPLDestroyXMLNode(psService);
63 :
64 121 : CSLDestroy(papszHttpOptions);
65 121 : CSLDestroy(papszSDSModifiers);
66 :
67 121 : CPLFree(apszCoverageOfferingMD[0]);
68 :
69 121 : FlushMemoryResult();
70 121 : }
71 :
72 : /************************************************************************/
73 : /* SetCRS() */
74 : /* */
75 : /* Set the name and the WKT of the projection of this dataset. */
76 : /* Based on the projection, sets the axis order flag. */
77 : /* Also set the native flag. */
78 : /************************************************************************/
79 :
80 57 : bool WCSDataset::SetCRS(const std::string &crs, bool native)
81 : {
82 57 : osCRS = crs;
83 57 : char *pszProjection = nullptr;
84 57 : if (!CRSImpliesAxisOrderSwap(osCRS, axis_order_swap, &pszProjection))
85 : {
86 0 : return false;
87 : }
88 57 : m_oSRS.importFromWkt(pszProjection);
89 57 : CPLFree(pszProjection);
90 57 : native_crs = native;
91 57 : return true;
92 : }
93 :
94 : /************************************************************************/
95 : /* SetGeometry() */
96 : /* */
97 : /* Set GeoTransform and RasterSize from the coverage envelope, */
98 : /* axis_order, grid size, and grid offsets. */
99 : /************************************************************************/
100 :
101 57 : void WCSDataset::SetGeometry(const std::vector<int> &size,
102 : const std::vector<double> &origin,
103 : const std::vector<std::vector<double>> &offsets)
104 : {
105 : // note that this method is not used by wcsdataset100.cpp
106 57 : nRasterXSize = size[0];
107 57 : nRasterYSize = size[1];
108 :
109 57 : m_gt[0] = origin[0];
110 57 : m_gt[1] = offsets[0][0];
111 57 : m_gt[2] = offsets[0].size() == 1 ? 0.0 : offsets[0][1];
112 57 : m_gt[3] = origin[1];
113 57 : m_gt[4] = offsets[1].size() == 1 ? 0.0 : offsets[1][0];
114 57 : m_gt[5] = offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
115 :
116 57 : if (!CPLGetXMLBoolean(psService, "OriginAtBoundary"))
117 : {
118 54 : m_gt[0] -= m_gt[1] * 0.5;
119 54 : m_gt[0] -= m_gt[2] * 0.5;
120 54 : m_gt[3] -= m_gt[4] * 0.5;
121 54 : m_gt[3] -= m_gt[5] * 0.5;
122 : }
123 57 : }
124 :
125 : /************************************************************************/
126 : /* TestUseBlockIO() */
127 : /* */
128 : /* Check whether we should use blocked IO (true) or direct io */
129 : /* (FALSE) for a given request configuration and environment. */
130 : /************************************************************************/
131 :
132 80 : int WCSDataset::TestUseBlockIO(CPL_UNUSED int nXOff, CPL_UNUSED int nYOff,
133 : int nXSize, int nYSize, int nBufXSize,
134 : int nBufYSize) const
135 : {
136 80 : int bUseBlockedIO = bForceCachedIO;
137 :
138 80 : if (nYSize == 1 || nXSize * ((double)nYSize) < 100.0)
139 56 : bUseBlockedIO = TRUE;
140 :
141 80 : if (nBufYSize == 1 || nBufXSize * ((double)nBufYSize) < 100.0)
142 56 : bUseBlockedIO = TRUE;
143 :
144 136 : if (bUseBlockedIO &&
145 56 : CPLTestBool(CPLGetConfigOption("GDAL_ONE_BIG_READ", "NO")))
146 0 : bUseBlockedIO = FALSE;
147 :
148 80 : return bUseBlockedIO;
149 : }
150 :
151 : /************************************************************************/
152 : /* IRasterIO() */
153 : /************************************************************************/
154 :
155 22 : CPLErr WCSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
156 : int nXSize, int nYSize, void *pData, int nBufXSize,
157 : int nBufYSize, GDALDataType eBufType,
158 : int nBandCount, BANDMAP_TYPE panBandMap,
159 : GSpacing nPixelSpace, GSpacing nLineSpace,
160 : GSpacing nBandSpace,
161 : GDALRasterIOExtraArg *psExtraArg)
162 :
163 : {
164 22 : if ((nMaxCols > 0 && nMaxCols < nBufXSize) ||
165 22 : (nMaxRows > 0 && nMaxRows < nBufYSize))
166 0 : return CE_Failure;
167 :
168 : /* -------------------------------------------------------------------- */
169 : /* We need various criteria to skip out to block based methods. */
170 : /* -------------------------------------------------------------------- */
171 22 : if (TestUseBlockIO(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize))
172 11 : return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
173 : pData, nBufXSize, nBufYSize, eBufType,
174 : nBandCount, panBandMap, nPixelSpace,
175 11 : nLineSpace, nBandSpace, psExtraArg);
176 : else
177 11 : return DirectRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
178 : nBufXSize, nBufYSize, eBufType, nBandCount,
179 : panBandMap, nPixelSpace, nLineSpace, nBandSpace,
180 11 : psExtraArg);
181 : }
182 :
183 : /************************************************************************/
184 : /* DirectRasterIO() */
185 : /* */
186 : /* Make exactly one request to the server for this data. */
187 : /************************************************************************/
188 :
189 24 : CPLErr WCSDataset::DirectRasterIO(CPL_UNUSED GDALRWFlag eRWFlag, int nXOff,
190 : int nYOff, int nXSize, int nYSize,
191 : void *pData, int nBufXSize, int nBufYSize,
192 : GDALDataType eBufType, int nBandCount,
193 : const int *panBandMap, GSpacing nPixelSpace,
194 : GSpacing nLineSpace, GSpacing nBandSpace,
195 : GDALRasterIOExtraArg *psExtraArg)
196 : {
197 24 : CPLDebug("WCS", "DirectRasterIO(%d,%d,%d,%d) -> (%d,%d) (%d bands)\n",
198 : nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nBandCount);
199 :
200 : /* -------------------------------------------------------------------- */
201 : /* Get the coverage. */
202 : /* -------------------------------------------------------------------- */
203 :
204 : // if INTERLEAVE is set to PIXEL, then we'll request all bands.
205 : // That is necessary at least with MapServer, which seems to often
206 : // return all bands instead of requested.
207 : // todo: in 2.0.1 the band list in this dataset may be user-defined
208 :
209 24 : int band_count = nBandCount;
210 24 : if (EQUAL(CPLGetXMLValue(psService, "INTERLEAVE", ""), "PIXEL"))
211 : {
212 24 : band_count = 0;
213 : }
214 :
215 24 : CPLHTTPResult *psResult = nullptr;
216 : CPLErr eErr =
217 24 : GetCoverage(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
218 : band_count, panBandMap, psExtraArg, &psResult);
219 :
220 24 : if (eErr != CE_None)
221 0 : return eErr;
222 :
223 : /* -------------------------------------------------------------------- */
224 : /* Try and open result as a dataset. */
225 : /* -------------------------------------------------------------------- */
226 24 : GDALDataset *poTileDS = GDALOpenResult(psResult);
227 :
228 24 : if (poTileDS == nullptr)
229 0 : return CE_Failure;
230 :
231 : /* -------------------------------------------------------------------- */
232 : /* Verify configuration. */
233 : /* -------------------------------------------------------------------- */
234 48 : if (poTileDS->GetRasterXSize() != nBufXSize ||
235 24 : poTileDS->GetRasterYSize() != nBufYSize)
236 : {
237 0 : CPLError(CE_Failure, CPLE_AppDefined,
238 : "Returned tile does not match expected configuration.\n"
239 : "Got %dx%d instead of %dx%d.",
240 : poTileDS->GetRasterXSize(), poTileDS->GetRasterYSize(),
241 : nBufXSize, nBufYSize);
242 0 : delete poTileDS;
243 0 : return CE_Failure;
244 : }
245 :
246 24 : if (band_count != 0 && ((!osBandIdentifier.empty() &&
247 0 : poTileDS->GetRasterCount() != nBandCount) ||
248 0 : (osBandIdentifier.empty() &&
249 0 : poTileDS->GetRasterCount() != GetRasterCount())))
250 : {
251 0 : CPLError(CE_Failure, CPLE_AppDefined,
252 : "Returned tile does not match expected band count.");
253 0 : delete poTileDS;
254 0 : return CE_Failure;
255 : }
256 :
257 : /* -------------------------------------------------------------------- */
258 : /* Pull requested bands from the downloaded dataset. */
259 : /* -------------------------------------------------------------------- */
260 24 : eErr = CE_None;
261 :
262 69 : for (int iBand = 0; iBand < nBandCount && eErr == CE_None; iBand++)
263 : {
264 45 : GDALRasterBand *poTileBand = nullptr;
265 :
266 45 : if (!osBandIdentifier.empty())
267 18 : poTileBand = poTileDS->GetRasterBand(iBand + 1);
268 : else
269 27 : poTileBand = poTileDS->GetRasterBand(panBandMap[iBand]);
270 :
271 45 : eErr = poTileBand->RasterIO(GF_Read, 0, 0, nBufXSize, nBufYSize,
272 45 : ((GByte *)pData) + iBand * nBandSpace,
273 : nBufXSize, nBufYSize, eBufType, nPixelSpace,
274 : nLineSpace, nullptr);
275 : }
276 :
277 : /* -------------------------------------------------------------------- */
278 : /* Cleanup */
279 : /* -------------------------------------------------------------------- */
280 24 : delete poTileDS;
281 :
282 24 : FlushMemoryResult();
283 :
284 24 : return eErr;
285 : }
286 :
287 : static bool ProcessError(CPLHTTPResult *psResult);
288 :
289 : /************************************************************************/
290 : /* GetCoverage() */
291 : /* */
292 : /* Issue the appropriate version of request for a given window, */
293 : /* buffer size and band list. */
294 : /************************************************************************/
295 :
296 72 : CPLErr WCSDataset::GetCoverage(int nXOff, int nYOff, int nXSize, int nYSize,
297 : int nBufXSize, int nBufYSize, int nBandCount,
298 : const int *panBandList,
299 : GDALRasterIOExtraArg *psExtraArg,
300 : CPLHTTPResult **ppsResult)
301 :
302 : {
303 : /* -------------------------------------------------------------------- */
304 : /* Figure out the georeferenced extents. */
305 : /* -------------------------------------------------------------------- */
306 : std::vector<double> extent =
307 144 : GetNativeExtent(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
308 :
309 : /* -------------------------------------------------------------------- */
310 : /* Build band list if we have the band identifier. */
311 : /* -------------------------------------------------------------------- */
312 144 : std::string osBandList;
313 :
314 72 : if (!osBandIdentifier.empty() && nBandCount > 0 && panBandList != nullptr)
315 : {
316 : int iBand;
317 :
318 0 : for (iBand = 0; iBand < nBandCount; iBand++)
319 : {
320 0 : if (iBand > 0)
321 0 : osBandList += ",";
322 0 : osBandList += CPLString().Printf("%d", panBandList[iBand]);
323 : }
324 : }
325 :
326 : /* -------------------------------------------------------------------- */
327 : /* Construct a KVP GetCoverage request. */
328 : /* -------------------------------------------------------------------- */
329 72 : bool scaled = nBufXSize != nXSize || nBufYSize != nYSize;
330 : std::string osRequest =
331 144 : GetCoverageRequest(scaled, nBufXSize, nBufYSize, extent, osBandList);
332 : // for the test setup we need the actual URLs this driver generates
333 : // fprintf(stdout, "URL=%s\n", osRequest.c_str());
334 :
335 : /* -------------------------------------------------------------------- */
336 : /* Fetch the result. */
337 : /* -------------------------------------------------------------------- */
338 72 : CPLErrorReset();
339 72 : if (psExtraArg && psExtraArg->pfnProgress != nullptr)
340 : {
341 24 : *ppsResult = CPLHTTPFetchEx(
342 24 : osRequest.c_str(), papszHttpOptions, psExtraArg->pfnProgress,
343 : psExtraArg->pProgressData, nullptr, nullptr);
344 : }
345 : else
346 : {
347 48 : *ppsResult = CPLHTTPFetch(osRequest.c_str(), papszHttpOptions);
348 : }
349 :
350 72 : if (ProcessError(*ppsResult))
351 0 : return CE_Failure;
352 : else
353 72 : return CE_None;
354 : }
355 :
356 : /************************************************************************/
357 : /* DescribeCoverage() */
358 : /* */
359 : /* Fetch the DescribeCoverage result and attach it to the */
360 : /* service description. */
361 : /************************************************************************/
362 :
363 25 : int WCSDataset::DescribeCoverage()
364 :
365 : {
366 50 : std::string osRequest;
367 :
368 : /* -------------------------------------------------------------------- */
369 : /* Fetch coverage description for this coverage. */
370 : /* -------------------------------------------------------------------- */
371 :
372 25 : CPLXMLNode *psDC = nullptr;
373 :
374 : // if it is in cache, get it from there
375 : std::string dc_filename =
376 50 : this->GetDescription(); // the WCS_GDAL file (<basename>.xml)
377 25 : dc_filename.erase(dc_filename.length() - 4, 4);
378 25 : dc_filename += ".DC.xml";
379 25 : if (FileIsReadable(dc_filename))
380 : {
381 0 : psDC = CPLParseXMLFile(dc_filename.c_str());
382 : }
383 :
384 25 : if (!psDC)
385 : {
386 25 : osRequest = DescribeCoverageRequest();
387 25 : CPLErrorReset();
388 : CPLHTTPResult *psResult =
389 25 : CPLHTTPFetch(osRequest.c_str(), papszHttpOptions);
390 25 : if (ProcessError(psResult))
391 : {
392 1 : return FALSE;
393 : }
394 :
395 : /* --------------------------------------------------------------------
396 : */
397 : /* Parse result. */
398 : /* --------------------------------------------------------------------
399 : */
400 24 : psDC = CPLParseXMLString((const char *)psResult->pabyData);
401 24 : CPLHTTPDestroyResult(psResult);
402 :
403 24 : if (psDC == nullptr)
404 : {
405 0 : return FALSE;
406 : }
407 :
408 : // if we have cache, put it there
409 24 : if (dc_filename != "")
410 : {
411 24 : CPLSerializeXMLTreeToFile(psDC, dc_filename.c_str());
412 : }
413 : }
414 :
415 24 : CPLStripXMLNamespace(psDC, nullptr, TRUE);
416 :
417 : /* -------------------------------------------------------------------- */
418 : /* Did we get a CoverageOffering? */
419 : /* -------------------------------------------------------------------- */
420 24 : CPLXMLNode *psCO = CoverageOffering(psDC);
421 :
422 24 : if (!psCO)
423 : {
424 0 : CPLDestroyXMLNode(psDC);
425 :
426 0 : CPLError(CE_Failure, CPLE_AppDefined,
427 : "Failed to fetch a <CoverageOffering> back %s.",
428 : osRequest.c_str());
429 0 : return FALSE;
430 : }
431 :
432 : /* -------------------------------------------------------------------- */
433 : /* Duplicate the coverage offering, and insert into */
434 : /* -------------------------------------------------------------------- */
435 24 : CPLXMLNode *psNext = psCO->psNext;
436 24 : psCO->psNext = nullptr;
437 :
438 24 : CPLAddXMLChild(psService, CPLCloneXMLTree(psCO));
439 24 : bServiceDirty = true;
440 :
441 24 : psCO->psNext = psNext;
442 :
443 24 : CPLDestroyXMLNode(psDC);
444 24 : return TRUE;
445 : }
446 :
447 : /************************************************************************/
448 : /* ProcessError() */
449 : /* */
450 : /* Process an HTTP error, reporting it via CPL, and destroying */
451 : /* the HTTP result object. Returns TRUE if there was an error, */
452 : /* or FALSE if the result seems ok. */
453 : /************************************************************************/
454 :
455 121 : static bool ProcessError(CPLHTTPResult *psResult)
456 :
457 : {
458 : /* -------------------------------------------------------------------- */
459 : /* There isn't much we can do in this case. Hopefully an error */
460 : /* was already issued by CPLHTTPFetch() */
461 : /* -------------------------------------------------------------------- */
462 121 : if (psResult == nullptr || psResult->nDataLen == 0)
463 : {
464 1 : CPLHTTPDestroyResult(psResult);
465 1 : return TRUE;
466 : }
467 :
468 : /* -------------------------------------------------------------------- */
469 : /* If we got an html document, we presume it is an error */
470 : /* message and report it verbatim up to a certain size limit. */
471 : /* -------------------------------------------------------------------- */
472 :
473 120 : if (psResult->pszContentType != nullptr &&
474 120 : strstr(psResult->pszContentType, "html") != nullptr)
475 : {
476 0 : std::string osErrorMsg = (char *)psResult->pabyData;
477 :
478 0 : if (osErrorMsg.size() > 2048)
479 0 : osErrorMsg.resize(2048);
480 :
481 0 : CPLError(CE_Failure, CPLE_AppDefined, "Malformed Result:\n%s",
482 : osErrorMsg.c_str());
483 0 : CPLHTTPDestroyResult(psResult);
484 0 : return TRUE;
485 : }
486 :
487 : /* -------------------------------------------------------------------- */
488 : /* Does this look like a service exception? We would like to */
489 : /* check based on the Content-type, but this seems quite */
490 : /* undependable, even from MapServer! */
491 : /* -------------------------------------------------------------------- */
492 120 : if (strstr((const char *)psResult->pabyData, "ExceptionReport"))
493 : {
494 : CPLXMLNode *psTree =
495 0 : CPLParseXMLString((const char *)psResult->pabyData);
496 0 : CPLStripXMLNamespace(psTree, nullptr, TRUE);
497 : std::string msg = CPLGetXMLValue(
498 0 : psTree, "=ServiceExceptionReport.ServiceException", "");
499 0 : if (msg == "")
500 : {
501 : msg = CPLGetXMLValue(
502 0 : psTree, "=ExceptionReport.Exception.exceptionCode", "");
503 0 : if (msg != "")
504 : {
505 0 : msg += ": ";
506 : }
507 : msg += CPLGetXMLValue(
508 0 : psTree, "=ExceptionReport.Exception.ExceptionText", "");
509 : }
510 0 : if (msg != "")
511 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
512 : else
513 0 : CPLError(CE_Failure, CPLE_AppDefined,
514 : "Corrupt Service Exception:\n%s",
515 0 : (const char *)psResult->pabyData);
516 0 : CPLDestroyXMLNode(psTree);
517 0 : CPLHTTPDestroyResult(psResult);
518 0 : return TRUE;
519 : }
520 :
521 : /* -------------------------------------------------------------------- */
522 : /* Hopefully the error already issued by CPLHTTPFetch() is */
523 : /* sufficient. */
524 : /* -------------------------------------------------------------------- */
525 120 : if (CPLGetLastErrorNo() != 0)
526 : {
527 0 : CPLHTTPDestroyResult(psResult);
528 0 : return TRUE;
529 : }
530 :
531 120 : return false;
532 : }
533 :
534 : /************************************************************************/
535 : /* EstablishRasterDetails() */
536 : /* */
537 : /* Do a "test" coverage query to work out the number of bands, */
538 : /* and pixel data type of the remote coverage. */
539 : /************************************************************************/
540 :
541 72 : int WCSDataset::EstablishRasterDetails()
542 :
543 : {
544 72 : CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageOffering");
545 :
546 : const char *pszCols =
547 72 : CPLGetXMLValue(psCO, "dimensionLimit.columns", nullptr);
548 72 : const char *pszRows = CPLGetXMLValue(psCO, "dimensionLimit.rows", nullptr);
549 72 : if (pszCols && pszRows)
550 : {
551 0 : nMaxCols = atoi(pszCols);
552 0 : nMaxRows = atoi(pszRows);
553 0 : SetMetadataItem("MAXNCOLS", pszCols, "IMAGE_STRUCTURE");
554 0 : SetMetadataItem("MAXNROWS", pszRows, "IMAGE_STRUCTURE");
555 : }
556 :
557 : /* -------------------------------------------------------------------- */
558 : /* Do we already have bandcount and pixel type settings? */
559 : /* -------------------------------------------------------------------- */
560 133 : if (CPLGetXMLValue(psService, "BandCount", nullptr) != nullptr &&
561 61 : CPLGetXMLValue(psService, "BandType", nullptr) != nullptr)
562 48 : return TRUE;
563 :
564 : /* -------------------------------------------------------------------- */
565 : /* Fetch a small block of raster data. */
566 : /* -------------------------------------------------------------------- */
567 24 : CPLHTTPResult *psResult = nullptr;
568 : CPLErr eErr;
569 :
570 24 : eErr = GetCoverage(0, 0, 2, 2, 2, 2, 0, nullptr, nullptr, &psResult);
571 24 : if (eErr != CE_None)
572 0 : return false;
573 :
574 : /* -------------------------------------------------------------------- */
575 : /* Try and open result as a dataset. */
576 : /* -------------------------------------------------------------------- */
577 24 : GDALDataset *poDS = GDALOpenResult(psResult);
578 :
579 24 : if (poDS == nullptr)
580 0 : return false;
581 :
582 24 : const auto poSRS = poDS->GetSpatialRef();
583 24 : m_oSRS.Clear();
584 24 : if (poSRS)
585 24 : m_oSRS = *poSRS;
586 :
587 : /* -------------------------------------------------------------------- */
588 : /* Record details. */
589 : /* -------------------------------------------------------------------- */
590 24 : if (poDS->GetRasterCount() < 1)
591 : {
592 0 : delete poDS;
593 0 : return false;
594 : }
595 :
596 24 : if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
597 11 : CPLCreateXMLElementAndValue(
598 : psService, "BandCount",
599 22 : CPLString().Printf("%d", poDS->GetRasterCount()));
600 :
601 24 : CPLCreateXMLElementAndValue(
602 : psService, "BandType",
603 : GDALGetDataTypeName(poDS->GetRasterBand(1)->GetRasterDataType()));
604 :
605 24 : bServiceDirty = true;
606 :
607 : /* -------------------------------------------------------------------- */
608 : /* Cleanup */
609 : /* -------------------------------------------------------------------- */
610 24 : delete poDS;
611 :
612 24 : FlushMemoryResult();
613 :
614 24 : return TRUE;
615 : }
616 :
617 : /************************************************************************/
618 : /* FlushMemoryResult() */
619 : /* */
620 : /* This actually either cleans up the in memory /vsimem/ */
621 : /* temporary file, or the on disk temporary file. */
622 : /************************************************************************/
623 265 : void WCSDataset::FlushMemoryResult()
624 :
625 : {
626 265 : if (!osResultFilename.empty())
627 : {
628 72 : VSIUnlink(osResultFilename.c_str());
629 72 : osResultFilename = "";
630 : }
631 :
632 265 : if (pabySavedDataBuffer)
633 : {
634 72 : CPLFree(pabySavedDataBuffer);
635 72 : pabySavedDataBuffer = nullptr;
636 : }
637 265 : }
638 :
639 : /************************************************************************/
640 : /* GDALOpenResult() */
641 : /* */
642 : /* Open a CPLHTTPResult as a GDALDataset (if possible). First */
643 : /* attempt is to open handle it "in memory". Eventually we */
644 : /* will add support for handling it on file if necessary. */
645 : /* */
646 : /* This method will free CPLHTTPResult, the caller should not */
647 : /* access it after the call. */
648 : /************************************************************************/
649 :
650 72 : GDALDataset *WCSDataset::GDALOpenResult(CPLHTTPResult *psResult)
651 :
652 : {
653 72 : FlushMemoryResult();
654 :
655 72 : CPLDebug("WCS", "GDALOpenResult() on content-type: %s",
656 : psResult->pszContentType);
657 :
658 : /* -------------------------------------------------------------------- */
659 : /* If this is multipart/related content type, we should search */
660 : /* for the second part. */
661 : /* -------------------------------------------------------------------- */
662 72 : GByte *pabyData = psResult->pabyData;
663 72 : int nDataLen = psResult->nDataLen;
664 :
665 216 : if (psResult->pszContentType &&
666 72 : strstr(psResult->pszContentType, "multipart") &&
667 0 : CPLHTTPParseMultipartMime(psResult))
668 : {
669 0 : if (psResult->nMimePartCount > 1)
670 : {
671 0 : pabyData = psResult->pasMimePart[1].pabyData;
672 0 : nDataLen = psResult->pasMimePart[1].nDataLen;
673 :
674 : const char *pszContentTransferEncoding =
675 0 : CSLFetchNameValue(psResult->pasMimePart[1].papszHeaders,
676 : "Content-Transfer-Encoding");
677 0 : if (pszContentTransferEncoding &&
678 0 : EQUAL(pszContentTransferEncoding, "base64"))
679 : {
680 0 : nDataLen = CPLBase64DecodeInPlace(pabyData);
681 : }
682 : }
683 : }
684 :
685 : /* -------------------------------------------------------------------- */
686 : /* Create a memory file from the result. */
687 : /* -------------------------------------------------------------------- */
688 : #ifdef DEBUG_WCS
689 : // this facility is used by requests.pl to generate files for the test
690 : // server
691 : std::string xfn = CPLGetXMLValue(psService, "filename", "");
692 : if (xfn != "")
693 : {
694 : VSILFILE *fpTemp = VSIFOpenL(xfn, "wb");
695 : VSIFWriteL(pabyData, nDataLen, 1, fpTemp);
696 : VSIFCloseL(fpTemp);
697 : }
698 : #endif
699 : // Eventually we should be looking at mime info and stuff to figure
700 : // out an optimal filename, but for now we just use a fixed one.
701 72 : osResultFilename = VSIMemGenerateHiddenFilename("wcsresult.dat");
702 :
703 72 : VSILFILE *fp = VSIFileFromMemBuffer(osResultFilename.c_str(), pabyData,
704 : nDataLen, FALSE);
705 :
706 72 : if (fp == nullptr)
707 : {
708 0 : CPLHTTPDestroyResult(psResult);
709 0 : return nullptr;
710 : }
711 :
712 72 : VSIFCloseL(fp);
713 :
714 : /* -------------------------------------------------------------------- */
715 : /* Try opening this result as a gdaldataset. */
716 : /* -------------------------------------------------------------------- */
717 : GDALDataset *poDS =
718 72 : (GDALDataset *)GDALOpen(osResultFilename.c_str(), GA_ReadOnly);
719 :
720 : /* -------------------------------------------------------------------- */
721 : /* If opening it in memory didn't work, perhaps we need to */
722 : /* write to a temp file on disk? */
723 : /* -------------------------------------------------------------------- */
724 72 : if (poDS == nullptr)
725 : {
726 : std::string osTempFilename =
727 0 : CPLString().Printf("/tmp/%p_wcs.dat", this);
728 0 : VSILFILE *fpTemp = VSIFOpenL(osTempFilename.c_str(), "wb");
729 0 : if (fpTemp == nullptr)
730 : {
731 0 : CPLError(CE_Failure, CPLE_OpenFailed,
732 : "Failed to create temporary file:%s",
733 : osTempFilename.c_str());
734 : }
735 : else
736 : {
737 0 : if (VSIFWriteL(pabyData, nDataLen, 1, fpTemp) != 1)
738 : {
739 0 : CPLError(CE_Failure, CPLE_OpenFailed,
740 : "Failed to write temporary file:%s",
741 : osTempFilename.c_str());
742 0 : VSIFCloseL(fpTemp);
743 0 : VSIUnlink(osTempFilename.c_str());
744 : }
745 : else
746 : {
747 0 : VSIFCloseL(fpTemp);
748 0 : VSIUnlink(osResultFilename.c_str());
749 0 : osResultFilename = std::move(osTempFilename);
750 :
751 : poDS =
752 0 : GDALDataset::Open(osResultFilename.c_str(),
753 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
754 : }
755 : }
756 : }
757 :
758 : /* -------------------------------------------------------------------- */
759 : /* Steal the memory buffer from HTTP result. */
760 : /* -------------------------------------------------------------------- */
761 72 : pabySavedDataBuffer = psResult->pabyData;
762 :
763 72 : psResult->pabyData = nullptr;
764 :
765 72 : if (poDS == nullptr)
766 0 : FlushMemoryResult();
767 :
768 72 : CPLHTTPDestroyResult(psResult);
769 :
770 72 : return poDS;
771 : }
772 :
773 : /************************************************************************/
774 : /* WCSParseVersion() */
775 : /************************************************************************/
776 :
777 217 : static int WCSParseVersion(const char *version)
778 : {
779 217 : if (EQUAL(version, "2.0.1"))
780 63 : return 201;
781 154 : if (EQUAL(version, "1.1.2"))
782 18 : return 112;
783 136 : if (EQUAL(version, "1.1.1"))
784 51 : return 111;
785 85 : if (EQUAL(version, "1.1.0"))
786 39 : return 110;
787 46 : if (EQUAL(version, "1.0.0"))
788 46 : return 100;
789 0 : return 0;
790 : }
791 :
792 : /************************************************************************/
793 : /* Version() */
794 : /************************************************************************/
795 :
796 147 : const char *WCSDataset::Version() const
797 : {
798 147 : if (this->m_Version == 201)
799 67 : return "2.0.1";
800 80 : if (this->m_Version == 112)
801 11 : return "1.1.2";
802 69 : if (this->m_Version == 111)
803 35 : return "1.1.1";
804 34 : if (this->m_Version == 110)
805 11 : return "1.1.0";
806 23 : if (this->m_Version == 100)
807 23 : return "1.0.0";
808 0 : return "";
809 : }
810 :
811 : /************************************************************************/
812 : /* FetchCapabilities() */
813 : /************************************************************************/
814 :
815 : #define WCS_HTTP_OPTIONS "TIMEOUT", "USERPWD", "HTTPAUTH"
816 :
817 24 : static bool FetchCapabilities(GDALOpenInfo *poOpenInfo,
818 : const std::string &urlIn, const std::string &path)
819 : {
820 48 : std::string url = CPLURLAddKVP(urlIn.c_str(), "SERVICE", "WCS");
821 24 : url = CPLURLAddKVP(url.c_str(), "REQUEST", "GetCapabilities");
822 24 : std::string extra = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
823 48 : "GetCapabilitiesExtra", "");
824 24 : if (extra != "")
825 : {
826 0 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
827 0 : for (unsigned int i = 0; i < pairs.size(); ++i)
828 : {
829 0 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
830 0 : url = CPLURLAddKVP(url.c_str(), pair[0].c_str(), pair[1].c_str());
831 : }
832 : }
833 24 : char **options = nullptr;
834 24 : const char *keys[] = {WCS_HTTP_OPTIONS};
835 96 : for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
836 : {
837 : std::string value =
838 144 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, keys[i], "");
839 72 : if (value != "")
840 : {
841 0 : options = CSLSetNameValue(options, keys[i], value.c_str());
842 : }
843 : }
844 24 : CPLHTTPResult *psResult = CPLHTTPFetch(url.c_str(), options);
845 24 : CSLDestroy(options);
846 24 : if (ProcessError(psResult))
847 : {
848 0 : return false;
849 : }
850 48 : CPLXMLTreeCloser doc(CPLParseXMLString((const char *)psResult->pabyData));
851 24 : CPLHTTPDestroyResult(psResult);
852 24 : if (doc.get() == nullptr)
853 : {
854 0 : return false;
855 : }
856 24 : CPLXMLNode *capabilities = doc.get();
857 24 : CPLSerializeXMLTreeToFile(capabilities, path.c_str());
858 24 : return true;
859 : }
860 :
861 : /************************************************************************/
862 : /* CreateFromCapabilities() */
863 : /************************************************************************/
864 :
865 24 : WCSDataset *WCSDataset::CreateFromCapabilities(const std::string &cache,
866 : const std::string &path,
867 : const std::string &url)
868 : {
869 48 : CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
870 24 : if (doc.get() == nullptr)
871 : {
872 0 : return nullptr;
873 : }
874 24 : CPLXMLNode *capabilities = doc.getDocumentElement();
875 24 : if (capabilities == nullptr)
876 : {
877 0 : return nullptr;
878 : }
879 : // get version, this version will overwrite the user's request
880 : int version_from_server =
881 24 : WCSParseVersion(CPLGetXMLValue(capabilities, "version", ""));
882 24 : if (version_from_server == 0)
883 : {
884 : // broken server, assume 1.0.0
885 0 : version_from_server = 100;
886 : }
887 : WCSDataset *poDS;
888 24 : if (version_from_server == 201)
889 : {
890 7 : poDS = new WCSDataset201(cache.c_str());
891 : }
892 17 : else if (version_from_server / 10 == 11)
893 : {
894 12 : poDS = new WCSDataset110(version_from_server, cache.c_str());
895 : }
896 : else
897 : {
898 5 : poDS = new WCSDataset100(cache.c_str());
899 : }
900 24 : if (poDS->ParseCapabilities(capabilities, url) != CE_None)
901 : {
902 0 : delete poDS;
903 0 : return nullptr;
904 : }
905 24 : poDS->SetDescription(RemoveExt(path).c_str());
906 24 : poDS->TrySaveXML();
907 24 : return poDS;
908 : }
909 :
910 : /************************************************************************/
911 : /* CreateFromMetadata() */
912 : /************************************************************************/
913 :
914 24 : WCSDataset *WCSDataset::CreateFromMetadata(const std::string &cache,
915 : const std::string &path)
916 : {
917 : WCSDataset *poDS;
918 24 : if (FileIsReadable(path))
919 : {
920 24 : CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
921 24 : CPLXMLNode *metadata = doc.get();
922 24 : if (metadata == nullptr)
923 : {
924 0 : return nullptr;
925 : }
926 24 : int version_from_metadata = WCSParseVersion(CPLGetXMLValue(
927 24 : SearchChildWithValue(SearchChildWithValue(metadata, "domain", ""),
928 : "key", "WCS_GLOBAL#version"),
929 : nullptr, ""));
930 24 : if (version_from_metadata == 201)
931 : {
932 7 : poDS = new WCSDataset201(cache.c_str());
933 : }
934 17 : else if (version_from_metadata / 10 == 11)
935 : {
936 12 : poDS = new WCSDataset110(version_from_metadata, cache.c_str());
937 : }
938 5 : else if (version_from_metadata / 10 == 10)
939 : {
940 5 : poDS = new WCSDataset100(cache.c_str());
941 : }
942 : else
943 : {
944 0 : CPLError(CE_Failure, CPLE_AppDefined,
945 : "The metadata does not contain version. RECREATE_META?");
946 0 : return nullptr;
947 : }
948 48 : std::string modifiedPath = RemoveExt(RemoveExt(path));
949 24 : poDS->SetDescription(modifiedPath.c_str());
950 24 : poDS->TryLoadXML(); // todo: avoid reload
951 : }
952 : else
953 : {
954 : // obviously there was an error
955 : // processing the Capabilities file
956 : // so we show it to the user
957 0 : GByte *pabyOut = nullptr;
958 0 : std::string modifiedPath = RemoveExt(RemoveExt(path)) + ".xml";
959 0 : if (!VSIIngestFile(nullptr, modifiedPath.c_str(), &pabyOut, nullptr,
960 : -1))
961 0 : return nullptr;
962 0 : std::string error = reinterpret_cast<char *>(pabyOut);
963 0 : if (error.size() > 2048)
964 : {
965 0 : error.resize(2048);
966 : }
967 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error:\n%s", error.c_str());
968 0 : CPLFree(pabyOut);
969 0 : return nullptr;
970 : }
971 24 : return poDS;
972 : }
973 :
974 : /************************************************************************/
975 : /* BootstrapGlobal() */
976 : /************************************************************************/
977 :
978 48 : static WCSDataset *BootstrapGlobal(GDALOpenInfo *poOpenInfo,
979 : const std::string &cache,
980 : const std::string &url)
981 : {
982 : // do we have the capabilities file
983 96 : std::string filename;
984 : bool cached;
985 48 : if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
986 : {
987 0 : return nullptr; // error in cache
988 : }
989 48 : if (!cached)
990 : {
991 24 : filename = "XXXXX";
992 24 : if (AddEntryToCache(cache, url, filename, ".xml") != CE_None)
993 : {
994 0 : return nullptr; // error in cache
995 : }
996 24 : if (!FetchCapabilities(poOpenInfo, url, filename))
997 : {
998 0 : DeleteEntryFromCache(cache, "", url);
999 0 : return nullptr;
1000 : }
1001 24 : return WCSDataset::CreateFromCapabilities(cache, filename, url);
1002 : }
1003 48 : std::string metadata = RemoveExt(filename) + ".aux.xml";
1004 : bool recreate_meta =
1005 24 : CPLFetchBool(poOpenInfo->papszOpenOptions, "RECREATE_META", false);
1006 24 : if (FileIsReadable(metadata) && !recreate_meta)
1007 : {
1008 24 : return WCSDataset::CreateFromMetadata(cache, metadata);
1009 : }
1010 : // we have capabilities but not meta
1011 0 : return WCSDataset::CreateFromCapabilities(cache, filename, url);
1012 : }
1013 :
1014 : /************************************************************************/
1015 : /* CreateService() */
1016 : /************************************************************************/
1017 :
1018 24 : static CPLXMLNode *CreateService(const std::string &base_url,
1019 : const std::string &version,
1020 : const std::string &coverage,
1021 : const std::string ¶meters)
1022 : {
1023 : // construct WCS_GDAL XML into psService
1024 24 : std::string xml = "<WCS_GDAL>";
1025 24 : xml += "<ServiceURL>" + base_url + "</ServiceURL>";
1026 24 : xml += "<Version>" + version + "</Version>";
1027 24 : xml += "<CoverageName>" + coverage + "</CoverageName>";
1028 24 : xml += "<Parameters>" + parameters + "</Parameters>";
1029 24 : xml += "</WCS_GDAL>";
1030 24 : CPLXMLNode *psService = CPLParseXMLString(xml.c_str());
1031 48 : return psService;
1032 : }
1033 :
1034 : /************************************************************************/
1035 : /* UpdateService() */
1036 : /************************************************************************/
1037 :
1038 : #define WCS_SERVICE_OPTIONS \
1039 : "PreferredFormat", "NoDataValue", "BlockXSize", "BlockYSize", \
1040 : "OverviewCount", "GetCoverageExtra", "DescribeCoverageExtra", \
1041 : "Domain", "BandCount", "BandType", "DefaultTime", "CRS"
1042 :
1043 : #define WCS_TWEAK_OPTIONS \
1044 : "OriginAtBoundary", "OuterExtents", "BufSizeAdjust", "OffsetsPositive", \
1045 : "NrOffsets", "GridCRSOptional", "NoGridAxisSwap", "GridAxisLabelSwap", \
1046 : "SubsetAxisSwap", "UseScaleFactor", "INTERLEAVE"
1047 :
1048 72 : static bool UpdateService(CPLXMLNode *service, GDALOpenInfo *poOpenInfo)
1049 : {
1050 72 : bool updated = false;
1051 : // descriptions in frmt_wcs.html
1052 72 : const char *keys[] = {"Subset",
1053 : "RangeSubsetting",
1054 : WCS_URL_PARAMETERS,
1055 : WCS_SERVICE_OPTIONS,
1056 : WCS_TWEAK_OPTIONS,
1057 : WCS_HTTP_OPTIONS
1058 : #ifdef DEBUG_WCS
1059 : ,
1060 : "filename"
1061 : #endif
1062 : };
1063 2808 : for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
1064 : {
1065 : const char *value;
1066 2736 : if (CSLFindString(poOpenInfo->papszOpenOptions, keys[i]) != -1)
1067 : {
1068 23 : value = "TRUE";
1069 : }
1070 : else
1071 : {
1072 2713 : value = CSLFetchNameValue(poOpenInfo->papszOpenOptions, keys[i]);
1073 2713 : if (value == nullptr)
1074 : {
1075 2580 : continue;
1076 : }
1077 : }
1078 156 : updated = CPLUpdateXML(service, keys[i], value) || updated;
1079 : }
1080 72 : return updated;
1081 : }
1082 :
1083 : /************************************************************************/
1084 : /* CreateFromCache() */
1085 : /************************************************************************/
1086 :
1087 0 : static WCSDataset *CreateFromCache(const std::string &cache)
1088 : {
1089 0 : WCSDataset *ds = new WCSDataset201(cache.c_str());
1090 0 : if (!ds)
1091 : {
1092 0 : return nullptr;
1093 : }
1094 0 : char **metadata = nullptr;
1095 0 : std::vector<std::string> contents = ReadCache(cache);
1096 0 : std::string path = "SUBDATASET_";
1097 0 : unsigned int index = 1;
1098 0 : for (unsigned int i = 0; i < contents.size(); ++i)
1099 : {
1100 0 : std::string name = path + CPLString().Printf("%d_", index) + "NAME";
1101 0 : std::string value = "WCS:" + contents[i];
1102 0 : metadata = CSLSetNameValue(metadata, name.c_str(), value.c_str());
1103 0 : index += 1;
1104 : }
1105 0 : ds->SetMetadata(metadata, "SUBDATASETS");
1106 0 : CSLDestroy(metadata);
1107 0 : return ds;
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* ParseURL() */
1112 : /************************************************************************/
1113 :
1114 96 : static void ParseURL(std::string &url, std::string &version,
1115 : std::string &coverage, std::string ¶meters)
1116 : {
1117 96 : version = CPLURLGetValue(url.c_str(), "version");
1118 96 : url = URLRemoveKey(url.c_str(), "version");
1119 : // the default version, the aim is to have version explicitly in cache keys
1120 96 : if (WCSParseVersion(version.c_str()) == 0)
1121 : {
1122 0 : version = "2.0.1";
1123 : }
1124 96 : coverage = CPLURLGetValue(url.c_str(), "coverageid"); // 2.0
1125 96 : if (coverage == "")
1126 : {
1127 96 : coverage = CPLURLGetValue(url.c_str(), "identifiers"); // 1.1
1128 96 : if (coverage == "")
1129 : {
1130 96 : coverage = CPLURLGetValue(url.c_str(), "coverage"); // 1.0
1131 96 : url = URLRemoveKey(url.c_str(), "coverage");
1132 : }
1133 : else
1134 : {
1135 0 : url = URLRemoveKey(url.c_str(), "identifiers");
1136 : }
1137 : }
1138 : else
1139 : {
1140 0 : url = URLRemoveKey(url.c_str(), "coverageid");
1141 : }
1142 96 : size_t pos = url.find("?");
1143 96 : if (pos == std::string::npos)
1144 : {
1145 0 : url += "?";
1146 0 : return;
1147 : }
1148 96 : parameters = url.substr(pos + 1, std::string::npos);
1149 96 : url.erase(pos + 1, std::string::npos);
1150 : }
1151 :
1152 : /************************************************************************/
1153 : /* Open() */
1154 : /************************************************************************/
1155 :
1156 97 : GDALDataset *WCSDataset::Open(GDALOpenInfo *poOpenInfo)
1157 :
1158 : {
1159 97 : if (!WCSDriverIdentify(poOpenInfo))
1160 : {
1161 0 : return nullptr;
1162 : }
1163 :
1164 : std::string cache =
1165 194 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "");
1166 97 : if (!SetupCache(cache, CPLFetchBool(poOpenInfo->papszOpenOptions,
1167 : "CLEAR_CACHE", false)))
1168 : {
1169 0 : return nullptr;
1170 : }
1171 97 : CPLXMLNode *service = nullptr;
1172 97 : char **papszModifiers = nullptr;
1173 :
1174 97 : if (poOpenInfo->nHeaderBytes == 0 &&
1175 96 : STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "WCS:"))
1176 : {
1177 : /* --------------------------------------------------------------------
1178 : */
1179 : /* Filename is WCS:URL */
1180 : /* --------------------------------------------------------------------
1181 : */
1182 96 : std::string url = (const char *)(poOpenInfo->pszFilename + 4);
1183 :
1184 96 : const char *del = CSLFetchNameValue(poOpenInfo->papszOpenOptions,
1185 : "DELETE_FROM_CACHE");
1186 96 : if (del != nullptr)
1187 : {
1188 0 : int k = atoi(del);
1189 0 : std::vector<std::string> contents = ReadCache(cache);
1190 0 : if (k > 0 && k <= (int)contents.size())
1191 : {
1192 0 : DeleteEntryFromCache(cache, "", contents[k - 1]);
1193 : }
1194 : }
1195 :
1196 96 : if (url == "")
1197 : {
1198 0 : return CreateFromCache(cache);
1199 : }
1200 :
1201 96 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "REFRESH_CACHE", false))
1202 : {
1203 0 : DeleteEntryFromCache(cache, "", url);
1204 : }
1205 :
1206 : // the cache:
1207 : // db = key=URL database
1208 : // key.xml = service file
1209 : // key.xml.aux.xml = metadata file
1210 : // key.xml = Capabilities response
1211 : // key.aux.xml = Global metadata
1212 : // key.DC.xml = DescribeCoverage response
1213 :
1214 96 : std::string filename;
1215 : bool cached;
1216 96 : if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
1217 : {
1218 0 : return nullptr; // error in cache
1219 : }
1220 :
1221 96 : std::string full_url = url, version, coverage, parameters;
1222 96 : ParseURL(url, version, coverage, parameters);
1223 :
1224 : // The goal is to get the service XML and a filename for it
1225 :
1226 96 : bool updated = false;
1227 96 : if (cached)
1228 : {
1229 : /* --------------------------------------------------------------------
1230 : */
1231 : /* The fast route, service file is in cache. */
1232 : /* --------------------------------------------------------------------
1233 : */
1234 48 : if (coverage == "")
1235 : {
1236 : std::string url2 =
1237 0 : CPLURLAddKVP(url.c_str(), "version", version.c_str());
1238 0 : WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
1239 0 : return global;
1240 : }
1241 48 : service = CPLParseXMLFile(filename.c_str());
1242 : }
1243 : else
1244 : {
1245 : /* --------------------------------------------------------------------
1246 : */
1247 : /* Get capabilities. */
1248 : /* --------------------------------------------------------------------
1249 : */
1250 : std::string url2 =
1251 48 : CPLURLAddKVP(url.c_str(), "version", version.c_str());
1252 48 : if (parameters != "")
1253 : {
1254 48 : url2 += "&" + parameters;
1255 : }
1256 48 : WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
1257 48 : if (!global)
1258 : {
1259 0 : return nullptr;
1260 : }
1261 48 : if (coverage == "")
1262 : {
1263 24 : return global;
1264 : }
1265 24 : if (version == "")
1266 : {
1267 0 : version = global->Version();
1268 : }
1269 24 : service = CreateService(url, version, coverage, parameters);
1270 : /* --------------------------------------------------------------------
1271 : */
1272 : /* The filename for the new service file. */
1273 : /* --------------------------------------------------------------------
1274 : */
1275 24 : filename = "XXXXX";
1276 24 : if (AddEntryToCache(cache, full_url, filename, ".xml") != CE_None)
1277 : {
1278 0 : return nullptr; // error in cache
1279 : }
1280 : // Create basic service metadata
1281 : // copy global metadata (not SUBDATASETS metadata)
1282 48 : std::string global_base = std::string(global->GetDescription());
1283 48 : std::string global_meta = global_base + ".aux.xml";
1284 48 : std::string capabilities = global_base + ".xml";
1285 48 : CPLXMLTreeCloser doc(CPLParseXMLFile(global_meta.c_str()));
1286 24 : CPLXMLNode *metadata = doc.getDocumentElement();
1287 : CPLXMLNode *domain =
1288 24 : SearchChildWithValue(metadata, "domain", "SUBDATASETS");
1289 24 : if (domain != nullptr)
1290 : {
1291 24 : CPLRemoveXMLChild(metadata, domain);
1292 24 : CPLDestroyXMLNode(domain);
1293 : }
1294 : // get metadata for this coverage from the capabilities XML
1295 48 : CPLXMLTreeCloser doc2(CPLParseXMLFile(capabilities.c_str()));
1296 24 : global->ParseCoverageCapabilities(doc2.getDocumentElement(),
1297 24 : coverage, metadata->psChild);
1298 24 : delete global;
1299 24 : std::string metadata_filename = filename + ".aux.xml";
1300 24 : CPLSerializeXMLTreeToFile(metadata, metadata_filename.c_str());
1301 24 : updated = true;
1302 : }
1303 72 : CPLFree(poOpenInfo->pszFilename);
1304 72 : poOpenInfo->pszFilename = CPLStrdup(filename.c_str());
1305 72 : updated = UpdateService(service, poOpenInfo) || updated;
1306 72 : if (updated || !cached)
1307 : {
1308 72 : CPLSerializeXMLTreeToFile(service, filename.c_str());
1309 72 : }
1310 : }
1311 : /* -------------------------------------------------------------------- */
1312 : /* Is this a WCS_GDAL service description file or "in url" */
1313 : /* equivalent? */
1314 : /* -------------------------------------------------------------------- */
1315 1 : else if (poOpenInfo->nHeaderBytes == 0 &&
1316 0 : STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
1317 : "<WCS_GDAL>"))
1318 : {
1319 0 : service = CPLParseXMLString(poOpenInfo->pszFilename);
1320 : }
1321 1 : else if (poOpenInfo->nHeaderBytes >= 10 &&
1322 1 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "<WCS_GDAL>"))
1323 : {
1324 1 : service = CPLParseXMLFile(poOpenInfo->pszFilename);
1325 : }
1326 : /* -------------------------------------------------------------------- */
1327 : /* Is this apparently a subdataset? */
1328 : /* -------------------------------------------------------------------- */
1329 0 : else if (STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
1330 0 : "WCS_SDS:") &&
1331 0 : poOpenInfo->nHeaderBytes == 0)
1332 : {
1333 : int iLast;
1334 :
1335 0 : papszModifiers = CSLTokenizeString2(poOpenInfo->pszFilename + 8, ",",
1336 : CSLT_HONOURSTRINGS);
1337 :
1338 0 : iLast = CSLCount(papszModifiers) - 1;
1339 0 : if (iLast >= 0)
1340 : {
1341 0 : service = CPLParseXMLFile(papszModifiers[iLast]);
1342 0 : CPLFree(papszModifiers[iLast]);
1343 0 : papszModifiers[iLast] = nullptr;
1344 : }
1345 : }
1346 :
1347 : /* -------------------------------------------------------------------- */
1348 : /* Success so far? */
1349 : /* -------------------------------------------------------------------- */
1350 73 : if (service == nullptr)
1351 : {
1352 0 : CSLDestroy(papszModifiers);
1353 0 : return nullptr;
1354 : }
1355 :
1356 : /* -------------------------------------------------------------------- */
1357 : /* Confirm the requested access is supported. */
1358 : /* -------------------------------------------------------------------- */
1359 73 : if (poOpenInfo->eAccess == GA_Update)
1360 : {
1361 0 : CSLDestroy(papszModifiers);
1362 0 : CPLDestroyXMLNode(service);
1363 0 : ReportUpdateNotSupportedByDriver("WCS");
1364 0 : return nullptr;
1365 : }
1366 :
1367 : /* -------------------------------------------------------------------- */
1368 : /* Check for required minimum fields. */
1369 : /* -------------------------------------------------------------------- */
1370 146 : if (!CPLGetXMLValue(service, "ServiceURL", nullptr) ||
1371 73 : !CPLGetXMLValue(service, "CoverageName", nullptr))
1372 : {
1373 0 : CSLDestroy(papszModifiers);
1374 0 : CPLError(
1375 : CE_Failure, CPLE_OpenFailed,
1376 : "Missing one or both of ServiceURL and CoverageName elements.\n"
1377 : "See WCS driver documentation for details on service description "
1378 : "file format.");
1379 :
1380 0 : CPLDestroyXMLNode(service);
1381 0 : return nullptr;
1382 : }
1383 :
1384 : /* -------------------------------------------------------------------- */
1385 : /* What version are we working with? */
1386 : /* -------------------------------------------------------------------- */
1387 73 : const char *pszVersion = CPLGetXMLValue(service, "Version", "1.0.0");
1388 :
1389 73 : int nVersion = WCSParseVersion(pszVersion);
1390 :
1391 73 : if (nVersion == 0)
1392 : {
1393 0 : CSLDestroy(papszModifiers);
1394 0 : CPLDestroyXMLNode(service);
1395 0 : return nullptr;
1396 : }
1397 :
1398 : /* -------------------------------------------------------------------- */
1399 : /* Create a corresponding GDALDataset. */
1400 : /* -------------------------------------------------------------------- */
1401 : WCSDataset *poDS;
1402 73 : if (nVersion == 201)
1403 : {
1404 21 : poDS = new WCSDataset201(cache.c_str());
1405 : }
1406 52 : else if (nVersion / 10 == 11)
1407 : {
1408 36 : poDS = new WCSDataset110(nVersion, cache.c_str());
1409 : }
1410 : else
1411 : {
1412 16 : poDS = new WCSDataset100(cache.c_str());
1413 : }
1414 :
1415 73 : poDS->psService = service;
1416 73 : poDS->SetDescription(poOpenInfo->pszFilename);
1417 73 : poDS->papszSDSModifiers = papszModifiers;
1418 : // WCS:URL => basic metadata was already made
1419 : // Metadata is needed in ExtractGridInfo
1420 73 : poDS->TryLoadXML();
1421 :
1422 : /* -------------------------------------------------------------------- */
1423 : /* Capture HTTP parameters. */
1424 : /* -------------------------------------------------------------------- */
1425 : const char *pszParam;
1426 :
1427 73 : poDS->papszHttpOptions =
1428 73 : CSLSetNameValue(poDS->papszHttpOptions, "TIMEOUT",
1429 : CPLGetXMLValue(service, "Timeout", "30"));
1430 :
1431 73 : pszParam = CPLGetXMLValue(service, "HTTPAUTH", nullptr);
1432 73 : if (pszParam)
1433 0 : poDS->papszHttpOptions =
1434 0 : CSLSetNameValue(poDS->papszHttpOptions, "HTTPAUTH", pszParam);
1435 :
1436 73 : pszParam = CPLGetXMLValue(service, "USERPWD", nullptr);
1437 73 : if (pszParam)
1438 0 : poDS->papszHttpOptions =
1439 0 : CSLSetNameValue(poDS->papszHttpOptions, "USERPWD", pszParam);
1440 :
1441 : /* -------------------------------------------------------------------- */
1442 : /* If we don't have the DescribeCoverage result for this */
1443 : /* coverage, fetch it now. */
1444 : /* -------------------------------------------------------------------- */
1445 136 : if (CPLGetXMLNode(service, "CoverageOffering") == nullptr &&
1446 63 : CPLGetXMLNode(service, "CoverageDescription") == nullptr)
1447 : {
1448 25 : if (!poDS->DescribeCoverage())
1449 : {
1450 1 : delete poDS;
1451 1 : return nullptr;
1452 : }
1453 : }
1454 :
1455 : /* -------------------------------------------------------------------- */
1456 : /* Extract coordinate system, grid size, and geotransform from */
1457 : /* the coverage description and/or service description */
1458 : /* information. */
1459 : /* -------------------------------------------------------------------- */
1460 72 : if (!poDS->ExtractGridInfo())
1461 : {
1462 0 : delete poDS;
1463 0 : return nullptr;
1464 : }
1465 :
1466 : /* -------------------------------------------------------------------- */
1467 : /* Leave now or there may be a GetCoverage call. */
1468 : /* */
1469 : /* -------------------------------------------------------------------- */
1470 72 : int nBandCount = -1;
1471 144 : std::string sBandCount = CPLGetXMLValue(service, "BandCount", "");
1472 72 : if (sBandCount != "")
1473 : {
1474 61 : nBandCount = atoi(sBandCount.c_str());
1475 : }
1476 72 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "SKIP_GETCOVERAGE", false) ||
1477 : nBandCount == 0)
1478 : {
1479 0 : return poDS;
1480 : }
1481 :
1482 : /* -------------------------------------------------------------------- */
1483 : /* Extract band count and type from a sample. */
1484 : /* -------------------------------------------------------------------- */
1485 72 : if (!poDS->EstablishRasterDetails()) // todo: do this only if missing info
1486 : {
1487 0 : delete poDS;
1488 0 : return nullptr;
1489 : }
1490 :
1491 : /* -------------------------------------------------------------------- */
1492 : /* It is ok to not have bands. The user just needs to supply */
1493 : /* more information. */
1494 : /* -------------------------------------------------------------------- */
1495 72 : nBandCount = atoi(CPLGetXMLValue(service, "BandCount", "0"));
1496 72 : if (nBandCount == 0)
1497 : {
1498 0 : return poDS;
1499 : }
1500 :
1501 : /* -------------------------------------------------------------------- */
1502 : /* Create band information objects. */
1503 : /* -------------------------------------------------------------------- */
1504 : int iBand;
1505 :
1506 72 : if (!GDALCheckBandCount(nBandCount, FALSE))
1507 : {
1508 0 : delete poDS;
1509 0 : return nullptr;
1510 : }
1511 :
1512 207 : for (iBand = 0; iBand < nBandCount; iBand++)
1513 : {
1514 135 : WCSRasterBand *band = new WCSRasterBand(poDS, iBand + 1, -1);
1515 : // copy band specific metadata to the band
1516 135 : char **md_from = poDS->GetMetadata("");
1517 135 : char **md_to = nullptr;
1518 135 : if (md_from)
1519 : {
1520 270 : std::string our_key = CPLString().Printf("FIELD_%d_", iBand + 1);
1521 3192 : for (char **from = md_from; *from != nullptr; ++from)
1522 : {
1523 6114 : std::vector<std::string> kv = Split(*from, "=");
1524 6114 : if (kv.size() > 1 &&
1525 3057 : STARTS_WITH(kv[0].c_str(), our_key.c_str()))
1526 : {
1527 174 : std::string key = kv[0];
1528 87 : std::string value = kv[1];
1529 87 : key.erase(0, our_key.length());
1530 87 : md_to = CSLSetNameValue(md_to, key.c_str(), value.c_str());
1531 : }
1532 : }
1533 : }
1534 135 : band->SetMetadata(md_to, "");
1535 135 : CSLDestroy(md_to);
1536 135 : poDS->SetBand(iBand + 1, band);
1537 : }
1538 :
1539 : /* -------------------------------------------------------------------- */
1540 : /* Set time metadata on the dataset if we are selecting a */
1541 : /* temporal slice. */
1542 : /* -------------------------------------------------------------------- */
1543 72 : std::string osTime = CSLFetchNameValueDef(poDS->papszSDSModifiers, "time",
1544 72 : poDS->osDefaultTime.c_str());
1545 :
1546 72 : if (osTime != "")
1547 0 : poDS->GDALMajorObject::SetMetadataItem("TIME_POSITION", osTime.c_str());
1548 :
1549 : /* -------------------------------------------------------------------- */
1550 : /* Do we have a band identifier to select only a subset of bands? */
1551 : /* -------------------------------------------------------------------- */
1552 72 : poDS->osBandIdentifier = CPLGetXMLValue(service, "BandIdentifier", "");
1553 :
1554 : /* -------------------------------------------------------------------- */
1555 : /* Do we have time based subdatasets? If so, record them in */
1556 : /* metadata. Note we don't do subdatasets if this is a */
1557 : /* subdataset or if this is an all-in-memory service. */
1558 : /* -------------------------------------------------------------------- */
1559 216 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WCS_SDS:") &&
1560 144 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "<WCS_GDAL>") &&
1561 72 : !poDS->aosTimePositions.empty())
1562 : {
1563 0 : char **papszSubdatasets = nullptr;
1564 : int iTime;
1565 :
1566 0 : for (iTime = 0; iTime < (int)poDS->aosTimePositions.size(); iTime++)
1567 : {
1568 0 : std::string osName;
1569 0 : std::string osValue;
1570 :
1571 0 : osName = CPLString().Printf("SUBDATASET_%d_NAME", iTime + 1);
1572 0 : osValue = CPLString().Printf("WCS_SDS:time=\"%s\",%s",
1573 0 : poDS->aosTimePositions[iTime].c_str(),
1574 0 : poOpenInfo->pszFilename);
1575 0 : papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
1576 : osValue.c_str());
1577 :
1578 : std::string osCoverage =
1579 0 : CPLGetXMLValue(poDS->psService, "CoverageName", "");
1580 :
1581 0 : osName = CPLString().Printf("SUBDATASET_%d_DESC", iTime + 1);
1582 : osValue =
1583 0 : CPLString().Printf("Coverage %s at time %s", osCoverage.c_str(),
1584 0 : poDS->aosTimePositions[iTime].c_str());
1585 0 : papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
1586 : osValue.c_str());
1587 : }
1588 :
1589 0 : poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS");
1590 :
1591 0 : CSLDestroy(papszSubdatasets);
1592 : }
1593 :
1594 : /* -------------------------------------------------------------------- */
1595 : /* Initialize any PAM information. */
1596 : /* -------------------------------------------------------------------- */
1597 72 : poDS->TryLoadXML();
1598 72 : return poDS;
1599 : }
1600 :
1601 : /************************************************************************/
1602 : /* GetGeoTransform() */
1603 : /************************************************************************/
1604 :
1605 72 : CPLErr WCSDataset::GetGeoTransform(GDALGeoTransform >) const
1606 :
1607 : {
1608 72 : gt = m_gt;
1609 72 : return CE_None;
1610 : }
1611 :
1612 : /************************************************************************/
1613 : /* GetSpatialRef() */
1614 : /************************************************************************/
1615 :
1616 48 : const OGRSpatialReference *WCSDataset::GetSpatialRef() const
1617 :
1618 : {
1619 48 : const auto poSRS = GDALPamDataset::GetSpatialRef();
1620 48 : if (poSRS)
1621 0 : return poSRS;
1622 :
1623 48 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1624 : }
1625 :
1626 : /************************************************************************/
1627 : /* GetFileList() */
1628 : /************************************************************************/
1629 :
1630 0 : char **WCSDataset::GetFileList()
1631 :
1632 : {
1633 0 : char **papszFileList = GDALPamDataset::GetFileList();
1634 :
1635 : /* -------------------------------------------------------------------- */
1636 : /* ESRI also wishes to include service urls in the file list */
1637 : /* though this is not currently part of the general definition */
1638 : /* of GetFileList() for GDAL. */
1639 : /* -------------------------------------------------------------------- */
1640 : #ifdef ESRI_BUILD
1641 : std::string file;
1642 : file.Printf("%s%s", CPLGetXMLValue(psService, "ServiceURL", ""),
1643 : CPLGetXMLValue(psService, "CoverageName", ""));
1644 : papszFileList = CSLAddString(papszFileList, file.c_str());
1645 : #endif /* def ESRI_BUILD */
1646 :
1647 0 : return papszFileList;
1648 : }
1649 :
1650 : /************************************************************************/
1651 : /* GetMetadataDomainList() */
1652 : /************************************************************************/
1653 :
1654 0 : char **WCSDataset::GetMetadataDomainList()
1655 : {
1656 0 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
1657 0 : TRUE, "xml:CoverageOffering", nullptr);
1658 : }
1659 :
1660 : /************************************************************************/
1661 : /* GetMetadata() */
1662 : /************************************************************************/
1663 :
1664 348 : char **WCSDataset::GetMetadata(const char *pszDomain)
1665 :
1666 : {
1667 348 : if (pszDomain == nullptr || !EQUAL(pszDomain, "xml:CoverageOffering"))
1668 348 : return GDALPamDataset::GetMetadata(pszDomain);
1669 :
1670 0 : CPLXMLNode *psNode = CPLGetXMLNode(psService, "CoverageOffering");
1671 :
1672 0 : if (psNode == nullptr)
1673 0 : psNode = CPLGetXMLNode(psService, "CoverageDescription");
1674 :
1675 0 : if (psNode == nullptr)
1676 0 : return nullptr;
1677 :
1678 0 : if (apszCoverageOfferingMD[0] == nullptr)
1679 : {
1680 0 : CPLXMLNode *psNext = psNode->psNext;
1681 0 : psNode->psNext = nullptr;
1682 :
1683 0 : apszCoverageOfferingMD[0] = CPLSerializeXMLTree(psNode);
1684 :
1685 0 : psNode->psNext = psNext;
1686 : }
1687 :
1688 0 : return apszCoverageOfferingMD;
1689 : }
1690 :
1691 : /************************************************************************/
1692 : /* GDALRegister_WCS() */
1693 : /************************************************************************/
1694 :
1695 1911 : void GDALRegister_WCS()
1696 :
1697 : {
1698 1911 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1699 282 : return;
1700 :
1701 1629 : GDALDriver *poDriver = new GDALDriver();
1702 1629 : WCSDriverSetCommonMetadata(poDriver);
1703 :
1704 1629 : poDriver->pfnOpen = WCSDataset::Open;
1705 :
1706 1629 : GetGDALDriverManager()->RegisterDriver(poDriver);
1707 : }
|