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