gdal/frmts/wcs/wcsdataset.cpp

1769 строки
66 KiB
C++

/******************************************************************************
*
* Project: WCS Client Driver
* Purpose: Implementation of Dataset and RasterBand classes for WCS.
* Author: Frank Warmerdam, warmerdam@pobox.com
*
******************************************************************************
* Copyright (c) 2006, Frank Warmerdam
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
****************************************************************************/
#include "cpl_string.h"
#include "cpl_minixml.h"
#include "cpl_http.h"
#include "gmlutils.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "ogr_spatialref.h"
#include "gmlcoverage.h"
#include <algorithm>
#include "wcsdataset.h"
#include "wcsrasterband.h"
#include "wcsutils.h"
using namespace WCSUtils;
/************************************************************************/
/* WCSDataset() */
/************************************************************************/
WCSDataset::WCSDataset(int version, const char *cache_dir)
: m_cache_dir(cache_dir), bServiceDirty(false), psService(nullptr),
papszSDSModifiers(nullptr), m_Version(version), native_crs(true),
axis_order_swap(false), pabySavedDataBuffer(nullptr),
papszHttpOptions(nullptr), nMaxCols(-1), nMaxRows(-1)
{
m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
adfGeoTransform[0] = 0.0;
adfGeoTransform[1] = 1.0;
adfGeoTransform[2] = 0.0;
adfGeoTransform[3] = 0.0;
adfGeoTransform[4] = 0.0;
adfGeoTransform[5] = 1.0;
apszCoverageOfferingMD[0] = nullptr;
apszCoverageOfferingMD[1] = nullptr;
}
/************************************************************************/
/* ~WCSDataset() */
/************************************************************************/
WCSDataset::~WCSDataset()
{
// perhaps this should be moved into a FlushCache(bool bAtClosing) method.
if (bServiceDirty && !STARTS_WITH_CI(GetDescription(), "<WCS_GDAL>"))
{
CPLSerializeXMLTreeToFile(psService, GetDescription());
bServiceDirty = false;
}
CPLDestroyXMLNode(psService);
CSLDestroy(papszHttpOptions);
CSLDestroy(papszSDSModifiers);
CPLFree(apszCoverageOfferingMD[0]);
FlushMemoryResult();
}
/************************************************************************/
/* SetCRS() */
/* */
/* Set the name and the WKT of the projection of this dataset. */
/* Based on the projection, sets the axis order flag. */
/* Also set the native flag. */
/************************************************************************/
bool WCSDataset::SetCRS(const CPLString &crs, bool native)
{
osCRS = crs;
char *pszProjection = nullptr;
if (!CRSImpliesAxisOrderSwap(osCRS, axis_order_swap, &pszProjection))
{
return false;
}
m_oSRS.importFromWkt(pszProjection);
CPLFree(pszProjection);
native_crs = native;
return true;
}
/************************************************************************/
/* SetGeometry() */
/* */
/* Set GeoTransform and RasterSize from the coverage envelope, */
/* axis_order, grid size, and grid offsets. */
/************************************************************************/
void WCSDataset::SetGeometry(const std::vector<int> &size,
const std::vector<double> &origin,
const std::vector<std::vector<double>> &offsets)
{
// note that this method is not used by wcsdataset100.cpp
nRasterXSize = size[0];
nRasterYSize = size[1];
adfGeoTransform[0] = origin[0];
adfGeoTransform[1] = offsets[0][0];
adfGeoTransform[2] = offsets[0].size() == 1 ? 0.0 : offsets[0][1];
adfGeoTransform[3] = origin[1];
adfGeoTransform[4] = offsets[1].size() == 1 ? 0.0 : offsets[1][0];
adfGeoTransform[5] = offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
if (!CPLGetXMLBoolean(psService, "OriginAtBoundary"))
{
adfGeoTransform[0] -= adfGeoTransform[1] * 0.5;
adfGeoTransform[0] -= adfGeoTransform[2] * 0.5;
adfGeoTransform[3] -= adfGeoTransform[4] * 0.5;
adfGeoTransform[3] -= adfGeoTransform[5] * 0.5;
}
}
/************************************************************************/
/* TestUseBlockIO() */
/* */
/* Check whether we should use blocked IO (true) or direct io */
/* (FALSE) for a given request configuration and environment. */
/************************************************************************/
int WCSDataset::TestUseBlockIO(CPL_UNUSED int nXOff, CPL_UNUSED int nYOff,
int nXSize, int nYSize, int nBufXSize,
int nBufYSize) const
{
int bUseBlockedIO = bForceCachedIO;
if (nYSize == 1 || nXSize * ((double)nYSize) < 100.0)
bUseBlockedIO = TRUE;
if (nBufYSize == 1 || nBufXSize * ((double)nBufYSize) < 100.0)
bUseBlockedIO = TRUE;
if (bUseBlockedIO &&
CPLTestBool(CPLGetConfigOption("GDAL_ONE_BIG_READ", "NO")))
bUseBlockedIO = FALSE;
return bUseBlockedIO;
}
/************************************************************************/
/* IRasterIO() */
/************************************************************************/
CPLErr WCSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
int nXSize, int nYSize, void *pData, int nBufXSize,
int nBufYSize, GDALDataType eBufType,
int nBandCount, int *panBandMap,
GSpacing nPixelSpace, GSpacing nLineSpace,
GSpacing nBandSpace,
GDALRasterIOExtraArg *psExtraArg)
{
if ((nMaxCols > 0 && nMaxCols < nBufXSize) ||
(nMaxRows > 0 && nMaxRows < nBufYSize))
return CE_Failure;
/* -------------------------------------------------------------------- */
/* We need various criteria to skip out to block based methods. */
/* -------------------------------------------------------------------- */
if (TestUseBlockIO(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize))
return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
pData, nBufXSize, nBufYSize, eBufType,
nBandCount, panBandMap, nPixelSpace,
nLineSpace, nBandSpace, psExtraArg);
else
return DirectRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
nBufXSize, nBufYSize, eBufType, nBandCount,
panBandMap, nPixelSpace, nLineSpace, nBandSpace,
psExtraArg);
}
/************************************************************************/
/* DirectRasterIO() */
/* */
/* Make exactly one request to the server for this data. */
/************************************************************************/
CPLErr WCSDataset::DirectRasterIO(CPL_UNUSED GDALRWFlag eRWFlag, int nXOff,
int nYOff, int nXSize, int nYSize,
void *pData, int nBufXSize, int nBufYSize,
GDALDataType eBufType, int nBandCount,
int *panBandMap, GSpacing nPixelSpace,
GSpacing nLineSpace, GSpacing nBandSpace,
GDALRasterIOExtraArg *psExtraArg)
{
CPLDebug("WCS", "DirectRasterIO(%d,%d,%d,%d) -> (%d,%d) (%d bands)\n",
nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize, nBandCount);
/* -------------------------------------------------------------------- */
/* Get the coverage. */
/* -------------------------------------------------------------------- */
// if INTERLEAVE is set to PIXEL, then we'll request all bands.
// That is necessary at least with MapServer, which seems to often
// return all bands instead of requested.
// todo: in 2.0.1 the band list in this dataset may be user-defined
int band_count = nBandCount;
if (EQUAL(CPLGetXMLValue(psService, "INTERLEAVE", ""), "PIXEL"))
{
band_count = 0;
}
CPLHTTPResult *psResult = nullptr;
CPLErr eErr =
GetCoverage(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
band_count, panBandMap, psExtraArg, &psResult);
if (eErr != CE_None)
return eErr;
/* -------------------------------------------------------------------- */
/* Try and open result as a dataset. */
/* -------------------------------------------------------------------- */
GDALDataset *poTileDS = GDALOpenResult(psResult);
if (poTileDS == nullptr)
return CE_Failure;
/* -------------------------------------------------------------------- */
/* Verify configuration. */
/* -------------------------------------------------------------------- */
if (poTileDS->GetRasterXSize() != nBufXSize ||
poTileDS->GetRasterYSize() != nBufYSize)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Returned tile does not match expected configuration.\n"
"Got %dx%d instead of %dx%d.",
poTileDS->GetRasterXSize(), poTileDS->GetRasterYSize(),
nBufXSize, nBufYSize);
delete poTileDS;
return CE_Failure;
}
if (band_count != 0 && ((!osBandIdentifier.empty() &&
poTileDS->GetRasterCount() != nBandCount) ||
(osBandIdentifier.empty() &&
poTileDS->GetRasterCount() != GetRasterCount())))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Returned tile does not match expected band count.");
delete poTileDS;
return CE_Failure;
}
/* -------------------------------------------------------------------- */
/* Pull requested bands from the downloaded dataset. */
/* -------------------------------------------------------------------- */
eErr = CE_None;
for (int iBand = 0; iBand < nBandCount && eErr == CE_None; iBand++)
{
GDALRasterBand *poTileBand = nullptr;
if (!osBandIdentifier.empty())
poTileBand = poTileDS->GetRasterBand(iBand + 1);
else
poTileBand = poTileDS->GetRasterBand(panBandMap[iBand]);
eErr = poTileBand->RasterIO(GF_Read, 0, 0, nBufXSize, nBufYSize,
((GByte *)pData) + iBand * nBandSpace,
nBufXSize, nBufYSize, eBufType, nPixelSpace,
nLineSpace, nullptr);
}
/* -------------------------------------------------------------------- */
/* Cleanup */
/* -------------------------------------------------------------------- */
delete poTileDS;
FlushMemoryResult();
return eErr;
}
static bool ProcessError(CPLHTTPResult *psResult);
/************************************************************************/
/* GetCoverage() */
/* */
/* Issue the appropriate version of request for a given window, */
/* buffer size and band list. */
/************************************************************************/
CPLErr WCSDataset::GetCoverage(int nXOff, int nYOff, int nXSize, int nYSize,
int nBufXSize, int nBufYSize, int nBandCount,
int *panBandList,
GDALRasterIOExtraArg *psExtraArg,
CPLHTTPResult **ppsResult)
{
/* -------------------------------------------------------------------- */
/* Figure out the georeferenced extents. */
/* -------------------------------------------------------------------- */
std::vector<double> extent =
GetExtent(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
/* -------------------------------------------------------------------- */
/* Build band list if we have the band identifier. */
/* -------------------------------------------------------------------- */
CPLString osBandList;
if (!osBandIdentifier.empty() && nBandCount > 0 && panBandList != nullptr)
{
int iBand;
for (iBand = 0; iBand < nBandCount; iBand++)
{
if (iBand > 0)
osBandList += ",";
osBandList += CPLString().Printf("%d", panBandList[iBand]);
}
}
/* -------------------------------------------------------------------- */
/* Construct a KVP GetCoverage request. */
/* -------------------------------------------------------------------- */
bool scaled = nBufXSize != nXSize || nBufYSize != nYSize;
CPLString osRequest =
GetCoverageRequest(scaled, nBufXSize, nBufYSize, extent, osBandList);
// for the test setup we need the actual URLs this driver generates
// fprintf(stdout, "URL=%s\n", osRequest.c_str());
/* -------------------------------------------------------------------- */
/* Fetch the result. */
/* -------------------------------------------------------------------- */
CPLErrorReset();
if (psExtraArg && psExtraArg->pfnProgress != nullptr)
{
*ppsResult =
CPLHTTPFetchEx(osRequest, papszHttpOptions, psExtraArg->pfnProgress,
psExtraArg->pProgressData, nullptr, nullptr);
}
else
{
*ppsResult = CPLHTTPFetch(osRequest, papszHttpOptions);
}
if (ProcessError(*ppsResult))
return CE_Failure;
else
return CE_None;
}
/************************************************************************/
/* DescribeCoverage() */
/* */
/* Fetch the DescribeCoverage result and attach it to the */
/* service description. */
/************************************************************************/
int WCSDataset::DescribeCoverage()
{
CPLString osRequest;
/* -------------------------------------------------------------------- */
/* Fetch coverage description for this coverage. */
/* -------------------------------------------------------------------- */
CPLXMLNode *psDC = nullptr;
// if it is in cache, get it from there
CPLString dc_filename =
this->GetDescription(); // the WCS_GDAL file (<basename>.xml)
dc_filename.erase(dc_filename.length() - 4, 4);
dc_filename += ".DC.xml";
if (FileIsReadable(dc_filename))
{
psDC = CPLParseXMLFile(dc_filename);
}
if (!psDC)
{
osRequest = DescribeCoverageRequest();
CPLErrorReset();
CPLHTTPResult *psResult = CPLHTTPFetch(osRequest, papszHttpOptions);
if (ProcessError(psResult))
{
return FALSE;
}
/* --------------------------------------------------------------------
*/
/* Parse result. */
/* --------------------------------------------------------------------
*/
psDC = CPLParseXMLString((const char *)psResult->pabyData);
CPLHTTPDestroyResult(psResult);
if (psDC == nullptr)
{
return FALSE;
}
// if we have cache, put it there
if (dc_filename != "")
{
CPLSerializeXMLTreeToFile(psDC, dc_filename);
}
}
CPLStripXMLNamespace(psDC, nullptr, TRUE);
/* -------------------------------------------------------------------- */
/* Did we get a CoverageOffering? */
/* -------------------------------------------------------------------- */
CPLXMLNode *psCO = CoverageOffering(psDC);
if (!psCO)
{
CPLDestroyXMLNode(psDC);
CPLError(CE_Failure, CPLE_AppDefined,
"Failed to fetch a <CoverageOffering> back %s.",
osRequest.c_str());
return FALSE;
}
/* -------------------------------------------------------------------- */
/* Duplicate the coverage offering, and insert into */
/* -------------------------------------------------------------------- */
CPLXMLNode *psNext = psCO->psNext;
psCO->psNext = nullptr;
CPLAddXMLChild(psService, CPLCloneXMLTree(psCO));
bServiceDirty = true;
psCO->psNext = psNext;
CPLDestroyXMLNode(psDC);
return TRUE;
}
/************************************************************************/
/* ProcessError() */
/* */
/* Process an HTTP error, reporting it via CPL, and destroying */
/* the HTTP result object. Returns TRUE if there was an error, */
/* or FALSE if the result seems ok. */
/************************************************************************/
static bool ProcessError(CPLHTTPResult *psResult)
{
/* -------------------------------------------------------------------- */
/* There isn't much we can do in this case. Hopefully an error */
/* was already issued by CPLHTTPFetch() */
/* -------------------------------------------------------------------- */
if (psResult == nullptr || psResult->nDataLen == 0)
{
CPLHTTPDestroyResult(psResult);
return TRUE;
}
/* -------------------------------------------------------------------- */
/* If we got an html document, we presume it is an error */
/* message and report it verbatim up to a certain size limit. */
/* -------------------------------------------------------------------- */
if (psResult->pszContentType != nullptr &&
strstr(psResult->pszContentType, "html") != nullptr)
{
CPLString osErrorMsg = (char *)psResult->pabyData;
if (osErrorMsg.size() > 2048)
osErrorMsg.resize(2048);
CPLError(CE_Failure, CPLE_AppDefined, "Malformed Result:\n%s",
osErrorMsg.c_str());
CPLHTTPDestroyResult(psResult);
return TRUE;
}
/* -------------------------------------------------------------------- */
/* Does this look like a service exception? We would like to */
/* check based on the Content-type, but this seems quite */
/* undependable, even from MapServer! */
/* -------------------------------------------------------------------- */
if (strstr((const char *)psResult->pabyData, "ExceptionReport"))
{
CPLXMLNode *psTree =
CPLParseXMLString((const char *)psResult->pabyData);
CPLStripXMLNamespace(psTree, nullptr, TRUE);
CPLString msg = CPLGetXMLValue(
psTree, "=ServiceExceptionReport.ServiceException", "");
if (msg == "")
{
msg = CPLGetXMLValue(
psTree, "=ExceptionReport.Exception.exceptionCode", "");
if (msg != "")
{
msg += ": ";
}
msg += CPLGetXMLValue(
psTree, "=ExceptionReport.Exception.ExceptionText", "");
}
if (msg != "")
CPLError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
else
CPLError(CE_Failure, CPLE_AppDefined,
"Corrupt Service Exception:\n%s",
(const char *)psResult->pabyData);
CPLDestroyXMLNode(psTree);
CPLHTTPDestroyResult(psResult);
return TRUE;
}
/* -------------------------------------------------------------------- */
/* Hopefully the error already issued by CPLHTTPFetch() is */
/* sufficient. */
/* -------------------------------------------------------------------- */
if (CPLGetLastErrorNo() != 0)
{
CPLHTTPDestroyResult(psResult);
return TRUE;
}
return false;
}
/************************************************************************/
/* EstablishRasterDetails() */
/* */
/* Do a "test" coverage query to work out the number of bands, */
/* and pixel data type of the remote coverage. */
/************************************************************************/
int WCSDataset::EstablishRasterDetails()
{
CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageOffering");
const char *pszCols =
CPLGetXMLValue(psCO, "dimensionLimit.columns", nullptr);
const char *pszRows = CPLGetXMLValue(psCO, "dimensionLimit.rows", nullptr);
if (pszCols && pszRows)
{
nMaxCols = atoi(pszCols);
nMaxRows = atoi(pszRows);
SetMetadataItem("MAXNCOLS", pszCols, "IMAGE_STRUCTURE");
SetMetadataItem("MAXNROWS", pszRows, "IMAGE_STRUCTURE");
}
/* -------------------------------------------------------------------- */
/* Do we already have bandcount and pixel type settings? */
/* -------------------------------------------------------------------- */
if (CPLGetXMLValue(psService, "BandCount", nullptr) != nullptr &&
CPLGetXMLValue(psService, "BandType", nullptr) != nullptr)
return TRUE;
/* -------------------------------------------------------------------- */
/* Fetch a small block of raster data. */
/* -------------------------------------------------------------------- */
CPLHTTPResult *psResult = nullptr;
CPLErr eErr;
eErr = GetCoverage(0, 0, 2, 2, 2, 2, 0, nullptr, nullptr, &psResult);
if (eErr != CE_None)
return false;
/* -------------------------------------------------------------------- */
/* Try and open result as a dataset. */
/* -------------------------------------------------------------------- */
GDALDataset *poDS = GDALOpenResult(psResult);
if (poDS == nullptr)
return false;
const auto poSRS = poDS->GetSpatialRef();
m_oSRS.Clear();
if (poSRS)
m_oSRS = *poSRS;
/* -------------------------------------------------------------------- */
/* Record details. */
/* -------------------------------------------------------------------- */
if (poDS->GetRasterCount() < 1)
{
delete poDS;
return false;
}
if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
CPLCreateXMLElementAndValue(
psService, "BandCount",
CPLString().Printf("%d", poDS->GetRasterCount()));
CPLCreateXMLElementAndValue(
psService, "BandType",
GDALGetDataTypeName(poDS->GetRasterBand(1)->GetRasterDataType()));
bServiceDirty = true;
/* -------------------------------------------------------------------- */
/* Cleanup */
/* -------------------------------------------------------------------- */
delete poDS;
FlushMemoryResult();
return TRUE;
}
/************************************************************************/
/* FlushMemoryResult() */
/* */
/* This actually either cleans up the in memory /vsimem/ */
/* temporary file, or the on disk temporary file. */
/************************************************************************/
void WCSDataset::FlushMemoryResult()
{
if (!osResultFilename.empty())
{
VSIUnlink(osResultFilename);
osResultFilename = "";
}
if (pabySavedDataBuffer)
{
CPLFree(pabySavedDataBuffer);
pabySavedDataBuffer = nullptr;
}
}
/************************************************************************/
/* GDALOpenResult() */
/* */
/* Open a CPLHTTPResult as a GDALDataset (if possible). First */
/* attempt is to open handle it "in memory". Eventually we */
/* will add support for handling it on file if necessary. */
/* */
/* This method will free CPLHTTPResult, the caller should not */
/* access it after the call. */
/************************************************************************/
GDALDataset *WCSDataset::GDALOpenResult(CPLHTTPResult *psResult)
{
FlushMemoryResult();
CPLDebug("WCS", "GDALOpenResult() on content-type: %s",
psResult->pszContentType);
/* -------------------------------------------------------------------- */
/* If this is multipart/related content type, we should search */
/* for the second part. */
/* -------------------------------------------------------------------- */
GByte *pabyData = psResult->pabyData;
int nDataLen = psResult->nDataLen;
if (psResult->pszContentType &&
strstr(psResult->pszContentType, "multipart") &&
CPLHTTPParseMultipartMime(psResult))
{
if (psResult->nMimePartCount > 1)
{
pabyData = psResult->pasMimePart[1].pabyData;
nDataLen = psResult->pasMimePart[1].nDataLen;
const char *pszContentTransferEncoding =
CSLFetchNameValue(psResult->pasMimePart[1].papszHeaders,
"Content-Transfer-Encoding");
if (pszContentTransferEncoding &&
EQUAL(pszContentTransferEncoding, "base64"))
{
nDataLen = CPLBase64DecodeInPlace(pabyData);
}
}
}
/* -------------------------------------------------------------------- */
/* Create a memory file from the result. */
/* -------------------------------------------------------------------- */
#ifdef DEBUG_WCS
// this facility is used by requests.pl to generate files for the test
// server
CPLString xfn = CPLGetXMLValue(psService, "filename", "");
if (xfn != "")
{
VSILFILE *fpTemp = VSIFOpenL(xfn, "wb");
VSIFWriteL(pabyData, nDataLen, 1, fpTemp);
VSIFCloseL(fpTemp);
}
#endif
// Eventually we should be looking at mime info and stuff to figure
// out an optimal filename, but for now we just use a fixed one.
osResultFilename.Printf("/vsimem/wcs/%p/wcsresult.dat", this);
VSILFILE *fp =
VSIFileFromMemBuffer(osResultFilename, pabyData, nDataLen, FALSE);
if (fp == nullptr)
{
CPLHTTPDestroyResult(psResult);
return nullptr;
}
VSIFCloseL(fp);
/* -------------------------------------------------------------------- */
/* Try opening this result as a gdaldataset. */
/* -------------------------------------------------------------------- */
GDALDataset *poDS = (GDALDataset *)GDALOpen(osResultFilename, GA_ReadOnly);
/* -------------------------------------------------------------------- */
/* If opening it in memory didn't work, perhaps we need to */
/* write to a temp file on disk? */
/* -------------------------------------------------------------------- */
if (poDS == nullptr)
{
CPLString osTempFilename;
VSILFILE *fpTemp;
osTempFilename.Printf("/tmp/%p_wcs.dat", this);
fpTemp = VSIFOpenL(osTempFilename, "wb");
if (fpTemp == nullptr)
{
CPLError(CE_Failure, CPLE_OpenFailed,
"Failed to create temporary file:%s",
osTempFilename.c_str());
}
else
{
if (VSIFWriteL(pabyData, nDataLen, 1, fpTemp) != 1)
{
CPLError(CE_Failure, CPLE_OpenFailed,
"Failed to write temporary file:%s",
osTempFilename.c_str());
VSIFCloseL(fpTemp);
VSIUnlink(osTempFilename);
}
else
{
VSIFCloseL(fpTemp);
VSIUnlink(osResultFilename);
osResultFilename = osTempFilename;
poDS = (GDALDataset *)GDALOpen(osResultFilename, GA_ReadOnly);
}
}
}
/* -------------------------------------------------------------------- */
/* Steal the memory buffer from HTTP result. */
/* -------------------------------------------------------------------- */
pabySavedDataBuffer = psResult->pabyData;
psResult->pabyData = nullptr;
if (poDS == nullptr)
FlushMemoryResult();
CPLHTTPDestroyResult(psResult);
return poDS;
}
/************************************************************************/
/* Identify() */
/************************************************************************/
int WCSDataset::Identify(GDALOpenInfo *poOpenInfo)
{
/* -------------------------------------------------------------------- */
/* Filename is WCS:URL */
/* */
/* -------------------------------------------------------------------- */
if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "WCS:"))
return TRUE;
/* -------------------------------------------------------------------- */
/* Is this a WCS_GDAL service description file or "in url" */
/* equivalent? */
/* -------------------------------------------------------------------- */
if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "<WCS_GDAL>"))
return TRUE;
else if (poOpenInfo->nHeaderBytes >= 10 &&
STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "<WCS_GDAL>"))
return TRUE;
/* -------------------------------------------------------------------- */
/* Is this apparently a WCS subdataset reference? */
/* -------------------------------------------------------------------- */
else if (STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
"WCS_SDS:") &&
poOpenInfo->nHeaderBytes == 0)
return TRUE;
else
return FALSE;
}
/************************************************************************/
/* WCSParseVersion() */
/************************************************************************/
static int WCSParseVersion(const char *version)
{
if (EQUAL(version, "2.0.1"))
return 201;
if (EQUAL(version, "1.1.2"))
return 112;
if (EQUAL(version, "1.1.1"))
return 111;
if (EQUAL(version, "1.1.0"))
return 110;
if (EQUAL(version, "1.0.0"))
return 100;
return 0;
}
/************************************************************************/
/* Version() */
/************************************************************************/
const char *WCSDataset::Version() const
{
if (this->m_Version == 201)
return "2.0.1";
if (this->m_Version == 112)
return "1.1.2";
if (this->m_Version == 111)
return "1.1.1";
if (this->m_Version == 110)
return "1.1.0";
if (this->m_Version == 100)
return "1.0.0";
return "";
}
/************************************************************************/
/* FetchCapabilities() */
/************************************************************************/
#define WCS_HTTP_OPTIONS "TIMEOUT", "USERPWD", "HTTPAUTH"
static bool FetchCapabilities(GDALOpenInfo *poOpenInfo, CPLString url,
CPLString path)
{
url = CPLURLAddKVP(url, "SERVICE", "WCS");
url = CPLURLAddKVP(url, "REQUEST", "GetCapabilities");
CPLString extra = CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
"GetCapabilitiesExtra", "");
if (extra != "")
{
std::vector<CPLString> pairs = Split(extra, "&");
for (unsigned int i = 0; i < pairs.size(); ++i)
{
std::vector<CPLString> pair = Split(pairs[i], "=");
url = CPLURLAddKVP(url, pair[0], pair[1]);
}
}
char **options = nullptr;
const char *keys[] = {WCS_HTTP_OPTIONS};
for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
{
CPLString value =
CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, keys[i], "");
if (value != "")
{
options = CSLSetNameValue(options, keys[i], value);
}
}
CPLHTTPResult *psResult = CPLHTTPFetch(url.c_str(), options);
CSLDestroy(options);
if (ProcessError(psResult))
{
return false;
}
CPLXMLTreeCloser doc(CPLParseXMLString((const char *)psResult->pabyData));
CPLHTTPDestroyResult(psResult);
if (doc.get() == nullptr)
{
return false;
}
CPLXMLNode *capabilities = doc.get();
CPLSerializeXMLTreeToFile(capabilities, path);
return true;
}
/************************************************************************/
/* CreateFromCapabilities() */
/************************************************************************/
WCSDataset *WCSDataset::CreateFromCapabilities(CPLString cache, CPLString path,
CPLString url)
{
CPLXMLTreeCloser doc(CPLParseXMLFile(path));
if (doc.get() == nullptr)
{
return nullptr;
}
CPLXMLNode *capabilities = doc.getDocumentElement();
if (capabilities == nullptr)
{
return nullptr;
}
// get version, this version will overwrite the user's request
int version_from_server =
WCSParseVersion(CPLGetXMLValue(capabilities, "version", ""));
if (version_from_server == 0)
{
// broken server, assume 1.0.0
version_from_server = 100;
}
WCSDataset *poDS;
if (version_from_server == 201)
{
poDS = new WCSDataset201(cache);
}
else if (version_from_server / 10 == 11)
{
poDS = new WCSDataset110(version_from_server, cache);
}
else
{
poDS = new WCSDataset100(cache);
}
if (poDS->ParseCapabilities(capabilities, url) != CE_None)
{
delete poDS;
return nullptr;
}
poDS->SetDescription(RemoveExt(path));
poDS->TrySaveXML();
return poDS;
}
/************************************************************************/
/* CreateFromMetadata() */
/************************************************************************/
WCSDataset *WCSDataset::CreateFromMetadata(const CPLString &cache,
CPLString path)
{
WCSDataset *poDS;
if (FileIsReadable(path))
{
CPLXMLTreeCloser doc(CPLParseXMLFile((path).c_str()));
CPLXMLNode *metadata = doc.get();
if (metadata == nullptr)
{
return nullptr;
}
int version_from_metadata = WCSParseVersion(CPLGetXMLValue(
SearchChildWithValue(SearchChildWithValue(metadata, "domain", ""),
"key", "WCS_GLOBAL#version"),
nullptr, ""));
if (version_from_metadata == 201)
{
poDS = new WCSDataset201(cache);
}
else if (version_from_metadata / 10 == 11)
{
poDS = new WCSDataset110(version_from_metadata, cache);
}
else if (version_from_metadata / 10 == 10)
{
poDS = new WCSDataset100(cache);
}
else
{
CPLError(CE_Failure, CPLE_AppDefined,
"The metadata does not contain version. RECREATE_META?");
return nullptr;
}
path = RemoveExt(RemoveExt(path));
poDS->SetDescription(path);
poDS->TryLoadXML(); // todo: avoid reload
}
else
{
// obviously there was an error
// processing the Capabilities file
// so we show it to the user
GByte *pabyOut = nullptr;
path = RemoveExt(RemoveExt(path)) + ".xml";
if (!VSIIngestFile(nullptr, path, &pabyOut, nullptr, -1))
return nullptr;
CPLString error = reinterpret_cast<char *>(pabyOut);
if (error.size() > 2048)
{
error.resize(2048);
}
CPLError(CE_Failure, CPLE_AppDefined, "Error:\n%s", error.c_str());
CPLFree(pabyOut);
return nullptr;
}
return poDS;
}
/************************************************************************/
/* BootstrapGlobal() */
/************************************************************************/
static WCSDataset *BootstrapGlobal(GDALOpenInfo *poOpenInfo,
const CPLString &cache, const CPLString &url)
{
// do we have the capabilities file
CPLString filename;
bool cached;
if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
{
return nullptr; // error in cache
}
if (!cached)
{
filename = "XXXXX";
if (AddEntryToCache(cache, url, filename, ".xml") != CE_None)
{
return nullptr; // error in cache
}
if (!FetchCapabilities(poOpenInfo, url, filename))
{
DeleteEntryFromCache(cache, "", url);
return nullptr;
}
return WCSDataset::CreateFromCapabilities(cache, filename, url);
}
CPLString metadata = RemoveExt(filename) + ".aux.xml";
bool recreate_meta =
CPLFetchBool(poOpenInfo->papszOpenOptions, "RECREATE_META", false);
if (FileIsReadable(metadata) && !recreate_meta)
{
return WCSDataset::CreateFromMetadata(cache, metadata);
}
// we have capabilities but not meta
return WCSDataset::CreateFromCapabilities(cache, filename, url);
}
/************************************************************************/
/* CreateService() */
/************************************************************************/
static CPLXMLNode *CreateService(const CPLString &base_url,
const CPLString &version,
const CPLString &coverage,
const CPLString &parameters)
{
// construct WCS_GDAL XML into psService
CPLString xml = "<WCS_GDAL>";
xml += "<ServiceURL>" + base_url + "</ServiceURL>";
xml += "<Version>" + version + "</Version>";
xml += "<CoverageName>" + coverage + "</CoverageName>";
xml += "<Parameters>" + parameters + "</Parameters>";
xml += "</WCS_GDAL>";
CPLXMLNode *psService = CPLParseXMLString(xml);
return psService;
}
/************************************************************************/
/* UpdateService() */
/************************************************************************/
#define WCS_SERVICE_OPTIONS \
"PreferredFormat", "NoDataValue", "BlockXSize", "BlockYSize", \
"OverviewCount", "GetCoverageExtra", "DescribeCoverageExtra", \
"Domain", "BandCount", "BandType", "DefaultTime", "CRS"
#define WCS_TWEAK_OPTIONS \
"OriginAtBoundary", "OuterExtents", "BufSizeAdjust", "OffsetsPositive", \
"NrOffsets", "GridCRSOptional", "NoGridAxisSwap", "GridAxisLabelSwap", \
"SubsetAxisSwap", "UseScaleFactor", "INTERLEAVE"
static bool UpdateService(CPLXMLNode *service, GDALOpenInfo *poOpenInfo)
{
bool updated = false;
// descriptions in frmt_wcs.html
const char *keys[] = {"Subset",
"RangeSubsetting",
WCS_URL_PARAMETERS,
WCS_SERVICE_OPTIONS,
WCS_TWEAK_OPTIONS,
WCS_HTTP_OPTIONS
#ifdef DEBUG_WCS
,
"filename"
#endif
};
for (unsigned int i = 0; i < CPL_ARRAYSIZE(keys); i++)
{
const char *value;
if (CSLFindString(poOpenInfo->papszOpenOptions, keys[i]) != -1)
{
value = "TRUE";
}
else
{
value = CSLFetchNameValue(poOpenInfo->papszOpenOptions, keys[i]);
if (value == nullptr)
{
continue;
}
}
updated = CPLUpdateXML(service, keys[i], value) || updated;
}
return updated;
}
/************************************************************************/
/* CreateFromCache() */
/************************************************************************/
static WCSDataset *CreateFromCache(const CPLString &cache)
{
WCSDataset *ds = new WCSDataset201(cache);
if (!ds)
{
return nullptr;
}
char **metadata = nullptr;
std::vector<CPLString> contents = ReadCache(cache);
CPLString path = "SUBDATASET_";
unsigned int index = 1;
for (unsigned int i = 0; i < contents.size(); ++i)
{
CPLString name = path + CPLString().Printf("%d_", index) + "NAME";
CPLString value = "WCS:" + contents[i];
metadata = CSLSetNameValue(metadata, name, value);
index += 1;
}
ds->SetMetadata(metadata, "SUBDATASETS");
CSLDestroy(metadata);
return ds;
}
/************************************************************************/
/* ParseURL() */
/************************************************************************/
static void ParseURL(CPLString &url, CPLString &version, CPLString &coverage,
CPLString &parameters)
{
version = CPLURLGetValue(url, "version");
url = URLRemoveKey(url, "version");
// the default version, the aim is to have version explicitly in cache keys
if (WCSParseVersion(version) == 0)
{
version = "2.0.1";
}
coverage = CPLURLGetValue(url, "coverageid"); // 2.0
if (coverage == "")
{
coverage = CPLURLGetValue(url, "identifiers"); // 1.1
if (coverage == "")
{
coverage = CPLURLGetValue(url, "coverage"); // 1.0
url = URLRemoveKey(url, "coverage");
}
else
{
url = URLRemoveKey(url, "identifiers");
}
}
else
{
url = URLRemoveKey(url, "coverageid");
}
size_t pos = url.find("?");
if (pos == std::string::npos)
{
url += "?";
return;
}
parameters = url.substr(pos + 1, std::string::npos);
url.erase(pos + 1, std::string::npos);
}
/************************************************************************/
/* Open() */
/************************************************************************/
GDALDataset *WCSDataset::Open(GDALOpenInfo *poOpenInfo)
{
if (!Identify(poOpenInfo))
{
return nullptr;
}
CPLString cache =
CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CACHE", "");
if (!SetupCache(cache, CPLFetchBool(poOpenInfo->papszOpenOptions,
"CLEAR_CACHE", false)))
{
return nullptr;
}
CPLXMLNode *service = nullptr;
char **papszModifiers = nullptr;
if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI((const char *)poOpenInfo->pszFilename, "WCS:"))
{
/* --------------------------------------------------------------------
*/
/* Filename is WCS:URL */
/* --------------------------------------------------------------------
*/
CPLString url = (const char *)(poOpenInfo->pszFilename + 4);
const char *del = CSLFetchNameValue(poOpenInfo->papszOpenOptions,
"DELETE_FROM_CACHE");
if (del != nullptr)
{
int k = atoi(del);
std::vector<CPLString> contents = ReadCache(cache);
if (k > 0 && k <= (int)contents.size())
{
DeleteEntryFromCache(cache, "", contents[k - 1]);
}
}
if (url == "")
{
return CreateFromCache(cache);
}
if (CPLFetchBool(poOpenInfo->papszOpenOptions, "REFRESH_CACHE", false))
{
DeleteEntryFromCache(cache, "", url);
}
// the cache:
// db = key=URL database
// key.xml = service file
// key.xml.aux.xml = metadata file
// key.xml = Capabilities response
// key.aux.xml = Global metadata
// key.DC.xml = DescribeCoverage response
CPLString filename;
bool cached;
if (SearchCache(cache, url, filename, ".xml", cached) != CE_None)
{
return nullptr; // error in cache
}
CPLString full_url = url, version, coverage, parameters;
ParseURL(url, version, coverage, parameters);
// The goal is to get the service XML and a filename for it
bool updated = false;
if (cached)
{
/* --------------------------------------------------------------------
*/
/* The fast route, service file is in cache. */
/* --------------------------------------------------------------------
*/
if (coverage == "")
{
CPLString url2 = CPLURLAddKVP(url, "version", version);
WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
return global;
}
service = CPLParseXMLFile(filename);
}
else
{
/* --------------------------------------------------------------------
*/
/* Get capabilities. */
/* --------------------------------------------------------------------
*/
CPLString url2 = CPLURLAddKVP(url, "version", version);
if (parameters != "")
{
url2 += "&" + parameters;
}
WCSDataset *global = BootstrapGlobal(poOpenInfo, cache, url2);
if (!global)
{
return nullptr;
}
if (coverage == "")
{
return global;
}
if (version == "")
{
version = global->Version();
}
service = CreateService(url, version, coverage, parameters);
/* --------------------------------------------------------------------
*/
/* The filename for the new service file. */
/* --------------------------------------------------------------------
*/
filename = "XXXXX";
if (AddEntryToCache(cache, full_url, filename, ".xml") != CE_None)
{
return nullptr; // error in cache
}
// Create basic service metadata
// copy global metadata (not SUBDATASETS metadata)
CPLString global_base = CPLString(global->GetDescription());
CPLString global_meta = global_base + ".aux.xml";
CPLString capabilities = global_base + ".xml";
CPLXMLTreeCloser doc(CPLParseXMLFile(global_meta));
CPLXMLNode *metadata = doc.getDocumentElement();
CPLXMLNode *domain =
SearchChildWithValue(metadata, "domain", "SUBDATASETS");
if (domain != nullptr)
{
CPLRemoveXMLChild(metadata, domain);
CPLDestroyXMLNode(domain);
}
// get metadata for this coverage from the capabilities XML
CPLXMLTreeCloser doc2(CPLParseXMLFile(capabilities));
global->ParseCoverageCapabilities(doc2.getDocumentElement(),
coverage, metadata->psChild);
delete global;
CPLString metadata_filename = filename + ".aux.xml";
CPLSerializeXMLTreeToFile(metadata, metadata_filename);
updated = true;
}
CPLFree(poOpenInfo->pszFilename);
poOpenInfo->pszFilename = CPLStrdup(filename);
updated = UpdateService(service, poOpenInfo) || updated;
if (updated || !cached)
{
CPLSerializeXMLTreeToFile(service, filename);
}
}
/* -------------------------------------------------------------------- */
/* Is this a WCS_GDAL service description file or "in url" */
/* equivalent? */
/* -------------------------------------------------------------------- */
else if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
"<WCS_GDAL>"))
{
service = CPLParseXMLString(poOpenInfo->pszFilename);
}
else if (poOpenInfo->nHeaderBytes >= 10 &&
STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "<WCS_GDAL>"))
{
service = CPLParseXMLFile(poOpenInfo->pszFilename);
}
/* -------------------------------------------------------------------- */
/* Is this apparently a subdataset? */
/* -------------------------------------------------------------------- */
else if (STARTS_WITH_CI((const char *)poOpenInfo->pszFilename,
"WCS_SDS:") &&
poOpenInfo->nHeaderBytes == 0)
{
int iLast;
papszModifiers = CSLTokenizeString2(poOpenInfo->pszFilename + 8, ",",
CSLT_HONOURSTRINGS);
iLast = CSLCount(papszModifiers) - 1;
if (iLast >= 0)
{
service = CPLParseXMLFile(papszModifiers[iLast]);
CPLFree(papszModifiers[iLast]);
papszModifiers[iLast] = nullptr;
}
}
/* -------------------------------------------------------------------- */
/* Success so far? */
/* -------------------------------------------------------------------- */
if (service == nullptr)
{
CSLDestroy(papszModifiers);
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Confirm the requested access is supported. */
/* -------------------------------------------------------------------- */
if (poOpenInfo->eAccess == GA_Update)
{
CSLDestroy(papszModifiers);
CPLDestroyXMLNode(service);
CPLError(CE_Failure, CPLE_NotSupported,
"The WCS driver does not support update access to existing"
" datasets.\n");
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Check for required minimum fields. */
/* -------------------------------------------------------------------- */
if (!CPLGetXMLValue(service, "ServiceURL", nullptr) ||
!CPLGetXMLValue(service, "CoverageName", nullptr))
{
CSLDestroy(papszModifiers);
CPLError(
CE_Failure, CPLE_OpenFailed,
"Missing one or both of ServiceURL and CoverageName elements.\n"
"See WCS driver documentation for details on service description "
"file format.");
CPLDestroyXMLNode(service);
return nullptr;
}
/* -------------------------------------------------------------------- */
/* What version are we working with? */
/* -------------------------------------------------------------------- */
const char *pszVersion = CPLGetXMLValue(service, "Version", "1.0.0");
int nVersion = WCSParseVersion(pszVersion);
if (nVersion == 0)
{
CSLDestroy(papszModifiers);
CPLDestroyXMLNode(service);
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Create a corresponding GDALDataset. */
/* -------------------------------------------------------------------- */
WCSDataset *poDS;
if (nVersion == 201)
{
poDS = new WCSDataset201(cache);
}
else if (nVersion / 10 == 11)
{
poDS = new WCSDataset110(nVersion, cache);
}
else
{
poDS = new WCSDataset100(cache);
}
poDS->psService = service;
poDS->SetDescription(poOpenInfo->pszFilename);
poDS->papszSDSModifiers = papszModifiers;
// WCS:URL => basic metadata was already made
// Metadata is needed in ExtractGridInfo
poDS->TryLoadXML();
/* -------------------------------------------------------------------- */
/* Capture HTTP parameters. */
/* -------------------------------------------------------------------- */
const char *pszParam;
poDS->papszHttpOptions =
CSLSetNameValue(poDS->papszHttpOptions, "TIMEOUT",
CPLGetXMLValue(service, "Timeout", "30"));
pszParam = CPLGetXMLValue(service, "HTTPAUTH", nullptr);
if (pszParam)
poDS->papszHttpOptions =
CSLSetNameValue(poDS->papszHttpOptions, "HTTPAUTH", pszParam);
pszParam = CPLGetXMLValue(service, "USERPWD", nullptr);
if (pszParam)
poDS->papszHttpOptions =
CSLSetNameValue(poDS->papszHttpOptions, "USERPWD", pszParam);
/* -------------------------------------------------------------------- */
/* If we don't have the DescribeCoverage result for this */
/* coverage, fetch it now. */
/* -------------------------------------------------------------------- */
if (CPLGetXMLNode(service, "CoverageOffering") == nullptr &&
CPLGetXMLNode(service, "CoverageDescription") == nullptr)
{
if (!poDS->DescribeCoverage())
{
delete poDS;
return nullptr;
}
}
/* -------------------------------------------------------------------- */
/* Extract coordinate system, grid size, and geotransform from */
/* the coverage description and/or service description */
/* information. */
/* -------------------------------------------------------------------- */
if (!poDS->ExtractGridInfo())
{
delete poDS;
return nullptr;
}
/* -------------------------------------------------------------------- */
/* Leave now or there may be a GetCoverage call. */
/* */
/* -------------------------------------------------------------------- */
int nBandCount = -1;
CPLString sBandCount = CPLGetXMLValue(service, "BandCount", "");
if (sBandCount != "")
{
nBandCount = atoi(sBandCount);
}
if (CPLFetchBool(poOpenInfo->papszOpenOptions, "SKIP_GETCOVERAGE", false) ||
nBandCount == 0)
{
return poDS;
}
/* -------------------------------------------------------------------- */
/* Extract band count and type from a sample. */
/* -------------------------------------------------------------------- */
if (!poDS->EstablishRasterDetails()) // todo: do this only if missing info
{
delete poDS;
return nullptr;
}
/* -------------------------------------------------------------------- */
/* It is ok to not have bands. The user just needs to supply */
/* more information. */
/* -------------------------------------------------------------------- */
nBandCount = atoi(CPLGetXMLValue(service, "BandCount", "0"));
if (nBandCount == 0)
{
return poDS;
}
/* -------------------------------------------------------------------- */
/* Create band information objects. */
/* -------------------------------------------------------------------- */
int iBand;
if (!GDALCheckBandCount(nBandCount, FALSE))
{
delete poDS;
return nullptr;
}
for (iBand = 0; iBand < nBandCount; iBand++)
{
WCSRasterBand *band = new WCSRasterBand(poDS, iBand + 1, -1);
// copy band specific metadata to the band
char **md_from = poDS->GetMetadata("");
char **md_to = nullptr;
if (md_from)
{
CPLString our_key = CPLString().Printf("FIELD_%d_", iBand + 1);
for (char **from = md_from; *from != nullptr; ++from)
{
std::vector<CPLString> kv = Split(*from, "=");
if (kv.size() > 1 && STARTS_WITH(kv[0], our_key))
{
CPLString key = kv[0];
CPLString value = kv[1];
key.erase(0, our_key.length());
md_to = CSLSetNameValue(md_to, key, value);
}
}
}
band->SetMetadata(md_to, "");
CSLDestroy(md_to);
poDS->SetBand(iBand + 1, band);
}
/* -------------------------------------------------------------------- */
/* Set time metadata on the dataset if we are selecting a */
/* temporal slice. */
/* -------------------------------------------------------------------- */
CPLString osTime = CSLFetchNameValueDef(poDS->papszSDSModifiers, "time",
poDS->osDefaultTime);
if (osTime != "")
poDS->GDALMajorObject::SetMetadataItem("TIME_POSITION", osTime.c_str());
/* -------------------------------------------------------------------- */
/* Do we have a band identifier to select only a subset of bands? */
/* -------------------------------------------------------------------- */
poDS->osBandIdentifier = CPLGetXMLValue(service, "BandIdentifier", "");
/* -------------------------------------------------------------------- */
/* Do we have time based subdatasets? If so, record them in */
/* metadata. Note we don't do subdatasets if this is a */
/* subdataset or if this is an all-in-memory service. */
/* -------------------------------------------------------------------- */
if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WCS_SDS:") &&
!STARTS_WITH_CI(poOpenInfo->pszFilename, "<WCS_GDAL>") &&
!poDS->aosTimePositions.empty())
{
char **papszSubdatasets = nullptr;
int iTime;
for (iTime = 0; iTime < (int)poDS->aosTimePositions.size(); iTime++)
{
CPLString osName;
CPLString osValue;
osName.Printf("SUBDATASET_%d_NAME", iTime + 1);
osValue.Printf("WCS_SDS:time=\"%s\",%s",
poDS->aosTimePositions[iTime].c_str(),
poOpenInfo->pszFilename);
papszSubdatasets =
CSLSetNameValue(papszSubdatasets, osName, osValue);
CPLString osCoverage =
CPLGetXMLValue(poDS->psService, "CoverageName", "");
osName.Printf("SUBDATASET_%d_DESC", iTime + 1);
osValue.Printf("Coverage %s at time %s", osCoverage.c_str(),
poDS->aosTimePositions[iTime].c_str());
papszSubdatasets =
CSLSetNameValue(papszSubdatasets, osName, osValue);
}
poDS->GDALMajorObject::SetMetadata(papszSubdatasets, "SUBDATASETS");
CSLDestroy(papszSubdatasets);
}
/* -------------------------------------------------------------------- */
/* Initialize any PAM information. */
/* -------------------------------------------------------------------- */
poDS->TryLoadXML();
return poDS;
}
/************************************************************************/
/* GetGeoTransform() */
/************************************************************************/
CPLErr WCSDataset::GetGeoTransform(double *padfTransform)
{
memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
return CE_None;
}
/************************************************************************/
/* GetSpatialRef() */
/************************************************************************/
const OGRSpatialReference *WCSDataset::GetSpatialRef() const
{
const auto poSRS = GDALPamDataset::GetSpatialRef();
if (poSRS)
return poSRS;
return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
}
/************************************************************************/
/* GetFileList() */
/************************************************************************/
char **WCSDataset::GetFileList()
{
char **papszFileList = GDALPamDataset::GetFileList();
/* -------------------------------------------------------------------- */
/* ESRI also wishes to include service urls in the file list */
/* though this is not currently part of the general definition */
/* of GetFileList() for GDAL. */
/* -------------------------------------------------------------------- */
#ifdef ESRI_BUILD
CPLString file;
file.Printf("%s%s", CPLGetXMLValue(psService, "ServiceURL", ""),
CPLGetXMLValue(psService, "CoverageName", ""));
papszFileList = CSLAddString(papszFileList, file.c_str());
#endif /* def ESRI_BUILD */
return papszFileList;
}
/************************************************************************/
/* GetMetadataDomainList() */
/************************************************************************/
char **WCSDataset::GetMetadataDomainList()
{
return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
TRUE, "xml:CoverageOffering", nullptr);
}
/************************************************************************/
/* GetMetadata() */
/************************************************************************/
char **WCSDataset::GetMetadata(const char *pszDomain)
{
if (pszDomain == nullptr || !EQUAL(pszDomain, "xml:CoverageOffering"))
return GDALPamDataset::GetMetadata(pszDomain);
CPLXMLNode *psNode = CPLGetXMLNode(psService, "CoverageOffering");
if (psNode == nullptr)
psNode = CPLGetXMLNode(psService, "CoverageDescription");
if (psNode == nullptr)
return nullptr;
if (apszCoverageOfferingMD[0] == nullptr)
{
CPLXMLNode *psNext = psNode->psNext;
psNode->psNext = nullptr;
apszCoverageOfferingMD[0] = CPLSerializeXMLTree(psNode);
psNode->psNext = psNext;
}
return apszCoverageOfferingMD;
}
/************************************************************************/
/* GDALRegister_WCS() */
/************************************************************************/
void GDALRegister_WCS()
{
if (GDALGetDriverByName("WCS") != nullptr)
return;
GDALDriver *poDriver = new GDALDriver();
poDriver->SetDescription("WCS");
poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OGC Web Coverage Service");
poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/wcs.html");
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
poDriver->pfnOpen = WCSDataset::Open;
poDriver->pfnIdentify = WCSDataset::Identify;
GetGDALDriverManager()->RegisterDriver(poDriver);
}