1003 строки
37 KiB
C++
1003 строки
37 KiB
C++
/******************************************************************************
|
|
*
|
|
* Project: WCS Client Driver
|
|
* Purpose: Implementation of Dataset class for WCS 1.1.
|
|
* Author: Frank Warmerdam, warmerdam@pobox.com
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2006, Frank Warmerdam
|
|
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
|
|
* Copyright (c) 2017, Ari Jolma
|
|
* Copyright (c) 2017, Finnish Environment Institute
|
|
*
|
|
* 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 "wcsutils.h"
|
|
|
|
using namespace WCSUtils;
|
|
|
|
/************************************************************************/
|
|
/* GetExtent() */
|
|
/* */
|
|
/************************************************************************/
|
|
|
|
std::vector<double> WCSDataset110::GetExtent(int nXOff, int nYOff, int nXSize,
|
|
int nYSize,
|
|
CPL_UNUSED int nBufXSize,
|
|
CPL_UNUSED int nBufYSize)
|
|
{
|
|
std::vector<double> extent;
|
|
|
|
// outer edges of outer pixels.
|
|
extent.push_back(adfGeoTransform[0] + (nXOff)*adfGeoTransform[1]);
|
|
extent.push_back(adfGeoTransform[3] +
|
|
(nYOff + nYSize) * adfGeoTransform[5]);
|
|
extent.push_back(adfGeoTransform[0] +
|
|
(nXOff + nXSize) * adfGeoTransform[1]);
|
|
extent.push_back(adfGeoTransform[3] + (nYOff)*adfGeoTransform[5]);
|
|
|
|
bool no_shrink = CPLGetXMLBoolean(psService, "OuterExtents");
|
|
|
|
// WCS 1.1 extents are centers of outer pixels.
|
|
if (!no_shrink)
|
|
{
|
|
extent[2] -= adfGeoTransform[1] * 0.5;
|
|
extent[0] += adfGeoTransform[1] * 0.5;
|
|
extent[1] -= adfGeoTransform[5] * 0.5;
|
|
extent[3] += adfGeoTransform[5] * 0.5;
|
|
}
|
|
|
|
double dfXStep, dfYStep;
|
|
|
|
if (!no_shrink)
|
|
{
|
|
dfXStep = (nXSize / (double)nBufXSize) * adfGeoTransform[1];
|
|
dfYStep = (nYSize / (double)nBufYSize) * adfGeoTransform[5];
|
|
// Carefully adjust bounds for pixel centered values at new
|
|
// sampling density.
|
|
if (nBufXSize != nXSize || nBufYSize != nYSize)
|
|
{
|
|
dfXStep = (nXSize / (double)nBufXSize) * adfGeoTransform[1];
|
|
dfYStep = (nYSize / (double)nBufYSize) * adfGeoTransform[5];
|
|
|
|
extent[0] =
|
|
nXOff * adfGeoTransform[1] + adfGeoTransform[0] + dfXStep * 0.5;
|
|
extent[2] = extent[0] + (nBufXSize - 1) * dfXStep;
|
|
|
|
extent[3] =
|
|
nYOff * adfGeoTransform[5] + adfGeoTransform[3] + dfYStep * 0.5;
|
|
extent[1] = extent[3] + (nBufYSize - 1) * dfYStep;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double adjust =
|
|
CPLAtof(CPLGetXMLValue(psService, "BufSizeAdjust", "0.0"));
|
|
dfXStep = (nXSize / ((double)nBufXSize + adjust)) * adfGeoTransform[1];
|
|
dfYStep = (nYSize / ((double)nBufYSize + adjust)) * adfGeoTransform[5];
|
|
}
|
|
|
|
extent.push_back(dfXStep);
|
|
extent.push_back(dfYStep);
|
|
|
|
return extent;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetCoverageRequest() */
|
|
/* */
|
|
/************************************************************************/
|
|
|
|
CPLString WCSDataset110::GetCoverageRequest(bool scaled,
|
|
CPL_UNUSED int nBufXSize,
|
|
CPL_UNUSED int nBufYSize,
|
|
const std::vector<double> &extent,
|
|
CPLString osBandList)
|
|
{
|
|
CPLString osRequest;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* URL encode strings that could have questionable characters. */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLString osCoverage = CPLGetXMLValue(psService, "CoverageName", "");
|
|
|
|
char *pszEncoded = CPLEscapeString(osCoverage, -1, CPLES_URL);
|
|
osCoverage = pszEncoded;
|
|
CPLFree(pszEncoded);
|
|
|
|
CPLString osFormat = CPLGetXMLValue(psService, "PreferredFormat", "");
|
|
|
|
pszEncoded = CPLEscapeString(osFormat, -1, CPLES_URL);
|
|
osFormat = pszEncoded;
|
|
CPLFree(pszEncoded);
|
|
|
|
CPLString osRangeSubset = CPLGetXMLValue(psService, "FieldName", "");
|
|
|
|
// todo: MapServer seems to require interpolation
|
|
|
|
CPLString interpolation = CPLGetXMLValue(psService, "Interpolation", "");
|
|
if (interpolation == "")
|
|
{
|
|
// old undocumented key for interpolation in service
|
|
interpolation = CPLGetXMLValue(psService, "Resample", "");
|
|
}
|
|
if (interpolation != "")
|
|
{
|
|
osRangeSubset += ":" + interpolation;
|
|
}
|
|
|
|
if (osBandList != "")
|
|
{
|
|
if (osBandIdentifier != "")
|
|
{
|
|
osRangeSubset += CPLString().Printf(
|
|
"[%s[%s]]", osBandIdentifier.c_str(), osBandList.c_str());
|
|
}
|
|
}
|
|
|
|
osRangeSubset = "&RangeSubset=" + URLEncode(osRangeSubset);
|
|
|
|
double bbox_0 = extent[0], // min X
|
|
bbox_1 = extent[1], // min Y
|
|
bbox_2 = extent[2], // max X
|
|
bbox_3 = extent[3]; // max Y
|
|
|
|
if (axis_order_swap)
|
|
{
|
|
bbox_0 = extent[1]; // min Y
|
|
bbox_1 = extent[0]; // min X
|
|
bbox_2 = extent[3]; // max Y
|
|
bbox_3 = extent[2]; // max X
|
|
}
|
|
CPLString request = CPLGetXMLValue(psService, "ServiceURL", "");
|
|
request = CPLURLAddKVP(request, "SERVICE", "WCS");
|
|
request += CPLString().Printf(
|
|
"&VERSION=%s&REQUEST=GetCoverage&IDENTIFIER=%s"
|
|
"&FORMAT=%s&BOUNDINGBOX=%.15g,%.15g,%.15g,%.15g,%s%s",
|
|
CPLGetXMLValue(psService, "Version", ""), osCoverage.c_str(),
|
|
osFormat.c_str(), bbox_0, bbox_1, bbox_2, bbox_3, osCRS.c_str(),
|
|
osRangeSubset.c_str());
|
|
double origin_1 = extent[0], // min X
|
|
origin_2 = extent[3], // max Y
|
|
offset_1 = extent[4], // dX
|
|
offset_2 = extent[5]; // dY
|
|
|
|
if (axis_order_swap)
|
|
{
|
|
origin_1 = extent[3]; // max Y
|
|
origin_2 = extent[0]; // min X
|
|
offset_1 = extent[5]; // dY
|
|
offset_2 = extent[4]; // dX
|
|
}
|
|
CPLString offsets;
|
|
if (CPLGetXMLBoolean(psService, "OffsetsPositive"))
|
|
{
|
|
offset_1 = fabs(offset_1);
|
|
offset_2 = fabs(offset_2);
|
|
}
|
|
if (EQUAL(CPLGetXMLValue(psService, "NrOffsets", "4"), "2"))
|
|
{
|
|
offsets = CPLString().Printf("%.15g,%.15g", offset_1, offset_2);
|
|
}
|
|
else
|
|
{
|
|
if (axis_order_swap)
|
|
{
|
|
// Only tested with GeoServer but this is the correct offset(?)
|
|
offsets = CPLString().Printf("0,%.15g,%.15g,0", offset_2, offset_1);
|
|
}
|
|
else
|
|
{
|
|
offsets = CPLString().Printf("%.15g,0,0,%.15g", offset_1, offset_2);
|
|
}
|
|
}
|
|
bool do_not_include =
|
|
CPLGetXMLBoolean(psService, "GridCRSOptional") && !scaled;
|
|
if (!do_not_include)
|
|
{
|
|
request += CPLString().Printf(
|
|
"&GridBaseCRS=%s"
|
|
"&GridCS=urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS"
|
|
"&GridType=urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs"
|
|
"&GridOrigin=%.15g,%.15g"
|
|
"&GridOffsets=%s",
|
|
osCRS.c_str(), origin_1, origin_2, offsets.c_str());
|
|
}
|
|
CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
|
|
if (extra != "")
|
|
{
|
|
std::vector<CPLString> pairs = Split(extra, "&");
|
|
for (unsigned int i = 0; i < pairs.size(); ++i)
|
|
{
|
|
std::vector<CPLString> pair = Split(pairs[i], "=");
|
|
request = CPLURLAddKVP(request, pair[0], pair[1]);
|
|
}
|
|
}
|
|
extra = CPLGetXMLValue(psService, "GetCoverageExtra", "");
|
|
if (extra != "")
|
|
{
|
|
std::vector<CPLString> pairs = Split(extra, "&");
|
|
for (unsigned int i = 0; i < pairs.size(); ++i)
|
|
{
|
|
std::vector<CPLString> pair = Split(pairs[i], "=");
|
|
request = CPLURLAddKVP(request, pair[0], pair[1]);
|
|
}
|
|
}
|
|
CPLDebug("WCS", "Requesting %s", request.c_str());
|
|
return request;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* DescribeCoverageRequest() */
|
|
/* */
|
|
/************************************************************************/
|
|
|
|
CPLString WCSDataset110::DescribeCoverageRequest()
|
|
{
|
|
CPLString request = CPLGetXMLValue(psService, "ServiceURL", "");
|
|
request = CPLURLAddKVP(request, "SERVICE", "WCS");
|
|
request = CPLURLAddKVP(request, "REQUEST", "DescribeCoverage");
|
|
request = CPLURLAddKVP(request, "VERSION",
|
|
CPLGetXMLValue(psService, "Version", "1.1.0"));
|
|
request = CPLURLAddKVP(request, "IDENTIFIERS",
|
|
CPLGetXMLValue(psService, "CoverageName", ""));
|
|
request = CPLURLAddKVP(request, "FORMAT", "text/xml");
|
|
CPLString extra = CPLGetXMLValue(psService, "Parameters", "");
|
|
if (extra != "")
|
|
{
|
|
std::vector<CPLString> pairs = Split(extra, "&");
|
|
for (unsigned int i = 0; i < pairs.size(); ++i)
|
|
{
|
|
std::vector<CPLString> pair = Split(pairs[i], "=");
|
|
request = CPLURLAddKVP(request, pair[0], pair[1]);
|
|
}
|
|
}
|
|
extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", "");
|
|
if (extra != "")
|
|
{
|
|
std::vector<CPLString> pairs = Split(extra, "&");
|
|
for (unsigned int i = 0; i < pairs.size(); ++i)
|
|
{
|
|
std::vector<CPLString> pair = Split(pairs[i], "=");
|
|
request = CPLURLAddKVP(request, pair[0], pair[1]);
|
|
}
|
|
}
|
|
return request;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CoverageOffering() */
|
|
/* */
|
|
/************************************************************************/
|
|
|
|
CPLXMLNode *WCSDataset110::CoverageOffering(CPLXMLNode *psDC)
|
|
{
|
|
return CPLGetXMLNode(psDC, "=CoverageDescriptions.CoverageDescription");
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ExtractGridInfo() */
|
|
/* */
|
|
/* Collect info about grid from describe coverage for WCS 1.1. */
|
|
/* */
|
|
/************************************************************************/
|
|
|
|
bool WCSDataset110::ExtractGridInfo()
|
|
|
|
{
|
|
CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageDescription");
|
|
|
|
if (psCO == nullptr)
|
|
return false;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* We need to strip off name spaces so it is easier to */
|
|
/* searchfor plain gml names. */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLStripXMLNamespace(psCO, nullptr, TRUE);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Verify we have a SpatialDomain and GridCRS. */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLXMLNode *psSD = CPLGetXMLNode(psCO, "Domain.SpatialDomain");
|
|
CPLXMLNode *psGCRS = CPLGetXMLNode(psSD, "GridCRS");
|
|
|
|
if (psSD == nullptr || psGCRS == nullptr)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unable to find GridCRS in CoverageDescription,\n"
|
|
"unable to process WCS Coverage.");
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Establish our coordinate system. */
|
|
/* This is needed before geometry since we may have axis order swap. */
|
|
/* -------------------------------------------------------------------- */
|
|
CPLString crs = ParseCRS(psGCRS);
|
|
|
|
if (crs.empty())
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unable to find GridCRS.GridBaseCRS");
|
|
return false;
|
|
}
|
|
|
|
// SetCRS should fail only if the CRS is really unknown to GDAL
|
|
if (!SetCRS(crs, true))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unable to interpret GridBaseCRS '%s'.", crs.c_str());
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Collect size, origin, and offsets for SetGeometry() */
|
|
/* */
|
|
/* Extract Geotransform from GridCRS. */
|
|
/* */
|
|
/* -------------------------------------------------------------------- */
|
|
const char *pszGridType = CPLGetXMLValue(
|
|
psGCRS, "GridType", "urn:ogc:def:method:WCS::2dSimpleGrid");
|
|
bool swap =
|
|
axis_order_swap && !CPLGetXMLBoolean(psService, "NoGridAxisSwap");
|
|
std::vector<double> origin =
|
|
Flist(Split(CPLGetXMLValue(psGCRS, "GridOrigin", ""), " ", swap));
|
|
|
|
std::vector<CPLString> offset_1 =
|
|
Split(CPLGetXMLValue(psGCRS, "GridOffsets", ""), " ");
|
|
std::vector<CPLString> offset_2;
|
|
size_t n = offset_1.size();
|
|
if (n % 2 != 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"GridOffsets has incorrect amount of coefficients.\n"
|
|
"Unable to process WCS coverage.");
|
|
return false;
|
|
}
|
|
for (unsigned int i = 0; i < n / 2; ++i)
|
|
{
|
|
CPLString s = offset_1.back();
|
|
offset_1.erase(offset_1.end() - 1);
|
|
offset_2.insert(offset_2.begin(), s);
|
|
}
|
|
std::vector<std::vector<double>> offsets;
|
|
if (swap)
|
|
{
|
|
offsets.push_back(Flist(offset_2));
|
|
offsets.push_back(Flist(offset_1));
|
|
}
|
|
else
|
|
{
|
|
offsets.push_back(Flist(offset_1));
|
|
offsets.push_back(Flist(offset_2));
|
|
}
|
|
|
|
if (strstr(pszGridType, ":2dGridIn2dCrs") ||
|
|
strstr(pszGridType, ":2dGridin2dCrs"))
|
|
{
|
|
if (!(offset_1.size() == 2 && origin.size() == 2))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"2dGridIn2dCrs does not have expected GridOrigin or\n"
|
|
"GridOffsets values - unable to process WCS coverage.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
else if (strstr(pszGridType, ":2dGridIn3dCrs"))
|
|
{
|
|
if (!(offset_1.size() == 3 && origin.size() == 3))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"2dGridIn3dCrs does not have expected GridOrigin or\n"
|
|
"GridOffsets values - unable to process WCS coverage.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
else if (strstr(pszGridType, ":2dSimpleGrid"))
|
|
{
|
|
if (!(offset_1.size() == 1 && origin.size() == 2))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"2dSimpleGrid does not have expected GridOrigin or\n"
|
|
"GridOffsets values - unable to process WCS coverage.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Unrecognized GridCRS.GridType value '%s',\n"
|
|
"unable to process WCS coverage.",
|
|
pszGridType);
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Search for an ImageCRS for raster size. */
|
|
/* -------------------------------------------------------------------- */
|
|
std::vector<int> size;
|
|
CPLXMLNode *psNode;
|
|
|
|
for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0;
|
|
psNode = psNode->psNext)
|
|
{
|
|
if (psNode->eType != CXT_Element ||
|
|
!EQUAL(psNode->pszValue, "BoundingBox"))
|
|
continue;
|
|
|
|
CPLString osBBCRS = ParseCRS(psNode);
|
|
if (strstr(osBBCRS, ":imageCRS"))
|
|
{
|
|
std::vector<CPLString> bbox = ParseBoundingBox(psNode);
|
|
if (bbox.size() >= 2)
|
|
{
|
|
std::vector<int> low = Ilist(Split(bbox[0], " "), 0, 2);
|
|
std::vector<int> high = Ilist(Split(bbox[1], " "), 0, 2);
|
|
if (low[0] == 0 && low[1] == 0)
|
|
{
|
|
size.push_back(high[0]);
|
|
size.push_back(high[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Otherwise we search for a bounding box in our coordinate */
|
|
/* system and derive the size from that. */
|
|
/* -------------------------------------------------------------------- */
|
|
for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0;
|
|
psNode = psNode->psNext)
|
|
{
|
|
if (psNode->eType != CXT_Element ||
|
|
!EQUAL(psNode->pszValue, "BoundingBox"))
|
|
continue;
|
|
|
|
CPLString osBBCRS = ParseCRS(psNode);
|
|
if (osBBCRS == osCRS)
|
|
{
|
|
std::vector<CPLString> bbox = ParseBoundingBox(psNode);
|
|
bool not_rot =
|
|
(offsets[0].size() == 1 && offsets[1].size() == 1) ||
|
|
((swap && offsets[0][0] == 0.0 && offsets[1][1] == 0.0) ||
|
|
(!swap && offsets[0][1] == 0.0 && offsets[1][0] == 0.0));
|
|
if (bbox.size() >= 2 && not_rot)
|
|
{
|
|
std::vector<double> low =
|
|
Flist(Split(bbox[0], " ", axis_order_swap), 0, 2);
|
|
std::vector<double> high =
|
|
Flist(Split(bbox[1], " ", axis_order_swap), 0, 2);
|
|
double c1 = offsets[0][0];
|
|
double c2 =
|
|
offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1];
|
|
size.push_back((int)((high[0] - low[0]) / c1 + 1.01));
|
|
size.push_back((int)((high[1] - low[1]) / fabs(c2) + 1.01));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (size.size() < 2)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Could not determine the size of the grid.");
|
|
return false;
|
|
}
|
|
|
|
SetGeometry(size, origin, offsets);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we have a coordinate system override? */
|
|
/* -------------------------------------------------------------------- */
|
|
const char *pszProjOverride = CPLGetXMLValue(psService, "SRS", nullptr);
|
|
|
|
if (pszProjOverride)
|
|
{
|
|
if (m_oSRS.SetFromUserInput(
|
|
pszProjOverride,
|
|
OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
|
|
OGRERR_NONE)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"<SRS> element contents not parsable:\n%s",
|
|
pszProjOverride);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Pick a format type if we don't already have one selected. */
|
|
/* */
|
|
/* We will prefer anything that sounds like TIFF, otherwise */
|
|
/* falling back to the first supported format. Should we */
|
|
/* consider preferring the nativeFormat if available? */
|
|
/* -------------------------------------------------------------------- */
|
|
if (CPLGetXMLValue(psService, "PreferredFormat", nullptr) == nullptr)
|
|
{
|
|
CPLString osPreferredFormat;
|
|
|
|
for (psNode = psCO->psChild; psNode != nullptr; psNode = psNode->psNext)
|
|
{
|
|
if (psNode->eType == CXT_Element &&
|
|
EQUAL(psNode->pszValue, "SupportedFormat") && psNode->psChild &&
|
|
psNode->psChild->eType == CXT_Text)
|
|
{
|
|
if (osPreferredFormat.empty())
|
|
osPreferredFormat = psNode->psChild->pszValue;
|
|
|
|
if (strstr(psNode->psChild->pszValue, "tiff") != nullptr ||
|
|
strstr(psNode->psChild->pszValue, "TIFF") != nullptr ||
|
|
strstr(psNode->psChild->pszValue, "Tiff") != nullptr)
|
|
{
|
|
osPreferredFormat = psNode->psChild->pszValue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!osPreferredFormat.empty())
|
|
{
|
|
bServiceDirty = true;
|
|
CPLCreateXMLElementAndValue(psService, "PreferredFormat",
|
|
osPreferredFormat);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Try to identify a nodata value. For now we only support the */
|
|
/* singleValue mechanism. */
|
|
/* -------------------------------------------------------------------- */
|
|
if (CPLGetXMLValue(psService, "NoDataValue", nullptr) == nullptr)
|
|
{
|
|
const char *pszSV =
|
|
CPLGetXMLValue(psCO, "Range.Field.NullValue", nullptr);
|
|
|
|
if (pszSV != nullptr && (CPLAtof(pszSV) != 0.0 || *pszSV == DIGIT_ZERO))
|
|
{
|
|
bServiceDirty = true;
|
|
CPLCreateXMLElementAndValue(psService, "NoDataValue", pszSV);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Grab the field name, if possible. */
|
|
/* -------------------------------------------------------------------- */
|
|
if (CPLGetXMLValue(psService, "FieldName", nullptr) == nullptr)
|
|
{
|
|
CPLString osFieldName =
|
|
CPLGetXMLValue(psCO, "Range.Field.Identifier", "");
|
|
|
|
if (!osFieldName.empty())
|
|
{
|
|
bServiceDirty = true;
|
|
CPLCreateXMLElementAndValue(psService, "FieldName", osFieldName);
|
|
}
|
|
else
|
|
{
|
|
CPLError(
|
|
CE_Failure, CPLE_AppDefined,
|
|
"Unable to find required Identifier name %s for Range Field.",
|
|
osCRS.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we have a "Band" axis? If so try to grab the bandcount */
|
|
/* and data type from it. */
|
|
/* -------------------------------------------------------------------- */
|
|
osBandIdentifier = CPLGetXMLValue(psService, "BandIdentifier", "");
|
|
CPLXMLNode *psAxis =
|
|
CPLGetXMLNode(psService, "CoverageDescription.Range.Field.Axis");
|
|
|
|
if (osBandIdentifier.empty() &&
|
|
(EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Band") ||
|
|
EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Bands")) &&
|
|
CPLGetXMLNode(psAxis, "AvailableKeys") != nullptr)
|
|
{
|
|
osBandIdentifier = CPLGetXMLValue(psAxis, "Identifier", "");
|
|
|
|
// verify keys are ascending starting at 1
|
|
CPLXMLNode *psValues = CPLGetXMLNode(psAxis, "AvailableKeys");
|
|
CPLXMLNode *psSV;
|
|
int iBand;
|
|
|
|
for (psSV = psValues->psChild, iBand = 1; psSV != nullptr;
|
|
psSV = psSV->psNext, iBand++)
|
|
{
|
|
if (psSV->eType != CXT_Element || !EQUAL(psSV->pszValue, "Key") ||
|
|
psSV->psChild == nullptr || psSV->psChild->eType != CXT_Text ||
|
|
atoi(psSV->psChild->pszValue) != iBand)
|
|
{
|
|
osBandIdentifier = "";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!osBandIdentifier.empty())
|
|
{
|
|
if (CPLGetXMLValue(psService, "BandIdentifier", nullptr) == nullptr)
|
|
{
|
|
bServiceDirty = true;
|
|
CPLSetXMLValue(psService, "BandIdentifier", osBandIdentifier);
|
|
}
|
|
|
|
if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr)
|
|
{
|
|
bServiceDirty = true;
|
|
CPLSetXMLValue(psService, "BandCount",
|
|
CPLString().Printf("%d", iBand - 1));
|
|
}
|
|
}
|
|
|
|
// Is this an ESRI server returning a GDAL recognised data type?
|
|
CPLString osDataType = CPLGetXMLValue(psAxis, "DataType", "");
|
|
if (GDALGetDataTypeByName(osDataType) != GDT_Unknown &&
|
|
CPLGetXMLValue(psService, "BandType", nullptr) == nullptr)
|
|
{
|
|
bServiceDirty = true;
|
|
CPLCreateXMLElementAndValue(psService, "BandType", osDataType);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ParseCapabilities() */
|
|
/************************************************************************/
|
|
|
|
CPLErr WCSDataset110::ParseCapabilities(CPLXMLNode *Capabilities, CPLString url)
|
|
{
|
|
CPLStripXMLNamespace(Capabilities, nullptr, TRUE);
|
|
|
|
// make sure this is a capabilities document
|
|
if (strcmp(Capabilities->pszValue, "Capabilities") != 0)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Error in capabilities document.\n");
|
|
return CE_Failure;
|
|
}
|
|
|
|
char **metadata = nullptr;
|
|
CPLString path = "WCS_GLOBAL#";
|
|
|
|
CPLString key = path + "version";
|
|
metadata = CSLSetNameValue(metadata, key, Version());
|
|
|
|
for (CPLXMLNode *node = Capabilities->psChild; node != nullptr;
|
|
node = node->psNext)
|
|
{
|
|
const char *attr = node->pszValue;
|
|
if (node->eType == CXT_Attribute && EQUAL(attr, "updateSequence"))
|
|
{
|
|
key = path + "updateSequence";
|
|
CPLString value = CPLGetXMLValue(node, nullptr, "");
|
|
metadata = CSLSetNameValue(metadata, key, value);
|
|
}
|
|
}
|
|
|
|
// identification metadata
|
|
CPLString path2 = path;
|
|
std::vector<CPLString> keys2;
|
|
keys2.push_back("Title");
|
|
keys2.push_back("Abstract");
|
|
keys2.push_back("Fees");
|
|
keys2.push_back("AccessConstraints");
|
|
CPLXMLNode *service = AddSimpleMetaData(&metadata, Capabilities, path2,
|
|
"ServiceIdentification", keys2);
|
|
CPLString kw = GetKeywords(service, "Keywords", "Keyword");
|
|
if (kw != "")
|
|
{
|
|
CPLString name = path + "Keywords";
|
|
metadata = CSLSetNameValue(metadata, name, kw);
|
|
}
|
|
CPLString profiles = GetKeywords(service, "", "Profile");
|
|
if (profiles != "")
|
|
{
|
|
CPLString name = path + "Profiles";
|
|
metadata = CSLSetNameValue(metadata, name, profiles);
|
|
}
|
|
|
|
// provider metadata
|
|
path2 = path;
|
|
keys2.clear();
|
|
keys2.push_back("ProviderName");
|
|
CPLXMLNode *provider = AddSimpleMetaData(&metadata, Capabilities, path2,
|
|
"ServiceProvider", keys2);
|
|
if (provider)
|
|
{
|
|
CPLXMLNode *site = CPLGetXMLNode(provider, "ProviderSite");
|
|
if (site)
|
|
{
|
|
CPLString path3 = path2 + "ProviderSite";
|
|
CPLString value =
|
|
CPLGetXMLValue(CPLGetXMLNode(site, "href"), nullptr, "");
|
|
metadata = CSLSetNameValue(metadata, path3, value);
|
|
}
|
|
CPLString path3 = path2;
|
|
std::vector<CPLString> keys3;
|
|
keys3.push_back("IndividualName");
|
|
keys3.push_back("PositionName");
|
|
keys3.push_back("Role");
|
|
CPLXMLNode *contact = AddSimpleMetaData(&metadata, provider, path3,
|
|
"ServiceContact", keys3);
|
|
if (contact)
|
|
{
|
|
CPLString path4 = path3;
|
|
std::vector<CPLString> keys4;
|
|
keys4.push_back("HoursOfService");
|
|
keys4.push_back("ContactInstructions");
|
|
CPLXMLNode *info = AddSimpleMetaData(&metadata, contact, path4,
|
|
"ContactInfo", keys4);
|
|
if (info)
|
|
{
|
|
CPLString path5 = path4;
|
|
std::vector<CPLString> keys5;
|
|
keys5.push_back("DeliveryPoint");
|
|
keys5.push_back("City");
|
|
keys5.push_back("AdministrativeArea");
|
|
keys5.push_back("PostalCode");
|
|
keys5.push_back("Country");
|
|
keys5.push_back("ElectronicMailAddress");
|
|
CPLString path6 = path4;
|
|
std::vector<CPLString> keys6;
|
|
keys6.push_back("Voice");
|
|
keys6.push_back("Facsimile");
|
|
AddSimpleMetaData(&metadata, info, path5, "Address", keys5);
|
|
AddSimpleMetaData(&metadata, info, path6, "Phone", keys6);
|
|
}
|
|
}
|
|
}
|
|
|
|
// operations metadata
|
|
CPLString DescribeCoverageURL = "";
|
|
CPLXMLNode *service2 = CPLGetXMLNode(Capabilities, "OperationsMetadata");
|
|
if (service2)
|
|
{
|
|
for (CPLXMLNode *operation = service2->psChild; operation != nullptr;
|
|
operation = operation->psNext)
|
|
{
|
|
if (operation->eType != CXT_Element ||
|
|
!EQUAL(operation->pszValue, "Operation"))
|
|
{
|
|
continue;
|
|
}
|
|
if (EQUAL(CPLGetXMLValue(CPLGetXMLNode(operation, "name"), nullptr,
|
|
""),
|
|
"DescribeCoverage"))
|
|
{
|
|
DescribeCoverageURL = CPLGetXMLValue(
|
|
CPLGetXMLNode(CPLSearchXMLNode(operation, "Get"), "href"),
|
|
nullptr, "");
|
|
}
|
|
}
|
|
}
|
|
// if DescribeCoverageURL looks wrong, we change it
|
|
if (DescribeCoverageURL.find("localhost") != std::string::npos)
|
|
{
|
|
DescribeCoverageURL = URLRemoveKey(url, "request");
|
|
}
|
|
|
|
// service metadata (in 2.0)
|
|
CPLString ext = "ServiceMetadata";
|
|
CPLString formats = GetKeywords(Capabilities, ext, "formatSupported");
|
|
if (formats != "")
|
|
{
|
|
CPLString name = path + "formatSupported";
|
|
metadata = CSLSetNameValue(metadata, name, formats);
|
|
}
|
|
// wcs:Extensions: interpolation, CRS, others?
|
|
ext += ".Extension";
|
|
CPLString interpolation =
|
|
GetKeywords(Capabilities, ext, "interpolationSupported");
|
|
if (interpolation == "")
|
|
{
|
|
interpolation =
|
|
GetKeywords(Capabilities, ext + ".InterpolationMetadata",
|
|
"InterpolationSupported");
|
|
}
|
|
if (interpolation != "")
|
|
{
|
|
CPLString name = path + "InterpolationSupported";
|
|
metadata = CSLSetNameValue(metadata, name, interpolation);
|
|
}
|
|
CPLString crs = GetKeywords(Capabilities, ext, "crsSupported");
|
|
if (crs == "")
|
|
{
|
|
crs = GetKeywords(Capabilities, ext + ".CrsMetadata", "crsSupported");
|
|
}
|
|
if (crs != "")
|
|
{
|
|
CPLString name = path + "crsSupported";
|
|
metadata = CSLSetNameValue(metadata, name, crs);
|
|
}
|
|
|
|
this->SetMetadata(metadata, "");
|
|
CSLDestroy(metadata);
|
|
metadata = nullptr;
|
|
|
|
// contents metadata
|
|
CPLXMLNode *contents = CPLGetXMLNode(Capabilities, "Contents");
|
|
if (contents)
|
|
{
|
|
int index = 1;
|
|
for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
|
|
summary = summary->psNext)
|
|
{
|
|
if (summary->eType != CXT_Element ||
|
|
!EQUAL(summary->pszValue, "CoverageSummary"))
|
|
{
|
|
continue;
|
|
}
|
|
CPLString path3;
|
|
path3.Printf("SUBDATASET_%d_", index);
|
|
index += 1;
|
|
|
|
// the name and description of the subdataset:
|
|
// GDAL Data Model:
|
|
// The value of the _NAME is a string that can be passed to
|
|
// GDALOpen() to access the file.
|
|
|
|
CPLString key2 = path3 + "NAME";
|
|
|
|
CPLString name = DescribeCoverageURL;
|
|
name = CPLURLAddKVP(name, "version", this->Version());
|
|
|
|
CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId");
|
|
CPLString id;
|
|
if (node)
|
|
{
|
|
id = CPLGetXMLValue(node, nullptr, "");
|
|
}
|
|
else
|
|
{
|
|
node = CPLGetXMLNode(summary, "Identifier");
|
|
if (node)
|
|
{
|
|
id = CPLGetXMLValue(node, nullptr, "");
|
|
}
|
|
else
|
|
{
|
|
// todo: maybe not an error since CoverageSummary may be
|
|
// within CoverageSummary (07-067r5 Fig4)
|
|
CSLDestroy(metadata);
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Error in capabilities document.\n");
|
|
return CE_Failure;
|
|
}
|
|
}
|
|
name = CPLURLAddKVP(name, "coverage", id);
|
|
name = "WCS:" + name;
|
|
metadata = CSLSetNameValue(metadata, key2, name);
|
|
|
|
key2 = path3 + "DESC";
|
|
CPLString desc;
|
|
|
|
node = CPLGetXMLNode(summary, "Title");
|
|
if (node)
|
|
{
|
|
desc = CPLGetXMLValue(node, nullptr, "");
|
|
}
|
|
else
|
|
{
|
|
desc = id;
|
|
}
|
|
|
|
metadata = CSLSetNameValue(metadata, key2, desc);
|
|
|
|
// todo: compose global bounding box from WGS84BoundingBox and
|
|
// BoundingBox
|
|
|
|
// further subdataset (coverage) parameters are parsed in
|
|
// ParseCoverageCapabilities
|
|
}
|
|
}
|
|
this->SetMetadata(metadata, "SUBDATASETS");
|
|
CSLDestroy(metadata);
|
|
return CE_None;
|
|
}
|
|
|
|
void WCSDataset110::ParseCoverageCapabilities(CPLXMLNode *capabilities,
|
|
const CPLString &coverage,
|
|
CPLXMLNode *metadata)
|
|
{
|
|
CPLStripXMLNamespace(capabilities, nullptr, TRUE);
|
|
CPLXMLNode *contents = CPLGetXMLNode(capabilities, "Contents");
|
|
if (contents)
|
|
{
|
|
for (CPLXMLNode *summary = contents->psChild; summary != nullptr;
|
|
summary = summary->psNext)
|
|
{
|
|
if (summary->eType != CXT_Element ||
|
|
!EQUAL(summary->pszValue, "CoverageSummary"))
|
|
{
|
|
continue;
|
|
}
|
|
CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId");
|
|
CPLString id;
|
|
if (node)
|
|
{
|
|
id = CPLGetXMLValue(node, nullptr, "");
|
|
}
|
|
else
|
|
{
|
|
node = CPLGetXMLNode(summary, "Identifier");
|
|
if (node)
|
|
{
|
|
id = CPLGetXMLValue(node, nullptr, "");
|
|
}
|
|
else
|
|
{
|
|
id = "";
|
|
}
|
|
}
|
|
if (id != coverage)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Description
|
|
// todo: there could be Title and Abstract for each supported
|
|
// language
|
|
XMLCopyMetadata(summary, metadata, "Title");
|
|
XMLCopyMetadata(summary, metadata, "Abstract");
|
|
|
|
// 2.0.1 stuff
|
|
XMLCopyMetadata(summary, metadata, "CoverageSubtype");
|
|
|
|
// Keywords
|
|
CPLString kw = GetKeywords(summary, "Keywords", "Keyword");
|
|
CPLAddXMLAttributeAndValue(
|
|
CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
|
|
"Keywords");
|
|
|
|
// WCSContents
|
|
const char *tags[] = {"SupportedCRS", "SupportedFormat",
|
|
"OtherSource"};
|
|
for (unsigned int i = 0; i < CPL_ARRAYSIZE(tags); i++)
|
|
{
|
|
kw = GetKeywords(summary, "", tags[i]);
|
|
CPLAddXMLAttributeAndValue(
|
|
CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key",
|
|
tags[i]);
|
|
}
|
|
|
|
// skipping WGS84BoundingBox, BoundingBox, Metadata, Extension
|
|
// since those we'll get from coverage description
|
|
}
|
|
}
|
|
}
|