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 : const 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 = osTempFilename;
756 :
757 0 : poDS = (GDALDataset *)GDALOpen(osResultFilename.c_str(),
758 : GA_ReadOnly);
759 : }
760 : }
761 : }
762 :
763 : /* -------------------------------------------------------------------- */
764 : /* Steal the memory buffer from HTTP result. */
765 : /* -------------------------------------------------------------------- */
766 72 : pabySavedDataBuffer = psResult->pabyData;
767 :
768 72 : psResult->pabyData = nullptr;
769 :
770 72 : if (poDS == nullptr)
771 0 : FlushMemoryResult();
772 :
773 72 : CPLHTTPDestroyResult(psResult);
774 :
775 72 : return poDS;
776 : }
777 :
778 : /************************************************************************/
779 : /* WCSParseVersion() */
780 : /************************************************************************/
781 :
782 217 : static int WCSParseVersion(const char *version)
783 : {
784 217 : if (EQUAL(version, "2.0.1"))
785 63 : return 201;
786 154 : if (EQUAL(version, "1.1.2"))
787 18 : return 112;
788 136 : if (EQUAL(version, "1.1.1"))
789 51 : return 111;
790 85 : if (EQUAL(version, "1.1.0"))
791 39 : return 110;
792 46 : if (EQUAL(version, "1.0.0"))
793 46 : return 100;
794 0 : return 0;
795 : }
796 :
797 : /************************************************************************/
798 : /* Version() */
799 : /************************************************************************/
800 :
801 147 : const char *WCSDataset::Version() const
802 : {
803 147 : if (this->m_Version == 201)
804 67 : return "2.0.1";
805 80 : if (this->m_Version == 112)
806 11 : return "1.1.2";
807 69 : if (this->m_Version == 111)
808 35 : return "1.1.1";
809 34 : if (this->m_Version == 110)
810 11 : return "1.1.0";
811 23 : if (this->m_Version == 100)
812 23 : return "1.0.0";
813 0 : return "";
814 : }
815 :
816 : /************************************************************************/
817 : /* FetchCapabilities() */
818 : /************************************************************************/
819 :
820 : #define WCS_HTTP_OPTIONS "TIMEOUT", "USERPWD", "HTTPAUTH"
821 :
822 24 : static bool FetchCapabilities(GDALOpenInfo *poOpenInfo,
823 : const std::string &urlIn, const std::string &path)
824 : {
825 48 : std::string url = CPLURLAddKVP(urlIn.c_str(), "SERVICE", "WCS");
826 24 : url = CPLURLAddKVP(url.c_str(), "REQUEST", "GetCapabilities");
827 24 : std::string extra = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
828 48 : "GetCapabilitiesExtra", "");
829 24 : if (extra != "")
830 : {
831 0 : std::vector<std::string> pairs = Split(extra.c_str(), "&");
832 0 : for (unsigned int i = 0; i < pairs.size(); ++i)
833 : {
834 0 : std::vector<std::string> pair = Split(pairs[i].c_str(), "=");
835 0 : url = CPLURLAddKVP(url.c_str(), pair[0].c_str(), pair[1].c_str());
836 : }
837 : }
838 24 : char **options = nullptr;
839 24 : const char *keys[] = {WCS_HTTP_OPTIONS};
840 96 : for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
841 : {
842 : std::string value =
843 144 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, keys[i], "");
844 72 : if (value != "")
845 : {
846 0 : options = CSLSetNameValue(options, keys[i], value.c_str());
847 : }
848 : }
849 24 : CPLHTTPResult *psResult = CPLHTTPFetch(url.c_str(), options);
850 24 : CSLDestroy(options);
851 24 : if (ProcessError(psResult))
852 : {
853 0 : return false;
854 : }
855 48 : CPLXMLTreeCloser doc(CPLParseXMLString((const char *)psResult->pabyData));
856 24 : CPLHTTPDestroyResult(psResult);
857 24 : if (doc.get() == nullptr)
858 : {
859 0 : return false;
860 : }
861 24 : CPLXMLNode *capabilities = doc.get();
862 24 : CPLSerializeXMLTreeToFile(capabilities, path.c_str());
863 24 : return true;
864 : }
865 :
866 : /************************************************************************/
867 : /* CreateFromCapabilities() */
868 : /************************************************************************/
869 :
870 24 : WCSDataset *WCSDataset::CreateFromCapabilities(const std::string &cache,
871 : const std::string &path,
872 : const std::string &url)
873 : {
874 48 : CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
875 24 : if (doc.get() == nullptr)
876 : {
877 0 : return nullptr;
878 : }
879 24 : CPLXMLNode *capabilities = doc.getDocumentElement();
880 24 : if (capabilities == nullptr)
881 : {
882 0 : return nullptr;
883 : }
884 : // get version, this version will overwrite the user's request
885 : int version_from_server =
886 24 : WCSParseVersion(CPLGetXMLValue(capabilities, "version", ""));
887 24 : if (version_from_server == 0)
888 : {
889 : // broken server, assume 1.0.0
890 0 : version_from_server = 100;
891 : }
892 : WCSDataset *poDS;
893 24 : if (version_from_server == 201)
894 : {
895 7 : poDS = new WCSDataset201(cache.c_str());
896 : }
897 17 : else if (version_from_server / 10 == 11)
898 : {
899 12 : poDS = new WCSDataset110(version_from_server, cache.c_str());
900 : }
901 : else
902 : {
903 5 : poDS = new WCSDataset100(cache.c_str());
904 : }
905 24 : if (poDS->ParseCapabilities(capabilities, url) != CE_None)
906 : {
907 0 : delete poDS;
908 0 : return nullptr;
909 : }
910 24 : poDS->SetDescription(RemoveExt(path).c_str());
911 24 : poDS->TrySaveXML();
912 24 : return poDS;
913 : }
914 :
915 : /************************************************************************/
916 : /* CreateFromMetadata() */
917 : /************************************************************************/
918 :
919 24 : WCSDataset *WCSDataset::CreateFromMetadata(const std::string &cache,
920 : const std::string &path)
921 : {
922 : WCSDataset *poDS;
923 24 : if (FileIsReadable(path))
924 : {
925 24 : CPLXMLTreeCloser doc(CPLParseXMLFile(path.c_str()));
926 24 : CPLXMLNode *metadata = doc.get();
927 24 : if (metadata == nullptr)
928 : {
929 0 : return nullptr;
930 : }
931 24 : int version_from_metadata = WCSParseVersion(CPLGetXMLValue(
932 24 : SearchChildWithValue(SearchChildWithValue(metadata, "domain", ""),
933 : "key", "WCS_GLOBAL#version"),
934 : nullptr, ""));
935 24 : if (version_from_metadata == 201)
936 : {
937 7 : poDS = new WCSDataset201(cache.c_str());
938 : }
939 17 : else if (version_from_metadata / 10 == 11)
940 : {
941 12 : poDS = new WCSDataset110(version_from_metadata, cache.c_str());
942 : }
943 5 : else if (version_from_metadata / 10 == 10)
944 : {
945 5 : poDS = new WCSDataset100(cache.c_str());
946 : }
947 : else
948 : {
949 0 : CPLError(CE_Failure, CPLE_AppDefined,
950 : "The metadata does not contain version. RECREATE_META?");
951 0 : return nullptr;
952 : }
953 48 : std::string modifiedPath = RemoveExt(RemoveExt(path));
954 24 : poDS->SetDescription(modifiedPath.c_str());
955 24 : poDS->TryLoadXML(); // todo: avoid reload
956 : }
957 : else
958 : {
959 : // obviously there was an error
960 : // processing the Capabilities file
961 : // so we show it to the user
962 0 : GByte *pabyOut = nullptr;
963 0 : std::string modifiedPath = RemoveExt(RemoveExt(path)) + ".xml";
964 0 : if (!VSIIngestFile(nullptr, modifiedPath.c_str(), &pabyOut, nullptr,
965 : -1))
966 0 : return nullptr;
967 0 : std::string error = reinterpret_cast<char *>(pabyOut);
968 0 : if (error.size() > 2048)
969 : {
970 0 : error.resize(2048);
971 : }
972 0 : CPLError(CE_Failure, CPLE_AppDefined, "Error:\n%s", error.c_str());
973 0 : CPLFree(pabyOut);
974 0 : return nullptr;
975 : }
976 24 : return poDS;
977 : }
978 :
979 : /************************************************************************/
980 : /* BootstrapGlobal() */
981 : /************************************************************************/
982 :
983 48 : static WCSDataset *BootstrapGlobal(GDALOpenInfo *poOpenInfo,
984 : const std::string &cache,
985 : const std::string &url)
986 : {
987 : // do we have the capabilities file
988 96 : std::string filename;
989 : bool cached;
990 48 : if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
991 : {
992 0 : return nullptr; // error in cache
993 : }
994 48 : if (!cached)
995 : {
996 24 : filename = "XXXXX";
997 24 : if (AddEntryToCache(cache, url, filename, ".xml") != CE_None)
998 : {
999 0 : return nullptr; // error in cache
1000 : }
1001 24 : if (!FetchCapabilities(poOpenInfo, url, filename))
1002 : {
1003 0 : DeleteEntryFromCache(cache, "", url);
1004 0 : return nullptr;
1005 : }
1006 24 : return WCSDataset::CreateFromCapabilities(cache, filename, url);
1007 : }
1008 48 : std::string metadata = RemoveExt(filename) + ".aux.xml";
1009 : bool recreate_meta =
1010 24 : CPLFetchBool(poOpenInfo->papszOpenOptions, "RECREATE_META", false);
1011 24 : if (FileIsReadable(metadata) && !recreate_meta)
1012 : {
1013 24 : return WCSDataset::CreateFromMetadata(cache, metadata);
1014 : }
1015 : // we have capabilities but not meta
1016 0 : return WCSDataset::CreateFromCapabilities(cache, filename, url);
1017 : }
1018 :
1019 : /************************************************************************/
1020 : /* CreateService() */
1021 : /************************************************************************/
1022 :
1023 24 : static CPLXMLNode *CreateService(const std::string &base_url,
1024 : const std::string &version,
1025 : const std::string &coverage,
1026 : const std::string ¶meters)
1027 : {
1028 : // construct WCS_GDAL XML into psService
1029 24 : std::string xml = "<WCS_GDAL>";
1030 24 : xml += "<ServiceURL>" + base_url + "</ServiceURL>";
1031 24 : xml += "<Version>" + version + "</Version>";
1032 24 : xml += "<CoverageName>" + coverage + "</CoverageName>";
1033 24 : xml += "<Parameters>" + parameters + "</Parameters>";
1034 24 : xml += "</WCS_GDAL>";
1035 24 : CPLXMLNode *psService = CPLParseXMLString(xml.c_str());
1036 48 : return psService;
1037 : }
1038 :
1039 : /************************************************************************/
1040 : /* UpdateService() */
1041 : /************************************************************************/
1042 :
1043 : #define WCS_SERVICE_OPTIONS \
1044 : "PreferredFormat", "NoDataValue", "BlockXSize", "BlockYSize", \
1045 : "OverviewCount", "GetCoverageExtra", "DescribeCoverageExtra", \
1046 : "Domain", "BandCount", "BandType", "DefaultTime", "CRS"
1047 :
1048 : #define WCS_TWEAK_OPTIONS \
1049 : "OriginAtBoundary", "OuterExtents", "BufSizeAdjust", "OffsetsPositive", \
1050 : "NrOffsets", "GridCRSOptional", "NoGridAxisSwap", "GridAxisLabelSwap", \
1051 : "SubsetAxisSwap", "UseScaleFactor", "INTERLEAVE"
1052 :
1053 72 : static bool UpdateService(CPLXMLNode *service, GDALOpenInfo *poOpenInfo)
1054 : {
1055 72 : bool updated = false;
1056 : // descriptions in frmt_wcs.html
1057 72 : const char *keys[] = {"Subset",
1058 : "RangeSubsetting",
1059 : WCS_URL_PARAMETERS,
1060 : WCS_SERVICE_OPTIONS,
1061 : WCS_TWEAK_OPTIONS,
1062 : WCS_HTTP_OPTIONS
1063 : #ifdef DEBUG_WCS
1064 : ,
1065 : "filename"
1066 : #endif
1067 : };
1068 2808 : for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
1069 : {
1070 : const char *value;
1071 2736 : if (CSLFindString(poOpenInfo->papszOpenOptions, keys[i]) != -1)
1072 : {
1073 23 : value = "TRUE";
1074 : }
1075 : else
1076 : {
1077 2713 : value = CSLFetchNameValue(poOpenInfo->papszOpenOptions, keys[i]);
1078 2713 : if (value == nullptr)
1079 : {
1080 2580 : continue;
1081 : }
1082 : }
1083 156 : updated = CPLUpdateXML(service, keys[i], value) || updated;
1084 : }
1085 72 : return updated;
1086 : }
1087 :
1088 : /************************************************************************/
1089 : /* CreateFromCache() */
1090 : /************************************************************************/
1091 :
1092 0 : static WCSDataset *CreateFromCache(const std::string &cache)
1093 : {
1094 0 : WCSDataset *ds = new WCSDataset201(cache.c_str());
1095 0 : if (!ds)
1096 : {
1097 0 : return nullptr;
1098 : }
1099 0 : char **metadata = nullptr;
1100 0 : std::vector<std::string> contents = ReadCache(cache);
1101 0 : std::string path = "SUBDATASET_";
1102 0 : unsigned int index = 1;
1103 0 : for (unsigned int i = 0; i < contents.size(); ++i)
1104 : {
1105 0 : std::string name = path + CPLString().Printf("%d_", index) + "NAME";
1106 0 : std::string value = "WCS:" + contents[i];
1107 0 : metadata = CSLSetNameValue(metadata, name.c_str(), value.c_str());
1108 0 : index += 1;
1109 : }
1110 0 : ds->SetMetadata(metadata, "SUBDATASETS");
1111 0 : CSLDestroy(metadata);
1112 0 : return ds;
1113 : }
1114 :
1115 : /************************************************************************/
1116 : /* ParseURL() */
1117 : /************************************************************************/
1118 :
1119 96 : static void ParseURL(std::string &url, std::string &version,
1120 : std::string &coverage, std::string ¶meters)
1121 : {
1122 96 : version = CPLURLGetValue(url.c_str(), "version");
1123 96 : url = URLRemoveKey(url.c_str(), "version");
1124 : // the default version, the aim is to have version explicitly in cache keys
1125 96 : if (WCSParseVersion(version.c_str()) == 0)
1126 : {
1127 0 : version = "2.0.1";
1128 : }
1129 96 : coverage = CPLURLGetValue(url.c_str(), "coverageid"); // 2.0
1130 96 : if (coverage == "")
1131 : {
1132 96 : coverage = CPLURLGetValue(url.c_str(), "identifiers"); // 1.1
1133 96 : if (coverage == "")
1134 : {
1135 96 : coverage = CPLURLGetValue(url.c_str(), "coverage"); // 1.0
1136 96 : url = URLRemoveKey(url.c_str(), "coverage");
1137 : }
1138 : else
1139 : {
1140 0 : url = URLRemoveKey(url.c_str(), "identifiers");
1141 : }
1142 : }
1143 : else
1144 : {
1145 0 : url = URLRemoveKey(url.c_str(), "coverageid");
1146 : }
1147 96 : size_t pos = url.find("?");
1148 96 : if (pos == std::string::npos)
1149 : {
1150 0 : url += "?";
1151 0 : return;
1152 : }
1153 96 : parameters = url.substr(pos + 1, std::string::npos);
1154 96 : url.erase(pos + 1, std::string::npos);
1155 : }
1156 :
1157 : /************************************************************************/
1158 : /* Open() */
1159 : /************************************************************************/
1160 :
1161 97 : GDALDataset *WCSDataset::Open(GDALOpenInfo *poOpenInfo)
1162 :
1163 : {
1164 97 : if (!WCSDriverIdentify(poOpenInfo))
1165 : {
1166 0 : return nullptr;
1167 : }
1168 :
1169 : std::string cache =
1170 194 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "");
1171 97 : if (!SetupCache(cache, CPLFetchBool(poOpenInfo->papszOpenOptions,
1172 : "CLEAR_CACHE", false)))
1173 : {
1174 0 : return nullptr;
1175 : }
1176 97 : CPLXMLNode *service = nullptr;
1177 97 : char **papszModifiers = nullptr;
1178 :
1179 97 : if (poOpenInfo->nHeaderBytes == 0 &&
1180 96 : STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "WCS:"))
1181 : {
1182 : /* --------------------------------------------------------------------
1183 : */
1184 : /* Filename is WCS:URL */
1185 : /* --------------------------------------------------------------------
1186 : */
1187 96 : std::string url = (const char *)(poOpenInfo->pszFilename + 4);
1188 :
1189 96 : const char *del = CSLFetchNameValue(poOpenInfo->papszOpenOptions,
1190 : "DELETE_FROM_CACHE");
1191 96 : if (del != nullptr)
1192 : {
1193 0 : int k = atoi(del);
1194 0 : std::vector<std::string> contents = ReadCache(cache);
1195 0 : if (k > 0 && k <= (int)contents.size())
1196 : {
1197 0 : DeleteEntryFromCache(cache, "", contents[k - 1]);
1198 : }
1199 : }
1200 :
1201 96 : if (url == "")
1202 : {
1203 0 : return CreateFromCache(cache);
1204 : }
1205 :
1206 96 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "REFRESH_CACHE", false))
1207 : {
1208 0 : DeleteEntryFromCache(cache, "", url);
1209 : }
1210 :
1211 : // the cache:
1212 : // db = key=URL database
1213 : // key.xml = service file
1214 : // key.xml.aux.xml = metadata file
1215 : // key.xml = Capabilities response
1216 : // key.aux.xml = Global metadata
1217 : // key.DC.xml = DescribeCoverage response
1218 :
1219 96 : std::string filename;
1220 : bool cached;
1221 96 : if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
1222 : {
1223 0 : return nullptr; // error in cache
1224 : }
1225 :
1226 96 : std::string full_url = url, version, coverage, parameters;
1227 96 : ParseURL(url, version, coverage, parameters);
1228 :
1229 : // The goal is to get the service XML and a filename for it
1230 :
1231 96 : bool updated = false;
1232 96 : if (cached)
1233 : {
1234 : /* --------------------------------------------------------------------
1235 : */
1236 : /* The fast route, service file is in cache. */
1237 : /* --------------------------------------------------------------------
1238 : */
1239 48 : if (coverage == "")
1240 : {
1241 : std::string url2 =
1242 0 : CPLURLAddKVP(url.c_str(), "version", version.c_str());
1243 0 : WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
1244 0 : return global;
1245 : }
1246 48 : service = CPLParseXMLFile(filename.c_str());
1247 : }
1248 : else
1249 : {
1250 : /* --------------------------------------------------------------------
1251 : */
1252 : /* Get capabilities. */
1253 : /* --------------------------------------------------------------------
1254 : */
1255 : std::string url2 =
1256 48 : CPLURLAddKVP(url.c_str(), "version", version.c_str());
1257 48 : if (parameters != "")
1258 : {
1259 48 : url2 += "&" + parameters;
1260 : }
1261 48 : WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
1262 48 : if (!global)
1263 : {
1264 0 : return nullptr;
1265 : }
1266 48 : if (coverage == "")
1267 : {
1268 24 : return global;
1269 : }
1270 24 : if (version == "")
1271 : {
1272 0 : version = global->Version();
1273 : }
1274 24 : service = CreateService(url, version, coverage, parameters);
1275 : /* --------------------------------------------------------------------
1276 : */
1277 : /* The filename for the new service file. */
1278 : /* --------------------------------------------------------------------
1279 : */
1280 24 : filename = "XXXXX";
1281 24 : if (AddEntryToCache(cache, full_url, filename, ".xml") != CE_None)
1282 : {
1283 0 : return nullptr; // error in cache
1284 : }
1285 : // Create basic service metadata
1286 : // copy global metadata (not SUBDATASETS metadata)
1287 48 : std::string global_base = std::string(global->GetDescription());
1288 48 : std::string global_meta = global_base + ".aux.xml";
1289 48 : std::string capabilities = global_base + ".xml";
1290 48 : CPLXMLTreeCloser doc(CPLParseXMLFile(global_meta.c_str()));
1291 24 : CPLXMLNode *metadata = doc.getDocumentElement();
1292 : CPLXMLNode *domain =
1293 24 : SearchChildWithValue(metadata, "domain", "SUBDATASETS");
1294 24 : if (domain != nullptr)
1295 : {
1296 24 : CPLRemoveXMLChild(metadata, domain);
1297 24 : CPLDestroyXMLNode(domain);
1298 : }
1299 : // get metadata for this coverage from the capabilities XML
1300 48 : CPLXMLTreeCloser doc2(CPLParseXMLFile(capabilities.c_str()));
1301 24 : global->ParseCoverageCapabilities(doc2.getDocumentElement(),
1302 24 : coverage, metadata->psChild);
1303 24 : delete global;
1304 24 : std::string metadata_filename = filename + ".aux.xml";
1305 24 : CPLSerializeXMLTreeToFile(metadata, metadata_filename.c_str());
1306 24 : updated = true;
1307 : }
1308 72 : CPLFree(poOpenInfo->pszFilename);
1309 72 : poOpenInfo->pszFilename = CPLStrdup(filename.c_str());
1310 72 : updated = UpdateService(service, poOpenInfo) || updated;
1311 72 : if (updated || !cached)
1312 : {
1313 72 : CPLSerializeXMLTreeToFile(service, filename.c_str());
1314 72 : }
1315 : }
1316 : /* -------------------------------------------------------------------- */
1317 : /* Is this a WCS_GDAL service description file or "in url" */
1318 : /* equivalent? */
1319 : /* -------------------------------------------------------------------- */
1320 1 : else if (poOpenInfo->nHeaderBytes == 0 &&
1321 0 : STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
1322 : "<WCS_GDAL>"))
1323 : {
1324 0 : service = CPLParseXMLString(poOpenInfo->pszFilename);
1325 : }
1326 1 : else if (poOpenInfo->nHeaderBytes >= 10 &&
1327 1 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "<WCS_GDAL>"))
1328 : {
1329 1 : service = CPLParseXMLFile(poOpenInfo->pszFilename);
1330 : }
1331 : /* -------------------------------------------------------------------- */
1332 : /* Is this apparently a subdataset? */
1333 : /* -------------------------------------------------------------------- */
1334 0 : else if (STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
1335 0 : "WCS_SDS:") &&
1336 0 : poOpenInfo->nHeaderBytes == 0)
1337 : {
1338 : int iLast;
1339 :
1340 0 : papszModifiers = CSLTokenizeString2(poOpenInfo->pszFilename + 8, ",",
1341 : CSLT_HONOURSTRINGS);
1342 :
1343 0 : iLast = CSLCount(papszModifiers) - 1;
1344 0 : if (iLast >= 0)
1345 : {
1346 0 : service = CPLParseXMLFile(papszModifiers[iLast]);
1347 0 : CPLFree(papszModifiers[iLast]);
1348 0 : papszModifiers[iLast] = nullptr;
1349 : }
1350 : }
1351 :
1352 : /* -------------------------------------------------------------------- */
1353 : /* Success so far? */
1354 : /* -------------------------------------------------------------------- */
1355 73 : if (service == nullptr)
1356 : {
1357 0 : CSLDestroy(papszModifiers);
1358 0 : return nullptr;
1359 : }
1360 :
1361 : /* -------------------------------------------------------------------- */
1362 : /* Confirm the requested access is supported. */
1363 : /* -------------------------------------------------------------------- */
1364 73 : if (poOpenInfo->eAccess == GA_Update)
1365 : {
1366 0 : CSLDestroy(papszModifiers);
1367 0 : CPLDestroyXMLNode(service);
1368 0 : CPLError(CE_Failure, CPLE_NotSupported,
1369 : "The WCS driver does not support update access to existing"
1370 : " datasets.\n");
1371 0 : return nullptr;
1372 : }
1373 :
1374 : /* -------------------------------------------------------------------- */
1375 : /* Check for required minimum fields. */
1376 : /* -------------------------------------------------------------------- */
1377 146 : if (!CPLGetXMLValue(service, "ServiceURL", nullptr) ||
1378 73 : !CPLGetXMLValue(service, "CoverageName", nullptr))
1379 : {
1380 0 : CSLDestroy(papszModifiers);
1381 0 : CPLError(
1382 : CE_Failure, CPLE_OpenFailed,
1383 : "Missing one or both of ServiceURL and CoverageName elements.\n"
1384 : "See WCS driver documentation for details on service description "
1385 : "file format.");
1386 :
1387 0 : CPLDestroyXMLNode(service);
1388 0 : return nullptr;
1389 : }
1390 :
1391 : /* -------------------------------------------------------------------- */
1392 : /* What version are we working with? */
1393 : /* -------------------------------------------------------------------- */
1394 73 : const char *pszVersion = CPLGetXMLValue(service, "Version", "1.0.0");
1395 :
1396 73 : int nVersion = WCSParseVersion(pszVersion);
1397 :
1398 73 : if (nVersion == 0)
1399 : {
1400 0 : CSLDestroy(papszModifiers);
1401 0 : CPLDestroyXMLNode(service);
1402 0 : return nullptr;
1403 : }
1404 :
1405 : /* -------------------------------------------------------------------- */
1406 : /* Create a corresponding GDALDataset. */
1407 : /* -------------------------------------------------------------------- */
1408 : WCSDataset *poDS;
1409 73 : if (nVersion == 201)
1410 : {
1411 21 : poDS = new WCSDataset201(cache.c_str());
1412 : }
1413 52 : else if (nVersion / 10 == 11)
1414 : {
1415 36 : poDS = new WCSDataset110(nVersion, cache.c_str());
1416 : }
1417 : else
1418 : {
1419 16 : poDS = new WCSDataset100(cache.c_str());
1420 : }
1421 :
1422 73 : poDS->psService = service;
1423 73 : poDS->SetDescription(poOpenInfo->pszFilename);
1424 73 : poDS->papszSDSModifiers = papszModifiers;
1425 : // WCS:URL => basic metadata was already made
1426 : // Metadata is needed in ExtractGridInfo
1427 73 : poDS->TryLoadXML();
1428 :
1429 : /* -------------------------------------------------------------------- */
1430 : /* Capture HTTP parameters. */
1431 : /* -------------------------------------------------------------------- */
1432 : const char *pszParam;
1433 :
1434 73 : poDS->papszHttpOptions =
1435 73 : CSLSetNameValue(poDS->papszHttpOptions, "TIMEOUT",
1436 : CPLGetXMLValue(service, "Timeout", "30"));
1437 :
1438 73 : pszParam = CPLGetXMLValue(service, "HTTPAUTH", nullptr);
1439 73 : if (pszParam)
1440 0 : poDS->papszHttpOptions =
1441 0 : CSLSetNameValue(poDS->papszHttpOptions, "HTTPAUTH", pszParam);
1442 :
1443 73 : pszParam = CPLGetXMLValue(service, "USERPWD", nullptr);
1444 73 : if (pszParam)
1445 0 : poDS->papszHttpOptions =
1446 0 : CSLSetNameValue(poDS->papszHttpOptions, "USERPWD", pszParam);
1447 :
1448 : /* -------------------------------------------------------------------- */
1449 : /* If we don't have the DescribeCoverage result for this */
1450 : /* coverage, fetch it now. */
1451 : /* -------------------------------------------------------------------- */
1452 136 : if (CPLGetXMLNode(service, "CoverageOffering") == nullptr &&
1453 63 : CPLGetXMLNode(service, "CoverageDescription") == nullptr)
1454 : {
1455 25 : if (!poDS->DescribeCoverage())
1456 : {
1457 1 : delete poDS;
1458 1 : return nullptr;
1459 : }
1460 : }
1461 :
1462 : /* -------------------------------------------------------------------- */
1463 : /* Extract coordinate system, grid size, and geotransform from */
1464 : /* the coverage description and/or service description */
1465 : /* information. */
1466 : /* -------------------------------------------------------------------- */
1467 72 : if (!poDS->ExtractGridInfo())
1468 : {
1469 0 : delete poDS;
1470 0 : return nullptr;
1471 : }
1472 :
1473 : /* -------------------------------------------------------------------- */
1474 : /* Leave now or there may be a GetCoverage call. */
1475 : /* */
1476 : /* -------------------------------------------------------------------- */
1477 72 : int nBandCount = -1;
1478 144 : std::string sBandCount = CPLGetXMLValue(service, "BandCount", "");
1479 72 : if (sBandCount != "")
1480 : {
1481 61 : nBandCount = atoi(sBandCount.c_str());
1482 : }
1483 72 : if (CPLFetchBool(poOpenInfo->papszOpenOptions, "SKIP_GETCOVERAGE", false) ||
1484 : nBandCount == 0)
1485 : {
1486 0 : return poDS;
1487 : }
1488 :
1489 : /* -------------------------------------------------------------------- */
1490 : /* Extract band count and type from a sample. */
1491 : /* -------------------------------------------------------------------- */
1492 72 : if (!poDS->EstablishRasterDetails()) // todo: do this only if missing info
1493 : {
1494 0 : delete poDS;
1495 0 : return nullptr;
1496 : }
1497 :
1498 : /* -------------------------------------------------------------------- */
1499 : /* It is ok to not have bands. The user just needs to supply */
1500 : /* more information. */
1501 : /* -------------------------------------------------------------------- */
1502 72 : nBandCount = atoi(CPLGetXMLValue(service, "BandCount", "0"));
1503 72 : if (nBandCount == 0)
1504 : {
1505 0 : return poDS;
1506 : }
1507 :
1508 : /* -------------------------------------------------------------------- */
1509 : /* Create band information objects. */
1510 : /* -------------------------------------------------------------------- */
1511 : int iBand;
1512 :
1513 72 : if (!GDALCheckBandCount(nBandCount, FALSE))
1514 : {
1515 0 : delete poDS;
1516 0 : return nullptr;
1517 : }
1518 :
1519 207 : for (iBand = 0; iBand < nBandCount; iBand++)
1520 : {
1521 135 : WCSRasterBand *band = new WCSRasterBand(poDS, iBand + 1, -1);
1522 : // copy band specific metadata to the band
1523 135 : char **md_from = poDS->GetMetadata("");
1524 135 : char **md_to = nullptr;
1525 135 : if (md_from)
1526 : {
1527 270 : std::string our_key = CPLString().Printf("FIELD_%d_", iBand + 1);
1528 3192 : for (char **from = md_from; *from != nullptr; ++from)
1529 : {
1530 6114 : std::vector<std::string> kv = Split(*from, "=");
1531 6114 : if (kv.size() > 1 &&
1532 3057 : STARTS_WITH(kv[0].c_str(), our_key.c_str()))
1533 : {
1534 174 : std::string key = kv[0];
1535 87 : std::string value = kv[1];
1536 87 : key.erase(0, our_key.length());
1537 87 : md_to = CSLSetNameValue(md_to, key.c_str(), value.c_str());
1538 : }
1539 : }
1540 : }
1541 135 : band->SetMetadata(md_to, "");
1542 135 : CSLDestroy(md_to);
1543 135 : poDS->SetBand(iBand + 1, band);
1544 : }
1545 :
1546 : /* -------------------------------------------------------------------- */
1547 : /* Set time metadata on the dataset if we are selecting a */
1548 : /* temporal slice. */
1549 : /* -------------------------------------------------------------------- */
1550 72 : std::string osTime = CSLFetchNameValueDef(poDS->papszSDSModifiers, "time",
1551 72 : poDS->osDefaultTime.c_str());
1552 :
1553 72 : if (osTime != "")
1554 0 : poDS->GDALMajorObject::SetMetadataItem("TIME_POSITION", osTime.c_str());
1555 :
1556 : /* -------------------------------------------------------------------- */
1557 : /* Do we have a band identifier to select only a subset of bands? */
1558 : /* -------------------------------------------------------------------- */
1559 72 : poDS->osBandIdentifier = CPLGetXMLValue(service, "BandIdentifier", "");
1560 :
1561 : /* -------------------------------------------------------------------- */
1562 : /* Do we have time based subdatasets? If so, record them in */
1563 : /* metadata. Note we don't do subdatasets if this is a */
1564 : /* subdataset or if this is an all-in-memory service. */
1565 : /* -------------------------------------------------------------------- */
1566 216 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WCS_SDS:") &&
1567 144 : !STARTS_WITH_CI(poOpenInfo->pszFilename, "<WCS_GDAL>") &&
1568 72 : !poDS->aosTimePositions.empty())
1569 : {
1570 0 : char **papszSubdatasets = nullptr;
1571 : int iTime;
1572 :
1573 0 : for (iTime = 0; iTime < (int)poDS->aosTimePositions.size(); iTime++)
1574 : {
1575 0 : std::string osName;
1576 0 : std::string osValue;
1577 :
1578 0 : osName = CPLString().Printf("SUBDATASET_%d_NAME", iTime + 1);
1579 0 : osValue = CPLString().Printf("WCS_SDS:time=\"%s\",%s",
1580 0 : poDS->aosTimePositions[iTime].c_str(),
1581 0 : poOpenInfo->pszFilename);
1582 0 : papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
1583 : osValue.c_str());
1584 :
1585 : std::string osCoverage =
1586 0 : CPLGetXMLValue(poDS->psService, "CoverageName", "");
1587 :
1588 0 : osName = CPLString().Printf("SUBDATASET_%d_DESC", iTime + 1);
1589 : osValue =
1590 0 : CPLString().Printf("Coverage %s at time %s", osCoverage.c_str(),
1591 0 : poDS->aosTimePositions[iTime].c_str());
1592 0 : papszSubdatasets = CSLSetNameValue(papszSubdatasets, osName.c_str(),
1593 : osValue.c_str());
1594 : }
1595 :
1596 0 : poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS");
1597 :
1598 0 : CSLDestroy(papszSubdatasets);
1599 : }
1600 :
1601 : /* -------------------------------------------------------------------- */
1602 : /* Initialize any PAM information. */
1603 : /* -------------------------------------------------------------------- */
1604 72 : poDS->TryLoadXML();
1605 72 : return poDS;
1606 : }
1607 :
1608 : /************************************************************************/
1609 : /* GetGeoTransform() */
1610 : /************************************************************************/
1611 :
1612 72 : CPLErr WCSDataset::GetGeoTransform(double *padfTransform)
1613 :
1614 : {
1615 72 : memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
1616 72 : return CE_None;
1617 : }
1618 :
1619 : /************************************************************************/
1620 : /* GetSpatialRef() */
1621 : /************************************************************************/
1622 :
1623 48 : const OGRSpatialReference *WCSDataset::GetSpatialRef() const
1624 :
1625 : {
1626 48 : const auto poSRS = GDALPamDataset::GetSpatialRef();
1627 48 : if (poSRS)
1628 0 : return poSRS;
1629 :
1630 48 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1631 : }
1632 :
1633 : /************************************************************************/
1634 : /* GetFileList() */
1635 : /************************************************************************/
1636 :
1637 0 : char **WCSDataset::GetFileList()
1638 :
1639 : {
1640 0 : char **papszFileList = GDALPamDataset::GetFileList();
1641 :
1642 : /* -------------------------------------------------------------------- */
1643 : /* ESRI also wishes to include service urls in the file list */
1644 : /* though this is not currently part of the general definition */
1645 : /* of GetFileList() for GDAL. */
1646 : /* -------------------------------------------------------------------- */
1647 : #ifdef ESRI_BUILD
1648 : std::string file;
1649 : file.Printf("%s%s", CPLGetXMLValue(psService, "ServiceURL", ""),
1650 : CPLGetXMLValue(psService, "CoverageName", ""));
1651 : papszFileList = CSLAddString(papszFileList, file.c_str());
1652 : #endif /* def ESRI_BUILD */
1653 :
1654 0 : return papszFileList;
1655 : }
1656 :
1657 : /************************************************************************/
1658 : /* GetMetadataDomainList() */
1659 : /************************************************************************/
1660 :
1661 0 : char **WCSDataset::GetMetadataDomainList()
1662 : {
1663 0 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
1664 0 : TRUE, "xml:CoverageOffering", nullptr);
1665 : }
1666 :
1667 : /************************************************************************/
1668 : /* GetMetadata() */
1669 : /************************************************************************/
1670 :
1671 348 : char **WCSDataset::GetMetadata(const char *pszDomain)
1672 :
1673 : {
1674 348 : if (pszDomain == nullptr || !EQUAL(pszDomain, "xml:CoverageOffering"))
1675 348 : return GDALPamDataset::GetMetadata(pszDomain);
1676 :
1677 0 : CPLXMLNode *psNode = CPLGetXMLNode(psService, "CoverageOffering");
1678 :
1679 0 : if (psNode == nullptr)
1680 0 : psNode = CPLGetXMLNode(psService, "CoverageDescription");
1681 :
1682 0 : if (psNode == nullptr)
1683 0 : return nullptr;
1684 :
1685 0 : if (apszCoverageOfferingMD[0] == nullptr)
1686 : {
1687 0 : CPLXMLNode *psNext = psNode->psNext;
1688 0 : psNode->psNext = nullptr;
1689 :
1690 0 : apszCoverageOfferingMD[0] = CPLSerializeXMLTree(psNode);
1691 :
1692 0 : psNode->psNext = psNext;
1693 : }
1694 :
1695 0 : return apszCoverageOfferingMD;
1696 : }
1697 :
1698 : /************************************************************************/
1699 : /* GDALRegister_WCS() */
1700 : /************************************************************************/
1701 :
1702 1595 : void GDALRegister_WCS()
1703 :
1704 : {
1705 1595 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1706 302 : return;
1707 :
1708 1293 : GDALDriver *poDriver = new GDALDriver();
1709 1293 : WCSDriverSetCommonMetadata(poDriver);
1710 :
1711 1293 : poDriver->pfnOpen = WCSDataset::Open;
1712 :
1713 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
1714 : }
|