gdal/frmts/wms/wmsdriver.cpp

1180 строки
41 KiB
C++

/******************************************************************************
*
* Project: WMS Client Driver
* Purpose: Implementation of Dataset and RasterBand classes for WMS
* and other similar services.
* Author: Adam Nowacki, nowak@xpam.de
*
******************************************************************************
* Copyright (c) 2007, Adam Nowacki
* Copyright (c) 2009-2014, 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 "gdal_frmts.h"
#include "wmsdriver.h"
#include "wmsmetadataset.h"
#include "minidriver_wms.h"
#include "minidriver_tileservice.h"
#include "minidriver_worldwind.h"
#include "minidriver_tms.h"
#include "minidriver_tiled_wms.h"
#include "minidriver_virtualearth.h"
#include "minidriver_arcgis_server.h"
#include "minidriver_iip.h"
#include "minidriver_mrf.h"
#include "minidriver_ogcapimaps.h"
#include "minidriver_ogcapicoverage.h"
#include "cpl_json.h"
#include <limits>
#include <utility>
#include <algorithm>
//
// A static map holding seen server GetTileService responses, per process
// It makes opening and reopening rasters from the same server faster
//
GDALWMSDataset::StringMap_t GDALWMSDataset::cfg;
CPLMutex *GDALWMSDataset::cfgmtx = nullptr;
/************************************************************************/
/* GDALWMSDatasetGetConfigFromURL() */
/************************************************************************/
static CPLXMLNode *GDALWMSDatasetGetConfigFromURL(GDALOpenInfo *poOpenInfo)
{
const char *pszBaseURL = poOpenInfo->pszFilename;
if (STARTS_WITH_CI(pszBaseURL, "WMS:"))
pszBaseURL += 4;
CPLString osLayer = CPLURLGetValue(pszBaseURL, "LAYERS");
CPLString osVersion = CPLURLGetValue(pszBaseURL, "VERSION");
CPLString osSRS = CPLURLGetValue(pszBaseURL, "SRS");
CPLString osCRS = CPLURLGetValue(pszBaseURL, "CRS");
CPLString osBBOX = CPLURLGetValue(pszBaseURL, "BBOX");
CPLString osFormat = CPLURLGetValue(pszBaseURL, "FORMAT");
CPLString osTransparent = CPLURLGetValue(pszBaseURL, "TRANSPARENT");
/* GDAL specific extensions to alter the default settings */
CPLString osOverviewCount = CPLURLGetValue(pszBaseURL, "OVERVIEWCOUNT");
CPLString osTileSize = CPLURLGetValue(pszBaseURL, "TILESIZE");
CPLString osMinResolution = CPLURLGetValue(pszBaseURL, "MINRESOLUTION");
CPLString osBBOXOrder = CPLURLGetValue(pszBaseURL, "BBOXORDER");
CPLString osBaseURL = pszBaseURL;
/* Remove all keywords to get base URL */
if (osBBOXOrder.empty() && !osCRS.empty() &&
VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
{
OGRSpatialReference oSRS;
oSRS.SetFromUserInput(
osCRS, OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
oSRS.AutoIdentifyEPSG();
if (oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting())
{
osBBOXOrder = "yxYX";
}
}
osBaseURL = CPLURLAddKVP(osBaseURL, "VERSION", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "REQUEST", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "LAYERS", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "SRS", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "CRS", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "BBOX", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "FORMAT", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "TRANSPARENT", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "STYLES", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "WIDTH", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "HEIGHT", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "OVERVIEWCOUNT", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "TILESIZE", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "MINRESOLUTION", nullptr);
osBaseURL = CPLURLAddKVP(osBaseURL, "BBOXORDER", nullptr);
if (!osBaseURL.empty() && osBaseURL.back() == '&')
osBaseURL.resize(osBaseURL.size() - 1);
if (osVersion.empty())
osVersion = "1.1.1";
CPLString osSRSTag;
CPLString osSRSValue;
if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
{
if (!osSRS.empty())
{
CPLError(CE_Warning, CPLE_AppDefined,
"WMS version 1.3 and above expects CRS however SRS was "
"set instead.");
}
osSRSValue = osCRS;
osSRSTag = "CRS";
}
else
{
if (!osCRS.empty())
{
CPLError(CE_Warning, CPLE_AppDefined,
"WMS version 1.1.1 and below expects SRS however CRS was "
"set instead.");
}
osSRSValue = osSRS;
osSRSTag = "SRS";
}
if (osSRSValue.empty())
{
osSRSValue = "EPSG:4326";
if (osBBOX.empty())
{
if (osBBOXOrder.compare("yxYX") == 0)
osBBOX = "-90,-180,90,180";
else
osBBOX = "-180,-90,180,90";
}
}
else
{
if (osBBOX.empty())
{
OGRSpatialReference oSRS;
oSRS.SetFromUserInput(osSRSValue);
oSRS.AutoIdentifyEPSG();
double dfWestLongitudeDeg, dfSouthLatitudeDeg, dfEastLongitudeDeg,
dfNorthLatitudeDeg;
if (!oSRS.GetAreaOfUse(&dfWestLongitudeDeg, &dfSouthLatitudeDeg,
&dfEastLongitudeDeg, &dfNorthLatitudeDeg,
nullptr))
{
CPLError(CE_Failure, CPLE_AppDefined,
"Failed retrieving a default bounding box for the "
"requested SRS");
return nullptr;
}
auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
OGRCreateCoordinateTransformation(
OGRSpatialReference::GetWGS84SRS(), &oSRS));
if (!poCT)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Failed creating a coordinate transformation for the "
"requested SRS");
return nullptr;
}
if (!poCT->Transform(1, &dfWestLongitudeDeg, &dfNorthLatitudeDeg) ||
!poCT->Transform(1, &dfEastLongitudeDeg, &dfSouthLatitudeDeg))
{
CPLError(
CE_Failure, CPLE_AppDefined,
"Failed transforming coordinates to the requested SRS");
return nullptr;
}
const double dfMaxX =
std::max(dfWestLongitudeDeg, dfEastLongitudeDeg);
const double dfMinX =
std::min(dfWestLongitudeDeg, dfEastLongitudeDeg);
const double dfMaxY =
std::max(dfNorthLatitudeDeg, dfSouthLatitudeDeg);
const double dfMinY =
std::min(dfNorthLatitudeDeg, dfSouthLatitudeDeg);
if (osBBOXOrder.compare("yxYX") == 0)
{
osBBOX = CPLSPrintf("%lf,%lf,%lf,%lf", dfMinY, dfMinX, dfMaxY,
dfMaxX);
}
else
{
osBBOX = CPLSPrintf("%lf,%lf,%lf,%lf", dfMinX, dfMinY, dfMaxX,
dfMaxY);
}
}
}
char **papszTokens = CSLTokenizeStringComplex(osBBOX, ",", 0, 0);
if (CSLCount(papszTokens) != 4)
{
CSLDestroy(papszTokens);
return nullptr;
}
const char *pszMinX = papszTokens[0];
const char *pszMinY = papszTokens[1];
const char *pszMaxX = papszTokens[2];
const char *pszMaxY = papszTokens[3];
if (osBBOXOrder.compare("yxYX") == 0)
{
std::swap(pszMinX, pszMinY);
std::swap(pszMaxX, pszMaxY);
}
double dfMinX = CPLAtofM(pszMinX);
double dfMinY = CPLAtofM(pszMinY);
double dfMaxX = CPLAtofM(pszMaxX);
double dfMaxY = CPLAtofM(pszMaxY);
if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
{
CSLDestroy(papszTokens);
return nullptr;
}
int nTileSize = atoi(osTileSize);
if (nTileSize <= 128 || nTileSize > 2048)
nTileSize = 1024;
int nXSize, nYSize;
double dXSize, dYSize;
int nOverviewCount = (osOverviewCount.size()) ? atoi(osOverviewCount) : 20;
if (!osMinResolution.empty())
{
double dfMinResolution = CPLAtofM(osMinResolution);
while (nOverviewCount > 20)
{
nOverviewCount--;
dfMinResolution *= 2;
}
// Determine a suitable size that doesn't overflow max int.
dXSize = ((dfMaxX - dfMinX) / dfMinResolution + 0.5);
dYSize = ((dfMaxY - dfMinY) / dfMinResolution + 0.5);
while (dXSize > (std::numeric_limits<int>::max)() ||
dYSize > (std::numeric_limits<int>::max)())
{
dfMinResolution *= 2;
dXSize = ((dfMaxX - dfMinX) / dfMinResolution + 0.5);
dYSize = ((dfMaxY - dfMinY) / dfMinResolution + 0.5);
}
}
else
{
double dfRatio = (dfMaxX - dfMinX) / (dfMaxY - dfMinY);
if (dfRatio > 1)
{
dXSize = nTileSize;
dYSize = dXSize / dfRatio;
}
else
{
dYSize = nTileSize;
dXSize = dYSize * dfRatio;
}
if (nOverviewCount < 0 || nOverviewCount > 20)
nOverviewCount = 20;
dXSize = dXSize * (1 << nOverviewCount);
dYSize = dYSize * (1 << nOverviewCount);
// Determine a suitable size that doesn't overflow max int.
while (dXSize > (std::numeric_limits<int>::max)() ||
dYSize > (std::numeric_limits<int>::max)())
{
dXSize /= 2;
dYSize /= 2;
}
}
nXSize = (int)dXSize;
nYSize = (int)dYSize;
bool bTransparent = !osTransparent.empty() && CPLTestBool(osTransparent);
if (osFormat.empty())
{
if (!bTransparent)
{
osFormat = "image/jpeg";
}
else
{
osFormat = "image/png";
}
}
char *pszEscapedURL = CPLEscapeString(osBaseURL.c_str(), -1, CPLES_XML);
char *pszEscapedLayerXML = CPLEscapeString(osLayer.c_str(), -1, CPLES_XML);
CPLString osXML = CPLSPrintf(
"<GDAL_WMS>\n"
" <Service name=\"WMS\">\n"
" <Version>%s</Version>\n"
" <ServerUrl>%s</ServerUrl>\n"
" <Layers>%s</Layers>\n"
" <%s>%s</%s>\n"
" <ImageFormat>%s</ImageFormat>\n"
" <Transparent>%s</Transparent>\n"
" <BBoxOrder>%s</BBoxOrder>\n"
" </Service>\n"
" <DataWindow>\n"
" <UpperLeftX>%s</UpperLeftX>\n"
" <UpperLeftY>%s</UpperLeftY>\n"
" <LowerRightX>%s</LowerRightX>\n"
" <LowerRightY>%s</LowerRightY>\n"
" <SizeX>%d</SizeX>\n"
" <SizeY>%d</SizeY>\n"
" </DataWindow>\n"
" <BandsCount>%d</BandsCount>\n"
" <BlockSizeX>%d</BlockSizeX>\n"
" <BlockSizeY>%d</BlockSizeY>\n"
" <OverviewCount>%d</OverviewCount>\n"
"</GDAL_WMS>\n",
osVersion.c_str(), pszEscapedURL, pszEscapedLayerXML, osSRSTag.c_str(),
osSRSValue.c_str(), osSRSTag.c_str(), osFormat.c_str(),
(bTransparent) ? "TRUE" : "FALSE",
(osBBOXOrder.size()) ? osBBOXOrder.c_str() : "xyXY", pszMinX, pszMaxY,
pszMaxX, pszMinY, nXSize, nYSize, (bTransparent) ? 4 : 3, nTileSize,
nTileSize, nOverviewCount);
CPLFree(pszEscapedURL);
CPLFree(pszEscapedLayerXML);
CSLDestroy(papszTokens);
CPLDebug("WMS", "Opening WMS :\n%s", osXML.c_str());
return CPLParseXMLString(osXML);
}
/************************************************************************/
/* GDALWMSDatasetGetConfigFromTileMap() */
/************************************************************************/
static CPLXMLNode *GDALWMSDatasetGetConfigFromTileMap(CPLXMLNode *psXML)
{
CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TileMap");
if (psRoot == nullptr)
return nullptr;
CPLXMLNode *psTileSets = CPLGetXMLNode(psRoot, "TileSets");
if (psTileSets == nullptr)
return nullptr;
const char *pszURL = CPLGetXMLValue(psRoot, "tilemapservice", nullptr);
int bCanChangeURL = TRUE;
CPLString osURL;
if (pszURL)
{
osURL = pszURL;
/* Special hack for
* http://tilecache.osgeo.org/wms-c/Basic.py/1.0.0/basic/ */
if (strlen(pszURL) > 10 &&
STARTS_WITH(pszURL,
"http://tilecache.osgeo.org/wms-c/Basic.py/1.0.0/") &&
strcmp(pszURL + strlen(pszURL) - strlen("1.0.0/"), "1.0.0/") == 0)
{
osURL.resize(strlen(pszURL) - strlen("1.0.0/"));
bCanChangeURL = FALSE;
}
osURL += "${z}/${x}/${y}.${format}";
}
const char *pszSRS = CPLGetXMLValue(psRoot, "SRS", nullptr);
if (pszSRS == nullptr)
return nullptr;
CPLXMLNode *psBoundingBox = CPLGetXMLNode(psRoot, "BoundingBox");
if (psBoundingBox == nullptr)
return nullptr;
const char *pszMinX = CPLGetXMLValue(psBoundingBox, "minx", nullptr);
const char *pszMinY = CPLGetXMLValue(psBoundingBox, "miny", nullptr);
const char *pszMaxX = CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
const char *pszMaxY = CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
if (pszMinX == nullptr || pszMinY == nullptr || pszMaxX == nullptr ||
pszMaxY == nullptr)
return nullptr;
double dfMinX = CPLAtofM(pszMinX);
double dfMinY = CPLAtofM(pszMinY);
double dfMaxX = CPLAtofM(pszMaxX);
double dfMaxY = CPLAtofM(pszMaxY);
if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
return nullptr;
CPLXMLNode *psTileFormat = CPLGetXMLNode(psRoot, "TileFormat");
if (psTileFormat == nullptr)
return nullptr;
const char *pszTileWidth = CPLGetXMLValue(psTileFormat, "width", nullptr);
const char *pszTileHeight = CPLGetXMLValue(psTileFormat, "height", nullptr);
const char *pszTileFormat =
CPLGetXMLValue(psTileFormat, "extension", nullptr);
if (pszTileWidth == nullptr || pszTileHeight == nullptr ||
pszTileFormat == nullptr)
return nullptr;
int nTileWidth = atoi(pszTileWidth);
int nTileHeight = atoi(pszTileHeight);
if (nTileWidth < 128 || nTileHeight < 128)
return nullptr;
CPLXMLNode *psIter = psTileSets->psChild;
int nLevelCount = 0;
double dfPixelSize = 0;
for (; psIter != nullptr; psIter = psIter->psNext)
{
if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
{
const char *pszOrder = CPLGetXMLValue(psIter, "order", nullptr);
if (pszOrder == nullptr)
{
CPLDebug("WMS", "Cannot find order attribute");
return nullptr;
}
if (atoi(pszOrder) != nLevelCount)
{
CPLDebug("WMS", "Expected order=%d, got %s", nLevelCount,
pszOrder);
return nullptr;
}
const char *pszHref = CPLGetXMLValue(psIter, "href", nullptr);
if (nLevelCount == 0 && pszHref != nullptr)
{
if (bCanChangeURL && strlen(pszHref) > 10 &&
strcmp(pszHref + strlen(pszHref) - strlen("/0"), "/0") == 0)
{
osURL = pszHref;
osURL.resize(strlen(pszHref) - strlen("/0"));
osURL += "/${z}/${x}/${y}.${format}";
}
}
const char *pszUnitsPerPixel =
CPLGetXMLValue(psIter, "units-per-pixel", nullptr);
if (pszUnitsPerPixel == nullptr)
return nullptr;
dfPixelSize = CPLAtofM(pszUnitsPerPixel);
nLevelCount++;
}
}
if (nLevelCount == 0 || osURL.empty())
return nullptr;
int nXSize = 0;
int nYSize = 0;
while (nLevelCount > 0)
{
GIntBig nXSizeBig = (GIntBig)((dfMaxX - dfMinX) / dfPixelSize + 0.5);
GIntBig nYSizeBig = (GIntBig)((dfMaxY - dfMinY) / dfPixelSize + 0.5);
if (nXSizeBig < INT_MAX && nYSizeBig < INT_MAX)
{
nXSize = (int)nXSizeBig;
nYSize = (int)nYSizeBig;
break;
}
CPLDebug(
"WMS",
"Dropping one overview level so raster size fits into 32bit...");
dfPixelSize *= 2;
nLevelCount--;
}
char *pszEscapedURL = CPLEscapeString(osURL.c_str(), -1, CPLES_XML);
CPLString osXML = CPLSPrintf("<GDAL_WMS>\n"
" <Service name=\"TMS\">\n"
" <ServerUrl>%s</ServerUrl>\n"
" <Format>%s</Format>\n"
" </Service>\n"
" <DataWindow>\n"
" <UpperLeftX>%s</UpperLeftX>\n"
" <UpperLeftY>%s</UpperLeftY>\n"
" <LowerRightX>%s</LowerRightX>\n"
" <LowerRightY>%s</LowerRightY>\n"
" <TileLevel>%d</TileLevel>\n"
" <SizeX>%d</SizeX>\n"
" <SizeY>%d</SizeY>\n"
" </DataWindow>\n"
" <Projection>%s</Projection>\n"
" <BlockSizeX>%d</BlockSizeX>\n"
" <BlockSizeY>%d</BlockSizeY>\n"
" <BandsCount>%d</BandsCount>\n"
"</GDAL_WMS>\n",
pszEscapedURL, pszTileFormat, pszMinX, pszMaxY,
pszMaxX, pszMinY, nLevelCount - 1, nXSize,
nYSize, pszSRS, nTileWidth, nTileHeight, 3);
CPLDebug("WMS", "Opening TMS :\n%s", osXML.c_str());
CPLFree(pszEscapedURL);
return CPLParseXMLString(osXML);
}
/************************************************************************/
/* GDALWMSDatasetGetConfigFromArcGISJSON() */
/************************************************************************/
static CPLXMLNode *GDALWMSDatasetGetConfigFromArcGISJSON(const char *pszURL,
const char *pszContent)
{
CPLJSONDocument oDoc;
if (!oDoc.LoadMemory(std::string(pszContent)))
return nullptr;
auto oRoot(oDoc.GetRoot());
auto oTileInfo(oRoot["tileInfo"]);
if (!oTileInfo.IsValid())
{
CPLDebug("WMS", "Did not get tileInfo");
return nullptr;
}
int nTileWidth = oTileInfo.GetInteger("cols", -1);
int nTileHeight = oTileInfo.GetInteger("rows", -1);
auto oSpatialReference(oTileInfo["spatialReference"]);
if (!oSpatialReference.IsValid())
{
CPLDebug("WMS", "Did not get spatialReference");
return nullptr;
}
int nWKID = oSpatialReference.GetInteger("wkid", -1);
int nLatestWKID = oSpatialReference.GetInteger("latestWkid", -1);
CPLString osWKT(oSpatialReference.GetString("wkt"));
auto oOrigin(oTileInfo["origin"]);
if (!oOrigin.IsValid())
{
CPLDebug("WMS", "Did not get origin");
return nullptr;
}
double dfMinX =
oOrigin.GetDouble("x", std::numeric_limits<double>::infinity());
double dfMaxY =
oOrigin.GetDouble("y", std::numeric_limits<double>::infinity());
auto oLods(oTileInfo["lods"].ToArray());
if (!oLods.IsValid())
{
CPLDebug("WMS", "Did not get lods");
return nullptr;
}
double dfBaseResolution = 0.0;
for (int i = 0; i < oLods.Size(); i++)
{
if (oLods[i].GetInteger("level", -1) == 0)
{
dfBaseResolution = oLods[i].GetDouble("resolution");
break;
}
}
int nLevelCount = oLods.Size() - 1;
if (nLevelCount < 1)
{
CPLDebug("WMS", "Did not get levels");
return nullptr;
}
if (nTileWidth <= 0)
{
CPLDebug("WMS", "Did not get tile width");
return nullptr;
}
if (nTileHeight <= 0)
{
CPLDebug("WMS", "Did not get tile height");
return nullptr;
}
if (nWKID <= 0 && osWKT.empty())
{
CPLDebug("WMS", "Did not get WKID");
return nullptr;
}
if (dfMinX == std::numeric_limits<double>::infinity())
{
CPLDebug("WMS", "Did not get min x");
return nullptr;
}
if (dfMaxY == std::numeric_limits<double>::infinity())
{
CPLDebug("WMS", "Did not get max y");
return nullptr;
}
if (nLatestWKID > 0)
nWKID = nLatestWKID;
if (nWKID == 102100)
nWKID = 3857;
const char *pszEndURL = strstr(pszURL, "/?f=json");
if (pszEndURL == nullptr)
pszEndURL = strstr(pszURL, "?f=json");
CPLAssert(pszEndURL);
CPLString osURL(pszURL);
osURL.resize(pszEndURL - pszURL);
double dfMaxX = dfMinX + dfBaseResolution * nTileWidth;
double dfMinY = dfMaxY - dfBaseResolution * nTileHeight;
int nTileCountX = 1;
if (fabs(dfMinX - -180) < 1e-4 && fabs(dfMaxY - 90) < 1e-4 &&
fabs(dfMinY - -90) < 1e-4)
{
nTileCountX = 2;
dfMaxX = 180;
}
const int nLevelCountOri = nLevelCount;
while ((double)nTileCountX * nTileWidth * (1 << nLevelCount) > INT_MAX)
nLevelCount--;
while (nLevelCount >= 0 &&
(double)nTileHeight * (1 << nLevelCount) > INT_MAX)
nLevelCount--;
if (nLevelCount != nLevelCountOri)
CPLDebug("WMS",
"Had to limit level count to %d instead of %d to stay within "
"GDAL raster size limits",
nLevelCount, nLevelCountOri);
CPLString osEscapedWKT;
if (nWKID < 0 && !osWKT.empty())
{
OGRSpatialReference oSRS;
oSRS.importFromWkt(osWKT);
const auto poSRSMatch = oSRS.FindBestMatch(100);
if (poSRSMatch)
{
oSRS = *poSRSMatch;
poSRSMatch->Release();
const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
const char *pszCode = oSRS.GetAuthorityCode(nullptr);
if (pszAuthName && EQUAL(pszAuthName, "EPSG") && pszCode)
nWKID = atoi(pszCode);
}
char *pszWKT = nullptr;
oSRS.exportToWkt(&pszWKT);
osWKT = pszWKT;
CPLFree(pszWKT);
char *pszEscaped = CPLEscapeString(osWKT, -1, CPLES_XML);
osEscapedWKT = pszEscaped;
CPLFree(pszEscaped);
}
CPLString osXML = CPLSPrintf(
"<GDAL_WMS>\n"
" <Service name=\"TMS\">\n"
" <ServerUrl>%s/tile/${z}/${y}/${x}</ServerUrl>\n"
" </Service>\n"
" <DataWindow>\n"
" <UpperLeftX>%.8f</UpperLeftX>\n"
" <UpperLeftY>%.8f</UpperLeftY>\n"
" <LowerRightX>%.8f</LowerRightX>\n"
" <LowerRightY>%.8f</LowerRightY>\n"
" <TileLevel>%d</TileLevel>\n"
" <TileCountX>%d</TileCountX>\n"
" <YOrigin>top</YOrigin>\n"
" </DataWindow>\n"
" <Projection>%s</Projection>\n"
" <BlockSizeX>%d</BlockSizeX>\n"
" <BlockSizeY>%d</BlockSizeY>\n"
" <Cache/>\n"
"</GDAL_WMS>\n",
osURL.c_str(), dfMinX, dfMaxY, dfMaxX, dfMinY, nLevelCount, nTileCountX,
nWKID > 0 ? CPLSPrintf("EPSG:%d", nWKID) : osEscapedWKT.c_str(),
nTileWidth, nTileHeight);
CPLDebug("WMS", "Opening TMS :\n%s", osXML.c_str());
return CPLParseXMLString(osXML);
}
/************************************************************************/
/* Identify() */
/************************************************************************/
int GDALWMSDataset::Identify(GDALOpenInfo *poOpenInfo)
{
const char *pszFilename = poOpenInfo->pszFilename;
const char *pabyHeader = (const char *)poOpenInfo->pabyHeader;
if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "<GDAL_WMS>"))
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes >= 10 &&
STARTS_WITH_CI(pabyHeader, "<GDAL_WMS>"))
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes == 0 &&
(STARTS_WITH_CI(pszFilename, "WMS:") ||
CPLString(pszFilename).ifind("SERVICE=WMS") != std::string::npos))
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
(strstr(pabyHeader, "<WMT_MS_Capabilities") != nullptr ||
strstr(pabyHeader, "<WMS_Capabilities") != nullptr ||
strstr(pabyHeader, "<!DOCTYPE WMT_MS_Capabilities") != nullptr))
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<WMS_Tile_Service") != nullptr)
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<TileMap version=\"1.0.0\"") != nullptr)
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<Services") != nullptr &&
strstr(pabyHeader, "<TileMapService version=\"1.0") != nullptr)
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<TileMapService version=\"1.0.0\"") != nullptr)
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "http") &&
(strstr(pszFilename, "/MapServer?f=json") != nullptr ||
strstr(pszFilename, "/MapServer/?f=json") != nullptr ||
strstr(pszFilename, "/ImageServer?f=json") != nullptr ||
strstr(pszFilename, "/ImageServer/?f=json") != nullptr))
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "AGS:"))
{
return TRUE;
}
else if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "IIP:"))
{
return TRUE;
}
else
return FALSE;
}
/************************************************************************/
/* Open() */
/************************************************************************/
GDALDataset *GDALWMSDataset::Open(GDALOpenInfo *poOpenInfo)
{
CPLXMLNode *config = nullptr;
CPLErr ret = CE_None;
const char *pszFilename = poOpenInfo->pszFilename;
const char *pabyHeader = (const char *)poOpenInfo->pabyHeader;
if (!Identify(poOpenInfo))
return nullptr;
if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "<GDAL_WMS>"))
{
config = CPLParseXMLString(pszFilename);
}
else if (poOpenInfo->nHeaderBytes >= 10 &&
STARTS_WITH_CI(pabyHeader, "<GDAL_WMS>"))
{
config = CPLParseXMLFile(pszFilename);
}
else if (poOpenInfo->nHeaderBytes == 0 &&
(STARTS_WITH_CI(pszFilename, "WMS:http") ||
STARTS_WITH_CI(pszFilename, "http")) &&
(strstr(pszFilename, "/MapServer?f=json") != nullptr ||
strstr(pszFilename, "/MapServer/?f=json") != nullptr ||
strstr(pszFilename, "/ImageServer?f=json") != nullptr ||
strstr(pszFilename, "/ImageServer/?f=json") != nullptr))
{
if (STARTS_WITH_CI(pszFilename, "WMS:http"))
pszFilename += 4;
CPLString osURL(pszFilename);
if (strstr(pszFilename, "&pretty=true") == nullptr)
osURL += "&pretty=true";
CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), nullptr);
if (psResult == nullptr)
return nullptr;
if (psResult->pabyData == nullptr)
{
CPLHTTPDestroyResult(psResult);
return nullptr;
}
config = GDALWMSDatasetGetConfigFromArcGISJSON(
osURL, (const char *)psResult->pabyData);
CPLHTTPDestroyResult(psResult);
}
else if (poOpenInfo->nHeaderBytes == 0 &&
(STARTS_WITH_CI(pszFilename, "WMS:") ||
CPLString(pszFilename).ifind("SERVICE=WMS") != std::string::npos))
{
CPLString osLayers = CPLURLGetValue(pszFilename, "LAYERS");
CPLString osRequest = CPLURLGetValue(pszFilename, "REQUEST");
if (!osLayers.empty())
config = GDALWMSDatasetGetConfigFromURL(poOpenInfo);
else if (EQUAL(osRequest, "GetTileService"))
return GDALWMSMetaDataset::DownloadGetTileService(poOpenInfo);
else
return GDALWMSMetaDataset::DownloadGetCapabilities(poOpenInfo);
}
else if (poOpenInfo->nHeaderBytes != 0 &&
(strstr(pabyHeader, "<WMT_MS_Capabilities") != nullptr ||
strstr(pabyHeader, "<WMS_Capabilities") != nullptr ||
strstr(pabyHeader, "<!DOCTYPE WMT_MS_Capabilities") != nullptr))
{
CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
if (psXML == nullptr)
return nullptr;
GDALDataset *poRet = GDALWMSMetaDataset::AnalyzeGetCapabilities(psXML);
CPLDestroyXMLNode(psXML);
return poRet;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<WMS_Tile_Service") != nullptr)
{
CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
if (psXML == nullptr)
return nullptr;
GDALDataset *poRet =
GDALWMSMetaDataset::AnalyzeGetTileService(psXML, poOpenInfo);
CPLDestroyXMLNode(psXML);
return poRet;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<TileMap version=\"1.0.0\"") != nullptr)
{
CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
if (psXML == nullptr)
return nullptr;
config = GDALWMSDatasetGetConfigFromTileMap(psXML);
CPLDestroyXMLNode(psXML);
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<Services") != nullptr &&
strstr(pabyHeader, "<TileMapService version=\"1.0") != nullptr)
{
CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
if (psXML == nullptr)
return nullptr;
CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=Services");
GDALDataset *poRet = nullptr;
if (psRoot)
{
CPLXMLNode *psTileMapService =
CPLGetXMLNode(psRoot, "TileMapService");
if (psTileMapService)
{
const char *pszHref =
CPLGetXMLValue(psTileMapService, "href", nullptr);
if (pszHref)
{
poRet = (GDALDataset *)GDALOpen(pszHref, GA_ReadOnly);
}
}
}
CPLDestroyXMLNode(psXML);
return poRet;
}
else if (poOpenInfo->nHeaderBytes != 0 &&
strstr(pabyHeader, "<TileMapService version=\"1.0.0\"") != nullptr)
{
CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
if (psXML == nullptr)
return nullptr;
GDALDataset *poRet = GDALWMSMetaDataset::AnalyzeTileMapService(psXML);
CPLDestroyXMLNode(psXML);
return poRet;
}
else if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "AGS:"))
{
return nullptr;
}
else if (poOpenInfo->nHeaderBytes == 0 &&
STARTS_WITH_CI(pszFilename, "IIP:"))
{
CPLString osURL(pszFilename + 4);
osURL += "&obj=Basic-Info";
CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), nullptr);
if (psResult == nullptr)
return nullptr;
if (psResult->pabyData == nullptr)
{
CPLHTTPDestroyResult(psResult);
return nullptr;
}
int nXSize, nYSize;
const char *pszMaxSize =
strstr((const char *)psResult->pabyData, "Max-size:");
const char *pszResolutionNumber =
strstr((const char *)psResult->pabyData, "Resolution-number:");
if (pszMaxSize &&
sscanf(pszMaxSize + strlen("Max-size:"), "%d %d", &nXSize,
&nYSize) == 2 &&
pszResolutionNumber)
{
int nResolutions =
atoi(pszResolutionNumber + strlen("Resolution-number:"));
char *pszEscapedURL =
CPLEscapeString(pszFilename + 4, -1, CPLES_XML);
CPLString osXML =
CPLSPrintf("<GDAL_WMS>"
" <Service name=\"IIP\">"
" <ServerUrl>%s</ServerUrl>"
" </Service>"
" <DataWindow>"
" <SizeX>%d</SizeX>"
" <SizeY>%d</SizeY>"
" <TileLevel>%d</TileLevel>"
" </DataWindow>"
" <BlockSizeX>256</BlockSizeX>"
" <BlockSizeY>256</BlockSizeY>"
" <BandsCount>3</BandsCount>"
" <Cache />"
"</GDAL_WMS>",
pszEscapedURL, nXSize, nYSize, nResolutions - 1);
config = CPLParseXMLString(osXML);
CPLFree(pszEscapedURL);
}
CPLHTTPDestroyResult(psResult);
}
else
return nullptr;
if (config == nullptr)
return nullptr;
/* -------------------------------------------------------------------- */
/* Confirm the requested access is supported. */
/* -------------------------------------------------------------------- */
if (poOpenInfo->eAccess == GA_Update)
{
CPLDestroyXMLNode(config);
CPLError(CE_Failure, CPLE_NotSupported,
"The WMS poDriver does not support update access to existing"
" datasets.\n");
return nullptr;
}
GDALWMSDataset *ds = new GDALWMSDataset();
ret = ds->Initialize(config, poOpenInfo->papszOpenOptions);
if (ret != CE_None)
{
delete ds;
ds = nullptr;
}
CPLDestroyXMLNode(config);
/* -------------------------------------------------------------------- */
/* Initialize any PAM information. */
/* -------------------------------------------------------------------- */
if (ds != nullptr)
{
if (poOpenInfo->pszFilename && poOpenInfo->pszFilename[0] == '<')
{
ds->nPamFlags = GPF_DISABLED;
}
else
{
ds->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
ds->SetDescription(poOpenInfo->pszFilename);
ds->TryLoadXML();
}
}
return ds;
}
/************************************************************************/
/* GetServerConfig() */
/************************************************************************/
const char *GDALWMSDataset::GetServerConfig(const char *URI,
char **papszHTTPOptions)
{
CPLMutexHolder oHolder(&cfgmtx);
// Might have it cached already
if (cfg.end() != cfg.find(URI))
return cfg.find(URI)->second;
CPLHTTPResult *psResult = CPLHTTPFetch(URI, papszHTTPOptions);
if (nullptr == psResult)
return nullptr;
// Capture the result in buffer, get rid of http result
if ((psResult->nStatus == 0) && (nullptr != psResult->pabyData) &&
('\0' != psResult->pabyData[0]))
cfg.insert(make_pair(
URI, static_cast<CPLString>(
reinterpret_cast<const char *>(psResult->pabyData))));
CPLHTTPDestroyResult(psResult);
if (cfg.end() != cfg.find(URI))
return cfg.find(URI)->second;
else
return nullptr;
}
// Empties the server configuration cache and removes the mutex
void GDALWMSDataset::ClearConfigCache()
{
// Obviously not thread safe, should only be called when no WMS files are
// being opened
cfg.clear();
DestroyCfgMutex();
}
void GDALWMSDataset::DestroyCfgMutex()
{
if (cfgmtx)
CPLDestroyMutex(cfgmtx);
cfgmtx = nullptr;
}
/************************************************************************/
/* CreateCopy() */
/************************************************************************/
GDALDataset *GDALWMSDataset::CreateCopy(const char *pszFilename,
GDALDataset *poSrcDS,
CPL_UNUSED int bStrict,
CPL_UNUSED char **papszOptions,
CPL_UNUSED GDALProgressFunc pfnProgress,
CPL_UNUSED void *pProgressData)
{
if (poSrcDS->GetDriver() == nullptr ||
!EQUAL(poSrcDS->GetDriver()->GetDescription(), "WMS"))
{
CPLError(CE_Failure, CPLE_NotSupported,
"Source dataset must be a WMS dataset");
return nullptr;
}
const char *pszXML = poSrcDS->GetMetadataItem("XML", "WMS");
if (pszXML == nullptr)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Cannot get XML definition of source WMS dataset");
return nullptr;
}
VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
if (fp == nullptr)
return nullptr;
VSIFWriteL(pszXML, 1, strlen(pszXML), fp);
VSIFCloseL(fp);
GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
return Open(&oOpenInfo);
}
void WMSDeregister(CPL_UNUSED GDALDriver *d)
{
GDALWMSDataset::DestroyCfgMutex();
}
// Define a minidriver factory type, create one and register it
#define RegisterMinidriver(name) \
class WMSMiniDriverFactory_##name : public WMSMiniDriverFactory \
{ \
public: \
WMSMiniDriverFactory_##name() \
{ \
m_name = CPLString(#name); \
} \
virtual ~WMSMiniDriverFactory_##name() \
{ \
} \
virtual WMSMiniDriver *New() const override \
{ \
return new WMSMiniDriver_##name; \
} \
}; \
WMSRegisterMiniDriverFactory(new WMSMiniDriverFactory_##name());
/************************************************************************/
/* GDALRegister_WMS() */
/************************************************************************/
//
// Do not define any open options here!
// Doing so will enable checking the open options, which will generate warnings
// for undeclared options which may be handled by individual minidrivers
//
void GDALRegister_WMS()
{
if (GDALGetDriverByName("WMS") != nullptr)
return;
// Register all minidrivers here
RegisterMinidriver(WMS);
RegisterMinidriver(TileService);
RegisterMinidriver(WorldWind);
RegisterMinidriver(TMS);
RegisterMinidriver(TiledWMS);
RegisterMinidriver(VirtualEarth);
RegisterMinidriver(AGS);
RegisterMinidriver(IIP);
RegisterMinidriver(MRF);
RegisterMinidriver(OGCAPIMaps);
RegisterMinidriver(OGCAPICoverage);
GDALDriver *poDriver = new GDALDriver();
poDriver->SetDescription("WMS");
poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "OGC Web Map Service");
poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/wms.html");
poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
poDriver->pfnOpen = GDALWMSDataset::Open;
poDriver->pfnIdentify = GDALWMSDataset::Identify;
poDriver->pfnUnloadDriver = WMSDeregister;
poDriver->pfnCreateCopy = GDALWMSDataset::CreateCopy;
GetGDALDriverManager()->RegisterDriver(poDriver);
}