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