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