Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: WMS Client Driver
4 : * Purpose: Implementation of Dataset and RasterBand classes for WMS
5 : * and other similar services.
6 : * Author: Adam Nowacki, nowak@xpam.de
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2007, Adam Nowacki
10 : * Copyright (c) 2009-2014, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "gdal_frmts.h"
16 : #include "wmsdriver.h"
17 : #include "wmsmetadataset.h"
18 :
19 : #include "minidriver_wms.h"
20 : #include "minidriver_tileservice.h"
21 : #include "minidriver_worldwind.h"
22 : #include "minidriver_tms.h"
23 : #include "minidriver_tiled_wms.h"
24 : #include "minidriver_virtualearth.h"
25 : #include "minidriver_arcgis_server.h"
26 : #include "minidriver_iiifimage.h"
27 : #include "minidriver_iip.h"
28 : #include "minidriver_mrf.h"
29 : #include "minidriver_ogcapimaps.h"
30 : #include "minidriver_ogcapicoverage.h"
31 : #include "wmsdrivercore.h"
32 :
33 : #include "cpl_json.h"
34 :
35 : #include <limits>
36 : #include <utility>
37 : #include <algorithm>
38 :
39 : //
40 : // A static map holding seen server GetTileService responses, per process
41 : // It makes opening and reopening rasters from the same server faster
42 : //
43 : GDALWMSDataset::StringMap_t GDALWMSDataset::cfg;
44 : CPLMutex *GDALWMSDataset::cfgmtx = nullptr;
45 :
46 : WMSMiniDriver::~WMSMiniDriver() = default;
47 : WMSMiniDriverFactory::~WMSMiniDriverFactory() = default;
48 : GDALWMSCacheImpl::~GDALWMSCacheImpl() = default;
49 :
50 : /************************************************************************/
51 : /* GDALWMSDatasetGetConfigFromURL() */
52 : /************************************************************************/
53 :
54 1 : static CPLXMLNode *GDALWMSDatasetGetConfigFromURL(GDALOpenInfo *poOpenInfo)
55 : {
56 1 : const char *pszBaseURL = poOpenInfo->pszFilename;
57 1 : if (STARTS_WITH_CI(pszBaseURL, "WMS:"))
58 1 : pszBaseURL += strlen("WMS:");
59 :
60 2 : const CPLString osLayer = CPLURLGetValue(pszBaseURL, "LAYERS");
61 2 : CPLString osVersion = CPLURLGetValue(pszBaseURL, "VERSION");
62 2 : CPLString osSRS = CPLURLGetValue(pszBaseURL, "SRS");
63 2 : CPLString osCRS = CPLURLGetValue(pszBaseURL, "CRS");
64 2 : CPLString osBBOX = CPLURLGetValue(pszBaseURL, "BBOX");
65 2 : CPLString osFormat = CPLURLGetValue(pszBaseURL, "FORMAT");
66 2 : const CPLString osTransparent = CPLURLGetValue(pszBaseURL, "TRANSPARENT");
67 :
68 : /* GDAL specific extensions to alter the default settings */
69 : const CPLString osOverviewCount =
70 2 : CPLURLGetValue(pszBaseURL, "OVERVIEWCOUNT");
71 2 : const CPLString osTileSize = CPLURLGetValue(pszBaseURL, "TILESIZE");
72 : const CPLString osMinResolution =
73 2 : CPLURLGetValue(pszBaseURL, "MINRESOLUTION");
74 2 : CPLString osBBOXOrder = CPLURLGetValue(pszBaseURL, "BBOXORDER");
75 :
76 2 : CPLString osBaseURL = pszBaseURL;
77 : /* Remove all keywords to get base URL */
78 :
79 1 : if (osBBOXOrder.empty() && !osCRS.empty() &&
80 0 : VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
81 : {
82 0 : OGRSpatialReference oSRS;
83 0 : oSRS.SetFromUserInput(
84 : osCRS, OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
85 0 : oSRS.AutoIdentifyEPSG();
86 0 : if (oSRS.EPSGTreatsAsLatLong() || oSRS.EPSGTreatsAsNorthingEasting())
87 : {
88 0 : osBBOXOrder = "yxYX";
89 : }
90 : }
91 :
92 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "VERSION", nullptr);
93 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "REQUEST", nullptr);
94 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "LAYERS", nullptr);
95 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "SRS", nullptr);
96 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "CRS", nullptr);
97 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "BBOX", nullptr);
98 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "FORMAT", nullptr);
99 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "TRANSPARENT", nullptr);
100 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "STYLES", nullptr);
101 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "WIDTH", nullptr);
102 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "HEIGHT", nullptr);
103 :
104 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "OVERVIEWCOUNT", nullptr);
105 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "TILESIZE", nullptr);
106 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "MINRESOLUTION", nullptr);
107 1 : osBaseURL = CPLURLAddKVP(osBaseURL, "BBOXORDER", nullptr);
108 :
109 1 : if (!osBaseURL.empty() && osBaseURL.back() == '&')
110 1 : osBaseURL.pop_back();
111 :
112 1 : if (osVersion.empty())
113 0 : osVersion = "1.1.1";
114 :
115 2 : CPLString osSRSTag;
116 2 : CPLString osSRSValue;
117 1 : if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
118 : {
119 0 : if (!osSRS.empty())
120 : {
121 0 : CPLError(CE_Warning, CPLE_AppDefined,
122 : "WMS version 1.3 and above expects CRS however SRS was "
123 : "set instead.");
124 : }
125 0 : osSRSValue = std::move(osCRS);
126 0 : osSRSTag = "CRS";
127 : }
128 : else
129 : {
130 1 : if (!osCRS.empty())
131 : {
132 0 : CPLError(CE_Warning, CPLE_AppDefined,
133 : "WMS version 1.1.1 and below expects SRS however CRS was "
134 : "set instead.");
135 : }
136 1 : osSRSValue = std::move(osSRS);
137 1 : osSRSTag = "SRS";
138 : }
139 :
140 1 : if (osSRSValue.empty())
141 : {
142 0 : osSRSValue = "EPSG:4326";
143 :
144 0 : if (osBBOX.empty())
145 : {
146 0 : if (osBBOXOrder.compare("yxYX") == 0)
147 0 : osBBOX = "-90,-180,90,180";
148 : else
149 0 : osBBOX = "-180,-90,180,90";
150 : }
151 : }
152 : else
153 : {
154 1 : if (osBBOX.empty())
155 : {
156 0 : OGRSpatialReference oSRS;
157 0 : oSRS.SetFromUserInput(osSRSValue);
158 0 : oSRS.AutoIdentifyEPSG();
159 :
160 : double dfWestLongitudeDeg, dfSouthLatitudeDeg, dfEastLongitudeDeg,
161 : dfNorthLatitudeDeg;
162 0 : if (!oSRS.GetAreaOfUse(&dfWestLongitudeDeg, &dfSouthLatitudeDeg,
163 : &dfEastLongitudeDeg, &dfNorthLatitudeDeg,
164 : nullptr))
165 : {
166 0 : CPLError(CE_Failure, CPLE_AppDefined,
167 : "Failed retrieving a default bounding box for the "
168 : "requested SRS");
169 0 : return nullptr;
170 : }
171 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
172 : OGRCreateCoordinateTransformation(
173 0 : OGRSpatialReference::GetWGS84SRS(), &oSRS));
174 0 : if (!poCT)
175 : {
176 0 : CPLError(CE_Failure, CPLE_AppDefined,
177 : "Failed creating a coordinate transformation for the "
178 : "requested SRS");
179 0 : return nullptr;
180 : }
181 0 : if (!poCT->Transform(1, &dfWestLongitudeDeg, &dfNorthLatitudeDeg) ||
182 0 : !poCT->Transform(1, &dfEastLongitudeDeg, &dfSouthLatitudeDeg))
183 : {
184 0 : CPLError(
185 : CE_Failure, CPLE_AppDefined,
186 : "Failed transforming coordinates to the requested SRS");
187 0 : return nullptr;
188 : }
189 : const double dfMaxX =
190 0 : std::max(dfWestLongitudeDeg, dfEastLongitudeDeg);
191 : const double dfMinX =
192 0 : std::min(dfWestLongitudeDeg, dfEastLongitudeDeg);
193 : const double dfMaxY =
194 0 : std::max(dfNorthLatitudeDeg, dfSouthLatitudeDeg);
195 : const double dfMinY =
196 0 : std::min(dfNorthLatitudeDeg, dfSouthLatitudeDeg);
197 0 : if (osBBOXOrder.compare("yxYX") == 0)
198 : {
199 : osBBOX = CPLSPrintf("%lf,%lf,%lf,%lf", dfMinY, dfMinX, dfMaxY,
200 0 : dfMaxX);
201 : }
202 : else
203 : {
204 : osBBOX = CPLSPrintf("%lf,%lf,%lf,%lf", dfMinX, dfMinY, dfMaxX,
205 0 : dfMaxY);
206 : }
207 : }
208 : }
209 :
210 1 : char **papszTokens = CSLTokenizeStringComplex(osBBOX, ",", 0, 0);
211 1 : if (CSLCount(papszTokens) != 4)
212 : {
213 0 : CSLDestroy(papszTokens);
214 0 : return nullptr;
215 : }
216 1 : const char *pszMinX = papszTokens[0];
217 1 : const char *pszMinY = papszTokens[1];
218 1 : const char *pszMaxX = papszTokens[2];
219 1 : const char *pszMaxY = papszTokens[3];
220 :
221 1 : if (osBBOXOrder.compare("yxYX") == 0)
222 : {
223 0 : std::swap(pszMinX, pszMinY);
224 0 : std::swap(pszMaxX, pszMaxY);
225 : }
226 :
227 1 : double dfMinX = CPLAtofM(pszMinX);
228 1 : double dfMinY = CPLAtofM(pszMinY);
229 1 : double dfMaxX = CPLAtofM(pszMaxX);
230 1 : double dfMaxY = CPLAtofM(pszMaxY);
231 :
232 1 : if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
233 : {
234 0 : CSLDestroy(papszTokens);
235 0 : return nullptr;
236 : }
237 :
238 1 : int nTileSize = atoi(osTileSize);
239 1 : if (nTileSize <= 128 || nTileSize > 2048)
240 1 : nTileSize = 1024;
241 :
242 : int nXSize, nYSize;
243 : double dXSize, dYSize;
244 :
245 1 : int nOverviewCount = (osOverviewCount.size()) ? atoi(osOverviewCount) : 20;
246 :
247 1 : if (!osMinResolution.empty())
248 : {
249 0 : double dfMinResolution = CPLAtofM(osMinResolution);
250 :
251 0 : while (nOverviewCount > 20)
252 : {
253 0 : nOverviewCount--;
254 0 : dfMinResolution *= 2;
255 : }
256 :
257 : // Determine a suitable size that doesn't overflow max int.
258 0 : dXSize = ((dfMaxX - dfMinX) / dfMinResolution + 0.5);
259 0 : dYSize = ((dfMaxY - dfMinY) / dfMinResolution + 0.5);
260 :
261 0 : while (dXSize > (std::numeric_limits<int>::max)() ||
262 0 : dYSize > (std::numeric_limits<int>::max)())
263 : {
264 0 : dfMinResolution *= 2;
265 :
266 0 : dXSize = ((dfMaxX - dfMinX) / dfMinResolution + 0.5);
267 0 : dYSize = ((dfMaxY - dfMinY) / dfMinResolution + 0.5);
268 : }
269 : }
270 : else
271 : {
272 1 : double dfRatio = (dfMaxX - dfMinX) / (dfMaxY - dfMinY);
273 1 : if (dfRatio > 1)
274 : {
275 1 : dXSize = nTileSize;
276 1 : dYSize = dXSize / dfRatio;
277 : }
278 : else
279 : {
280 0 : dYSize = nTileSize;
281 0 : dXSize = dYSize * dfRatio;
282 : }
283 :
284 1 : if (nOverviewCount < 0 || nOverviewCount > 20)
285 0 : nOverviewCount = 20;
286 :
287 1 : dXSize = dXSize * (1 << nOverviewCount);
288 1 : dYSize = dYSize * (1 << nOverviewCount);
289 :
290 : // Determine a suitable size that doesn't overflow max int.
291 2 : while (dXSize > (std::numeric_limits<int>::max)() ||
292 1 : dYSize > (std::numeric_limits<int>::max)())
293 : {
294 0 : dXSize /= 2;
295 0 : dYSize /= 2;
296 : }
297 : }
298 :
299 1 : nXSize = static_cast<int>(dXSize);
300 1 : nYSize = static_cast<int>(dYSize);
301 :
302 1 : bool bTransparent = !osTransparent.empty() && CPLTestBool(osTransparent);
303 :
304 1 : if (osFormat.empty())
305 : {
306 1 : if (!bTransparent)
307 : {
308 1 : osFormat = "image/jpeg";
309 : }
310 : else
311 : {
312 0 : osFormat = "image/png";
313 : }
314 : }
315 :
316 1 : char *pszEscapedURL = CPLEscapeString(osBaseURL.c_str(), -1, CPLES_XML);
317 1 : char *pszEscapedLayerXML = CPLEscapeString(osLayer.c_str(), -1, CPLES_XML);
318 :
319 : CPLString osXML = CPLSPrintf(
320 : "<GDAL_WMS>\n"
321 : " <Service name=\"WMS\">\n"
322 : " <Version>%s</Version>\n"
323 : " <ServerUrl>%s</ServerUrl>\n"
324 : " <Layers>%s</Layers>\n"
325 : " <%s>%s</%s>\n"
326 : " <ImageFormat>%s</ImageFormat>\n"
327 : " <Transparent>%s</Transparent>\n"
328 : " <BBoxOrder>%s</BBoxOrder>\n"
329 : " </Service>\n"
330 : " <DataWindow>\n"
331 : " <UpperLeftX>%s</UpperLeftX>\n"
332 : " <UpperLeftY>%s</UpperLeftY>\n"
333 : " <LowerRightX>%s</LowerRightX>\n"
334 : " <LowerRightY>%s</LowerRightY>\n"
335 : " <SizeX>%d</SizeX>\n"
336 : " <SizeY>%d</SizeY>\n"
337 : " </DataWindow>\n"
338 : " <BandsCount>%d</BandsCount>\n"
339 : " <BlockSizeX>%d</BlockSizeX>\n"
340 : " <BlockSizeY>%d</BlockSizeY>\n"
341 : " <OverviewCount>%d</OverviewCount>\n"
342 : "</GDAL_WMS>\n",
343 : osVersion.c_str(), pszEscapedURL, pszEscapedLayerXML, osSRSTag.c_str(),
344 : osSRSValue.c_str(), osSRSTag.c_str(), osFormat.c_str(),
345 : (bTransparent) ? "TRUE" : "FALSE",
346 0 : (osBBOXOrder.size()) ? osBBOXOrder.c_str() : "xyXY", pszMinX, pszMaxY,
347 : pszMaxX, pszMinY, nXSize, nYSize, (bTransparent) ? 4 : 3, nTileSize,
348 2 : nTileSize, nOverviewCount);
349 :
350 1 : CPLFree(pszEscapedURL);
351 1 : CPLFree(pszEscapedLayerXML);
352 :
353 1 : CSLDestroy(papszTokens);
354 :
355 1 : CPLDebug("WMS", "Opening WMS :\n%s", osXML.c_str());
356 :
357 1 : return CPLParseXMLString(osXML);
358 : }
359 :
360 : /************************************************************************/
361 : /* GDALWMSDatasetGetConfigFromTileMap() */
362 : /************************************************************************/
363 :
364 0 : static CPLXMLNode *GDALWMSDatasetGetConfigFromTileMap(CPLXMLNode *psXML)
365 : {
366 0 : CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TileMap");
367 0 : if (psRoot == nullptr)
368 0 : return nullptr;
369 :
370 0 : CPLXMLNode *psTileSets = CPLGetXMLNode(psRoot, "TileSets");
371 0 : if (psTileSets == nullptr)
372 0 : return nullptr;
373 :
374 0 : const char *pszURL = CPLGetXMLValue(psRoot, "tilemapservice", nullptr);
375 :
376 0 : int bCanChangeURL = TRUE;
377 :
378 0 : CPLString osURL;
379 0 : if (pszURL)
380 : {
381 0 : osURL = pszURL;
382 : /* Special hack for
383 : * http://tilecache.osgeo.org/wms-c/Basic.py/1.0.0/basic/ */
384 0 : if (strlen(pszURL) > 10 &&
385 0 : STARTS_WITH(pszURL,
386 0 : "http://tilecache.osgeo.org/wms-c/Basic.py/1.0.0/") &&
387 0 : strcmp(pszURL + strlen(pszURL) - strlen("1.0.0/"), "1.0.0/") == 0)
388 : {
389 0 : osURL.resize(strlen(pszURL) - strlen("1.0.0/"));
390 0 : bCanChangeURL = FALSE;
391 : }
392 0 : osURL += "${z}/${x}/${y}.${format}";
393 : }
394 :
395 0 : const char *pszSRS = CPLGetXMLValue(psRoot, "SRS", nullptr);
396 0 : if (pszSRS == nullptr)
397 0 : return nullptr;
398 :
399 0 : CPLXMLNode *psBoundingBox = CPLGetXMLNode(psRoot, "BoundingBox");
400 0 : if (psBoundingBox == nullptr)
401 0 : return nullptr;
402 :
403 0 : const char *pszMinX = CPLGetXMLValue(psBoundingBox, "minx", nullptr);
404 0 : const char *pszMinY = CPLGetXMLValue(psBoundingBox, "miny", nullptr);
405 0 : const char *pszMaxX = CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
406 0 : const char *pszMaxY = CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
407 0 : if (pszMinX == nullptr || pszMinY == nullptr || pszMaxX == nullptr ||
408 : pszMaxY == nullptr)
409 0 : return nullptr;
410 :
411 0 : double dfMinX = CPLAtofM(pszMinX);
412 0 : double dfMinY = CPLAtofM(pszMinY);
413 0 : double dfMaxX = CPLAtofM(pszMaxX);
414 0 : double dfMaxY = CPLAtofM(pszMaxY);
415 0 : if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
416 0 : return nullptr;
417 :
418 0 : CPLXMLNode *psTileFormat = CPLGetXMLNode(psRoot, "TileFormat");
419 0 : if (psTileFormat == nullptr)
420 0 : return nullptr;
421 :
422 0 : const char *pszTileWidth = CPLGetXMLValue(psTileFormat, "width", nullptr);
423 0 : const char *pszTileHeight = CPLGetXMLValue(psTileFormat, "height", nullptr);
424 : const char *pszTileFormat =
425 0 : CPLGetXMLValue(psTileFormat, "extension", nullptr);
426 0 : if (pszTileWidth == nullptr || pszTileHeight == nullptr ||
427 : pszTileFormat == nullptr)
428 0 : return nullptr;
429 :
430 0 : int nTileWidth = atoi(pszTileWidth);
431 0 : int nTileHeight = atoi(pszTileHeight);
432 0 : if (nTileWidth < 128 || nTileHeight < 128)
433 0 : return nullptr;
434 :
435 0 : CPLXMLNode *psIter = psTileSets->psChild;
436 0 : int nLevelCount = 0;
437 0 : double dfPixelSize = 0;
438 0 : for (; psIter != nullptr; psIter = psIter->psNext)
439 : {
440 0 : if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
441 : {
442 0 : const char *pszOrder = CPLGetXMLValue(psIter, "order", nullptr);
443 0 : if (pszOrder == nullptr)
444 : {
445 0 : CPLDebug("WMS", "Cannot find order attribute");
446 0 : return nullptr;
447 : }
448 0 : if (atoi(pszOrder) != nLevelCount)
449 : {
450 0 : CPLDebug("WMS", "Expected order=%d, got %s", nLevelCount,
451 : pszOrder);
452 0 : return nullptr;
453 : }
454 :
455 0 : const char *pszHref = CPLGetXMLValue(psIter, "href", nullptr);
456 0 : if (nLevelCount == 0 && pszHref != nullptr)
457 : {
458 0 : if (bCanChangeURL && strlen(pszHref) > 10 &&
459 0 : strcmp(pszHref + strlen(pszHref) - strlen("/0"), "/0") == 0)
460 : {
461 0 : osURL = pszHref;
462 0 : osURL.resize(strlen(pszHref) - strlen("/0"));
463 0 : osURL += "/${z}/${x}/${y}.${format}";
464 : }
465 : }
466 : const char *pszUnitsPerPixel =
467 0 : CPLGetXMLValue(psIter, "units-per-pixel", nullptr);
468 0 : if (pszUnitsPerPixel == nullptr)
469 0 : return nullptr;
470 0 : dfPixelSize = CPLAtofM(pszUnitsPerPixel);
471 :
472 0 : nLevelCount++;
473 : }
474 : }
475 :
476 0 : if (nLevelCount == 0 || osURL.empty())
477 0 : return nullptr;
478 :
479 0 : int nXSize = 0;
480 0 : int nYSize = 0;
481 :
482 0 : while (nLevelCount > 0)
483 : {
484 0 : double dfXSizeBig = (dfMaxX - dfMinX) / dfPixelSize + 0.5;
485 0 : double dfYSizeBig = (dfMaxY - dfMinY) / dfPixelSize + 0.5;
486 0 : if (dfXSizeBig < INT_MAX && dfYSizeBig < INT_MAX)
487 : {
488 0 : nXSize = static_cast<int>(dfXSizeBig);
489 0 : nYSize = static_cast<int>(dfYSizeBig);
490 0 : break;
491 : }
492 0 : CPLDebug(
493 : "WMS",
494 : "Dropping one overview level so raster size fits into 32bit...");
495 0 : dfPixelSize *= 2;
496 0 : nLevelCount--;
497 : }
498 :
499 0 : char *pszEscapedURL = CPLEscapeString(osURL.c_str(), -1, CPLES_XML);
500 :
501 : CPLString osXML = CPLSPrintf("<GDAL_WMS>\n"
502 : " <Service name=\"TMS\">\n"
503 : " <ServerUrl>%s</ServerUrl>\n"
504 : " <Format>%s</Format>\n"
505 : " </Service>\n"
506 : " <DataWindow>\n"
507 : " <UpperLeftX>%s</UpperLeftX>\n"
508 : " <UpperLeftY>%s</UpperLeftY>\n"
509 : " <LowerRightX>%s</LowerRightX>\n"
510 : " <LowerRightY>%s</LowerRightY>\n"
511 : " <TileLevel>%d</TileLevel>\n"
512 : " <SizeX>%d</SizeX>\n"
513 : " <SizeY>%d</SizeY>\n"
514 : " </DataWindow>\n"
515 : " <Projection>%s</Projection>\n"
516 : " <BlockSizeX>%d</BlockSizeX>\n"
517 : " <BlockSizeY>%d</BlockSizeY>\n"
518 : " <BandsCount>%d</BandsCount>\n"
519 : "</GDAL_WMS>\n",
520 : pszEscapedURL, pszTileFormat, pszMinX, pszMaxY,
521 : pszMaxX, pszMinY, nLevelCount - 1, nXSize,
522 0 : nYSize, pszSRS, nTileWidth, nTileHeight, 3);
523 0 : CPLDebug("WMS", "Opening TMS :\n%s", osXML.c_str());
524 :
525 0 : CPLFree(pszEscapedURL);
526 :
527 0 : return CPLParseXMLString(osXML);
528 : }
529 :
530 : /************************************************************************/
531 : /* GDALWMSDatasetGetConfigFromArcGISJSON() */
532 : /************************************************************************/
533 :
534 1 : static CPLXMLNode *GDALWMSDatasetGetConfigFromArcGISJSON(const char *pszURL,
535 : const char *pszContent)
536 : {
537 2 : CPLJSONDocument oDoc;
538 1 : if (!oDoc.LoadMemory(std::string(pszContent)))
539 0 : return nullptr;
540 2 : auto oRoot(oDoc.GetRoot());
541 3 : auto oTileInfo(oRoot["tileInfo"]);
542 1 : if (!oTileInfo.IsValid())
543 : {
544 0 : CPLDebug("WMS", "Did not get tileInfo");
545 0 : return nullptr;
546 : }
547 1 : int nTileWidth = oTileInfo.GetInteger("cols", -1);
548 1 : int nTileHeight = oTileInfo.GetInteger("rows", -1);
549 :
550 3 : auto oSpatialReference(oTileInfo["spatialReference"]);
551 1 : if (!oSpatialReference.IsValid())
552 : {
553 0 : CPLDebug("WMS", "Did not get spatialReference");
554 0 : return nullptr;
555 : }
556 1 : int nWKID = oSpatialReference.GetInteger("wkid", -1);
557 1 : int nLatestWKID = oSpatialReference.GetInteger("latestWkid", -1);
558 3 : CPLString osWKT(oSpatialReference.GetString("wkt"));
559 :
560 3 : auto oOrigin(oTileInfo["origin"]);
561 1 : if (!oOrigin.IsValid())
562 : {
563 0 : CPLDebug("WMS", "Did not get origin");
564 0 : return nullptr;
565 : }
566 : double dfMinX =
567 1 : oOrigin.GetDouble("x", std::numeric_limits<double>::infinity());
568 : double dfMaxY =
569 1 : oOrigin.GetDouble("y", std::numeric_limits<double>::infinity());
570 :
571 3 : auto oLods(oTileInfo["lods"].ToArray());
572 1 : if (!oLods.IsValid())
573 : {
574 0 : CPLDebug("WMS", "Did not get lods");
575 0 : return nullptr;
576 : }
577 1 : double dfBaseResolution = 0.0;
578 1 : for (int i = 0; i < oLods.Size(); i++)
579 : {
580 1 : if (oLods[i].GetInteger("level", -1) == 0)
581 : {
582 1 : dfBaseResolution = oLods[i].GetDouble("resolution");
583 1 : break;
584 : }
585 : }
586 :
587 1 : int nLevelCount = oLods.Size() - 1;
588 1 : if (nLevelCount < 1)
589 : {
590 0 : CPLDebug("WMS", "Did not get levels");
591 0 : return nullptr;
592 : }
593 :
594 1 : if (nTileWidth <= 0)
595 : {
596 0 : CPLDebug("WMS", "Did not get tile width");
597 0 : return nullptr;
598 : }
599 1 : if (nTileHeight <= 0)
600 : {
601 0 : CPLDebug("WMS", "Did not get tile height");
602 0 : return nullptr;
603 : }
604 1 : if (nWKID <= 0 && osWKT.empty())
605 : {
606 0 : CPLDebug("WMS", "Did not get WKID");
607 0 : return nullptr;
608 : }
609 1 : if (dfMinX == std::numeric_limits<double>::infinity())
610 : {
611 0 : CPLDebug("WMS", "Did not get min x");
612 0 : return nullptr;
613 : }
614 1 : if (dfMaxY == std::numeric_limits<double>::infinity())
615 : {
616 0 : CPLDebug("WMS", "Did not get max y");
617 0 : return nullptr;
618 : }
619 :
620 1 : if (nLatestWKID > 0)
621 1 : nWKID = nLatestWKID;
622 :
623 1 : if (nWKID == 102100)
624 0 : nWKID = 3857;
625 :
626 1 : const char *pszEndURL = strstr(pszURL, "/?f=json");
627 1 : if (pszEndURL == nullptr)
628 1 : pszEndURL = strstr(pszURL, "?f=json");
629 1 : CPLAssert(pszEndURL);
630 2 : CPLString osURL(pszURL);
631 1 : osURL.resize(pszEndURL - pszURL);
632 :
633 1 : double dfMaxX = dfMinX + dfBaseResolution * nTileWidth;
634 1 : double dfMinY = dfMaxY - dfBaseResolution * nTileHeight;
635 :
636 1 : int nTileCountX = 1;
637 1 : if (fabs(dfMinX - -180) < 1e-4 && fabs(dfMaxY - 90) < 1e-4 &&
638 0 : fabs(dfMinY - -90) < 1e-4)
639 : {
640 0 : nTileCountX = 2;
641 0 : dfMaxX = 180;
642 : }
643 :
644 1 : const int nLevelCountOri = nLevelCount;
645 2 : while (static_cast<double>(nTileCountX) * nTileWidth * (1 << nLevelCount) >
646 : INT_MAX)
647 1 : nLevelCount--;
648 1 : while (nLevelCount >= 0 &&
649 1 : static_cast<double>(nTileHeight) * (1 << nLevelCount) > INT_MAX)
650 0 : nLevelCount--;
651 1 : if (nLevelCount != nLevelCountOri)
652 1 : CPLDebug("WMS",
653 : "Had to limit level count to %d instead of %d to stay within "
654 : "GDAL raster size limits",
655 : nLevelCount, nLevelCountOri);
656 :
657 2 : CPLString osEscapedWKT;
658 1 : if (nWKID < 0 && !osWKT.empty())
659 : {
660 0 : OGRSpatialReference oSRS;
661 0 : oSRS.importFromWkt(osWKT);
662 :
663 0 : const auto poSRSMatch = oSRS.FindBestMatch(100);
664 0 : if (poSRSMatch)
665 : {
666 0 : oSRS = *poSRSMatch;
667 0 : poSRSMatch->Release();
668 0 : const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
669 0 : const char *pszCode = oSRS.GetAuthorityCode(nullptr);
670 0 : if (pszAuthName && EQUAL(pszAuthName, "EPSG") && pszCode)
671 0 : nWKID = atoi(pszCode);
672 : }
673 :
674 0 : char *pszWKT = nullptr;
675 0 : oSRS.exportToWkt(&pszWKT);
676 0 : osWKT = pszWKT;
677 0 : CPLFree(pszWKT);
678 :
679 0 : char *pszEscaped = CPLEscapeString(osWKT, -1, CPLES_XML);
680 0 : osEscapedWKT = pszEscaped;
681 0 : CPLFree(pszEscaped);
682 : }
683 :
684 : CPLString osXML = CPLSPrintf(
685 : "<GDAL_WMS>\n"
686 : " <Service name=\"TMS\">\n"
687 : " <ServerUrl>%s/tile/${z}/${y}/${x}</ServerUrl>\n"
688 : " </Service>\n"
689 : " <DataWindow>\n"
690 : " <UpperLeftX>%.8f</UpperLeftX>\n"
691 : " <UpperLeftY>%.8f</UpperLeftY>\n"
692 : " <LowerRightX>%.8f</LowerRightX>\n"
693 : " <LowerRightY>%.8f</LowerRightY>\n"
694 : " <TileLevel>%d</TileLevel>\n"
695 : " <TileCountX>%d</TileCountX>\n"
696 : " <YOrigin>top</YOrigin>\n"
697 : " </DataWindow>\n"
698 : " <Projection>%s</Projection>\n"
699 : " <BlockSizeX>%d</BlockSizeX>\n"
700 : " <BlockSizeY>%d</BlockSizeY>\n"
701 : " <Cache/>\n"
702 : "</GDAL_WMS>\n",
703 : osURL.c_str(), dfMinX, dfMaxY, dfMaxX, dfMinY, nLevelCount, nTileCountX,
704 1 : nWKID > 0 ? CPLSPrintf("EPSG:%d", nWKID) : osEscapedWKT.c_str(),
705 3 : nTileWidth, nTileHeight);
706 1 : CPLDebug("WMS", "Opening TMS :\n%s", osXML.c_str());
707 :
708 1 : return CPLParseXMLString(osXML);
709 : }
710 :
711 : /************************************************************************/
712 : /* Open() */
713 : /************************************************************************/
714 :
715 356 : GDALDataset *GDALWMSDataset::Open(GDALOpenInfo *poOpenInfo)
716 : {
717 356 : CPLXMLNode *config = nullptr;
718 356 : CPLErr ret = CE_None;
719 :
720 356 : const char *pszFilename = poOpenInfo->pszFilename;
721 356 : const char *pabyHeader =
722 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
723 :
724 356 : if (!WMSDriverIdentify(poOpenInfo))
725 0 : return nullptr;
726 :
727 356 : if (poOpenInfo->nHeaderBytes == 0 &&
728 343 : STARTS_WITH_CI(pszFilename, "<GDAL_WMS>"))
729 : {
730 335 : config = CPLParseXMLString(pszFilename);
731 : }
732 21 : else if (poOpenInfo->nHeaderBytes >= 10 &&
733 13 : STARTS_WITH_CI(pabyHeader, "<GDAL_WMS>"))
734 : {
735 11 : config = CPLParseXMLFile(pszFilename);
736 : }
737 10 : else if (poOpenInfo->nHeaderBytes == 0 &&
738 8 : (STARTS_WITH_CI(pszFilename, "WMS:http") ||
739 7 : STARTS_WITH_CI(pszFilename, "http")) &&
740 3 : (strstr(pszFilename, "/MapServer?f=json") != nullptr ||
741 2 : strstr(pszFilename, "/MapServer/?f=json") != nullptr ||
742 2 : strstr(pszFilename, "/ImageServer?f=json") != nullptr ||
743 2 : strstr(pszFilename, "/ImageServer/?f=json") != nullptr))
744 : {
745 1 : if (STARTS_WITH_CI(pszFilename, "WMS:http"))
746 0 : pszFilename += 4;
747 1 : CPLString osURL(pszFilename);
748 1 : if (strstr(pszFilename, "&pretty=true") == nullptr)
749 0 : osURL += "&pretty=true";
750 1 : CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), nullptr);
751 1 : if (psResult == nullptr)
752 0 : return nullptr;
753 1 : if (psResult->pabyData == nullptr)
754 : {
755 0 : CPLHTTPDestroyResult(psResult);
756 0 : return nullptr;
757 : }
758 1 : config = GDALWMSDatasetGetConfigFromArcGISJSON(
759 1 : osURL, reinterpret_cast<const char *>(psResult->pabyData));
760 2 : CPLHTTPDestroyResult(psResult);
761 : }
762 :
763 16 : else if (poOpenInfo->nHeaderBytes == 0 &&
764 7 : (STARTS_WITH_CI(pszFilename, "WMS:") ||
765 15 : CPLString(pszFilename).ifind("SERVICE=WMS") !=
766 6 : std::string::npos ||
767 6 : (poOpenInfo->IsSingleAllowedDriver("WMS") &&
768 1 : (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
769 0 : STARTS_WITH(poOpenInfo->pszFilename, "https://")))))
770 : {
771 2 : CPLString osLayers = CPLURLGetValue(pszFilename, "LAYERS");
772 2 : CPLString osRequest = CPLURLGetValue(pszFilename, "REQUEST");
773 2 : if (!osLayers.empty())
774 1 : config = GDALWMSDatasetGetConfigFromURL(poOpenInfo);
775 1 : else if (EQUAL(osRequest, "GetTileService"))
776 0 : return GDALWMSMetaDataset::DownloadGetTileService(poOpenInfo);
777 : else
778 1 : return GDALWMSMetaDataset::DownloadGetCapabilities(poOpenInfo);
779 : }
780 7 : else if (poOpenInfo->nHeaderBytes != 0 &&
781 2 : (strstr(pabyHeader, "<WMT_MS_Capabilities") != nullptr ||
782 2 : strstr(pabyHeader, "<WMS_Capabilities") != nullptr ||
783 2 : strstr(pabyHeader, "<!DOCTYPE WMT_MS_Capabilities") != nullptr))
784 : {
785 0 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
786 0 : if (psXML == nullptr)
787 0 : return nullptr;
788 0 : GDALDataset *poRet = GDALWMSMetaDataset::AnalyzeGetCapabilities(psXML);
789 0 : CPLDestroyXMLNode(psXML);
790 0 : return poRet;
791 : }
792 7 : else if (poOpenInfo->nHeaderBytes != 0 &&
793 2 : strstr(pabyHeader, "<WMS_Tile_Service") != nullptr)
794 : {
795 2 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
796 2 : if (psXML == nullptr)
797 0 : return nullptr;
798 : GDALDataset *poRet =
799 2 : GDALWMSMetaDataset::AnalyzeGetTileService(psXML, poOpenInfo);
800 2 : CPLDestroyXMLNode(psXML);
801 2 : return poRet;
802 : }
803 5 : else if (poOpenInfo->nHeaderBytes != 0 &&
804 0 : strstr(pabyHeader, "<TileMap version=\"1.0.0\"") != nullptr)
805 : {
806 0 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
807 0 : if (psXML == nullptr)
808 0 : return nullptr;
809 0 : config = GDALWMSDatasetGetConfigFromTileMap(psXML);
810 0 : CPLDestroyXMLNode(psXML);
811 : }
812 5 : else if (poOpenInfo->nHeaderBytes != 0 &&
813 0 : strstr(pabyHeader, "<Services") != nullptr &&
814 0 : strstr(pabyHeader, "<TileMapService version=\"1.0") != nullptr)
815 : {
816 0 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
817 0 : if (psXML == nullptr)
818 0 : return nullptr;
819 0 : CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=Services");
820 0 : GDALDataset *poRet = nullptr;
821 0 : if (psRoot)
822 : {
823 : CPLXMLNode *psTileMapService =
824 0 : CPLGetXMLNode(psRoot, "TileMapService");
825 0 : if (psTileMapService)
826 : {
827 : const char *pszHref =
828 0 : CPLGetXMLValue(psTileMapService, "href", nullptr);
829 0 : if (pszHref)
830 : {
831 0 : poRet = GDALDataset::Open(
832 : pszHref, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR);
833 : }
834 : }
835 : }
836 0 : CPLDestroyXMLNode(psXML);
837 0 : return poRet;
838 : }
839 5 : else if (poOpenInfo->nHeaderBytes != 0 &&
840 0 : strstr(pabyHeader, "<TileMapService version=\"1.0.0\"") != nullptr)
841 : {
842 0 : CPLXMLNode *psXML = CPLParseXMLFile(pszFilename);
843 0 : if (psXML == nullptr)
844 0 : return nullptr;
845 0 : GDALDataset *poRet = GDALWMSMetaDataset::AnalyzeTileMapService(psXML);
846 0 : CPLDestroyXMLNode(psXML);
847 0 : return poRet;
848 : }
849 5 : else if (poOpenInfo->nHeaderBytes == 0 &&
850 5 : STARTS_WITH_CI(pszFilename, "AGS:"))
851 : {
852 0 : return nullptr;
853 : }
854 5 : else if (poOpenInfo->nHeaderBytes == 0 &&
855 5 : STARTS_WITH_CI(pszFilename, "IIP:"))
856 : {
857 1 : CPLString osURL(pszFilename + 4);
858 1 : osURL += "&obj=Basic-Info";
859 1 : CPLHTTPResult *psResult = CPLHTTPFetch(osURL.c_str(), nullptr);
860 1 : if (psResult == nullptr)
861 0 : return nullptr;
862 1 : if (psResult->pabyData == nullptr)
863 : {
864 0 : CPLHTTPDestroyResult(psResult);
865 0 : return nullptr;
866 : }
867 : int nXSize, nYSize;
868 1 : const char *pszMaxSize = strstr(
869 1 : reinterpret_cast<const char *>(psResult->pabyData), "Max-size:");
870 : const char *pszResolutionNumber =
871 1 : strstr(reinterpret_cast<const char *>(psResult->pabyData),
872 : "Resolution-number:");
873 2 : if (pszMaxSize &&
874 1 : sscanf(pszMaxSize + strlen("Max-size:"), "%d %d", &nXSize,
875 2 : &nYSize) == 2 &&
876 : pszResolutionNumber)
877 : {
878 : int nResolutions =
879 1 : atoi(pszResolutionNumber + strlen("Resolution-number:"));
880 : char *pszEscapedURL =
881 1 : CPLEscapeString(pszFilename + 4, -1, CPLES_XML);
882 : CPLString osXML =
883 : CPLSPrintf("<GDAL_WMS>"
884 : " <Service name=\"IIP\">"
885 : " <ServerUrl>%s</ServerUrl>"
886 : " </Service>"
887 : " <DataWindow>"
888 : " <SizeX>%d</SizeX>"
889 : " <SizeY>%d</SizeY>"
890 : " <TileLevel>%d</TileLevel>"
891 : " </DataWindow>"
892 : " <BlockSizeX>256</BlockSizeX>"
893 : " <BlockSizeY>256</BlockSizeY>"
894 : " <BandsCount>3</BandsCount>"
895 : " <Cache />"
896 : "</GDAL_WMS>",
897 2 : pszEscapedURL, nXSize, nYSize, nResolutions - 1);
898 1 : config = CPLParseXMLString(osXML);
899 1 : CPLFree(pszEscapedURL);
900 : }
901 2 : CPLHTTPDestroyResult(psResult);
902 : }
903 4 : else if (poOpenInfo->nHeaderBytes == 0 &&
904 4 : STARTS_WITH_CI(pszFilename, "IIIF:"))
905 : {
906 : // Implements https://iiif.io/api/image/3.0/ "Image API 3.0"
907 :
908 4 : std::string osURL(pszFilename + strlen("IIIF:"));
909 4 : if (!osURL.empty() && osURL.back() == '/')
910 0 : osURL.pop_back();
911 : std::unique_ptr<CPLHTTPResult, decltype(&CPLHTTPDestroyResult)>
912 0 : psResult(CPLHTTPFetch((osURL + "/info.json").c_str(), nullptr),
913 4 : CPLHTTPDestroyResult);
914 4 : if (!psResult || !psResult->pabyData)
915 0 : return nullptr;
916 4 : CPLJSONDocument oDoc;
917 8 : if (!oDoc.LoadMemory(
918 4 : reinterpret_cast<const char *>(psResult->pabyData)))
919 0 : return nullptr;
920 4 : const CPLJSONObject oRoot = oDoc.GetRoot();
921 4 : const int nWidth = oRoot.GetInteger("width");
922 4 : const int nHeight = oRoot.GetInteger("height");
923 4 : if (nWidth <= 0 || nHeight <= 0)
924 : {
925 1 : CPLError(CE_Failure, CPLE_AppDefined,
926 : "'width' and/or 'height' missing or invalid");
927 1 : return nullptr;
928 : }
929 3 : int nBlockSizeX = 256;
930 3 : int nBlockSizeY = 256;
931 6 : const auto oTiles = oRoot.GetArray("tiles");
932 3 : int nLevelCount = 1;
933 3 : if (oTiles.Size() == 1)
934 : {
935 3 : nBlockSizeX = oTiles[0].GetInteger("width");
936 3 : nBlockSizeY = oTiles[0].GetInteger("height");
937 3 : if (nBlockSizeX <= 0 || nBlockSizeY <= 0)
938 : {
939 1 : CPLError(CE_Failure, CPLE_AppDefined,
940 : "'tiles[0].width' and/or 'tiles[0].height' missing or "
941 : "invalid");
942 1 : return nullptr;
943 : }
944 :
945 6 : const auto scaleFactors = oTiles[0].GetArray("scaleFactors");
946 2 : if (scaleFactors.Size() >= 1)
947 : {
948 2 : nLevelCount = 0;
949 2 : int expectedFactor = 1;
950 14 : for (const auto &jVal : scaleFactors)
951 : {
952 12 : if (nLevelCount < 30 && jVal.ToInteger() == expectedFactor)
953 : {
954 12 : ++nLevelCount;
955 12 : expectedFactor *= 2;
956 : }
957 : else
958 : {
959 0 : break;
960 : }
961 : }
962 2 : nLevelCount = std::max(1, nLevelCount);
963 : }
964 : }
965 :
966 2 : char *pszEscapedURL = CPLEscapeString(osURL.c_str(), -1, CPLES_XML);
967 : const CPLString osXML =
968 : CPLSPrintf("<GDAL_WMS>"
969 : " <Service name=\"IIIFImage\">"
970 : " <ServerUrl>%s</ServerUrl>"
971 : " <ImageFormat>image/jpeg</ImageFormat>"
972 : " </Service>"
973 : " <DataWindow>"
974 : " <SizeX>%d</SizeX>"
975 : " <SizeY>%d</SizeY>"
976 : " <TileLevel>%d</TileLevel>"
977 : " </DataWindow>"
978 : " <BlockSizeX>%d</BlockSizeX>"
979 : " <BlockSizeY>%d</BlockSizeY>"
980 : " <BandsCount>3</BandsCount>"
981 : " <Cache />"
982 : "</GDAL_WMS>",
983 : pszEscapedURL, nWidth, nHeight, nLevelCount, nBlockSizeX,
984 4 : nBlockSizeY);
985 2 : config = CPLParseXMLString(osXML);
986 4 : CPLFree(pszEscapedURL);
987 : }
988 : else
989 0 : return nullptr;
990 351 : if (config == nullptr)
991 0 : return nullptr;
992 :
993 : /* -------------------------------------------------------------------- */
994 : /* Confirm the requested access is supported. */
995 : /* -------------------------------------------------------------------- */
996 351 : if (poOpenInfo->eAccess == GA_Update)
997 : {
998 0 : CPLDestroyXMLNode(config);
999 0 : ReportUpdateNotSupportedByDriver("WMS");
1000 0 : return nullptr;
1001 : }
1002 :
1003 351 : GDALWMSDataset *ds = new GDALWMSDataset();
1004 351 : ret = ds->Initialize(config, poOpenInfo->papszOpenOptions);
1005 351 : if (ret != CE_None)
1006 : {
1007 2 : delete ds;
1008 2 : ds = nullptr;
1009 : }
1010 351 : CPLDestroyXMLNode(config);
1011 :
1012 : /* -------------------------------------------------------------------- */
1013 : /* Initialize any PAM information. */
1014 : /* -------------------------------------------------------------------- */
1015 351 : if (ds != nullptr)
1016 : {
1017 349 : if (poOpenInfo->pszFilename && poOpenInfo->pszFilename[0] == '<')
1018 : {
1019 334 : ds->nPamFlags = GPF_DISABLED;
1020 : }
1021 : else
1022 : {
1023 15 : ds->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1024 15 : ds->SetDescription(poOpenInfo->pszFilename);
1025 15 : ds->TryLoadXML();
1026 : }
1027 : }
1028 :
1029 351 : return ds;
1030 : }
1031 :
1032 : /************************************************************************/
1033 : /* GetServerConfig() */
1034 : /************************************************************************/
1035 :
1036 2 : const char *GDALWMSDataset::GetServerConfig(const char *URI,
1037 : char **papszHTTPOptions)
1038 : {
1039 4 : CPLMutexHolder oHolder(&cfgmtx);
1040 :
1041 : // Might have it cached already
1042 2 : if (cfg.end() != cfg.find(URI))
1043 1 : return cfg.find(URI)->second;
1044 :
1045 1 : CPLHTTPResult *psResult = CPLHTTPFetch(URI, papszHTTPOptions);
1046 :
1047 1 : if (nullptr == psResult)
1048 0 : return nullptr;
1049 :
1050 : // Capture the result in buffer, get rid of http result
1051 1 : if ((psResult->nStatus == 0) && (nullptr != psResult->pabyData) &&
1052 1 : ('\0' != psResult->pabyData[0]))
1053 2 : cfg.insert(make_pair(
1054 1 : URI, static_cast<CPLString>(
1055 2 : reinterpret_cast<const char *>(psResult->pabyData))));
1056 :
1057 1 : CPLHTTPDestroyResult(psResult);
1058 :
1059 1 : if (cfg.end() != cfg.find(URI))
1060 1 : return cfg.find(URI)->second;
1061 : else
1062 0 : return nullptr;
1063 : }
1064 :
1065 : // Empties the server configuration cache and removes the mutex
1066 0 : void GDALWMSDataset::ClearConfigCache()
1067 : {
1068 : // Obviously not thread safe, should only be called when no WMS files are
1069 : // being opened
1070 0 : cfg.clear();
1071 0 : DestroyCfgMutex();
1072 0 : }
1073 :
1074 5 : void GDALWMSDataset::DestroyCfgMutex()
1075 : {
1076 5 : if (cfgmtx)
1077 0 : CPLDestroyMutex(cfgmtx);
1078 5 : cfgmtx = nullptr;
1079 5 : }
1080 :
1081 : /************************************************************************/
1082 : /* CreateCopy() */
1083 : /************************************************************************/
1084 :
1085 19 : GDALDataset *GDALWMSDataset::CreateCopy(const char *pszFilename,
1086 : GDALDataset *poSrcDS,
1087 : CPL_UNUSED int bStrict,
1088 : CPL_UNUSED char **papszOptions,
1089 : CPL_UNUSED GDALProgressFunc pfnProgress,
1090 : CPL_UNUSED void *pProgressData)
1091 : {
1092 38 : if (poSrcDS->GetDriver() == nullptr ||
1093 19 : !EQUAL(poSrcDS->GetDriver()->GetDescription(), "WMS"))
1094 : {
1095 18 : CPLError(CE_Failure, CPLE_NotSupported,
1096 : "Source dataset must be a WMS dataset");
1097 18 : return nullptr;
1098 : }
1099 :
1100 1 : const char *pszXML = poSrcDS->GetMetadataItem("XML", "WMS");
1101 1 : if (pszXML == nullptr)
1102 : {
1103 0 : CPLError(CE_Failure, CPLE_AppDefined,
1104 : "Cannot get XML definition of source WMS dataset");
1105 0 : return nullptr;
1106 : }
1107 :
1108 1 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
1109 1 : if (fp == nullptr)
1110 0 : return nullptr;
1111 :
1112 1 : VSIFWriteL(pszXML, 1, strlen(pszXML), fp);
1113 1 : VSIFCloseL(fp);
1114 :
1115 2 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
1116 1 : return Open(&oOpenInfo);
1117 : }
1118 :
1119 5 : void WMSDeregister(CPL_UNUSED GDALDriver *d)
1120 : {
1121 5 : GDALWMSDataset::DestroyCfgMutex();
1122 5 : }
1123 :
1124 : // Define a minidriver factory type, create one and register it
1125 : #define RegisterMinidriver(name) \
1126 : class WMSMiniDriverFactory_##name : public WMSMiniDriverFactory \
1127 : { \
1128 : public: \
1129 : WMSMiniDriverFactory_##name() \
1130 : { \
1131 : m_name = CPLString(#name); \
1132 : } \
1133 : virtual ~WMSMiniDriverFactory_##name() \
1134 : { \
1135 : } \
1136 : virtual WMSMiniDriver *New() const override \
1137 : { \
1138 : return new WMSMiniDriver_##name; \
1139 : } \
1140 : }; \
1141 : WMSRegisterMiniDriverFactory(new WMSMiniDriverFactory_##name());
1142 :
1143 : /************************************************************************/
1144 : /* GDALRegister_WMS() */
1145 : /************************************************************************/
1146 :
1147 : //
1148 : // Do not define any open options here!
1149 : // Doing so will enable checking the open options, which will generate warnings
1150 : // for undeclared options which may be handled by individual minidrivers
1151 : //
1152 :
1153 10 : void GDALRegister_WMS()
1154 :
1155 : {
1156 10 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1157 0 : return;
1158 :
1159 : // Register all minidrivers here
1160 42 : RegisterMinidriver(WMS);
1161 41 : RegisterMinidriver(TileService);
1162 40 : RegisterMinidriver(WorldWind);
1163 375 : RegisterMinidriver(TMS);
1164 44 : RegisterMinidriver(TiledWMS);
1165 41 : RegisterMinidriver(VirtualEarth);
1166 42 : RegisterMinidriver(AGS);
1167 41 : RegisterMinidriver(IIP);
1168 42 : RegisterMinidriver(IIIFImage);
1169 40 : RegisterMinidriver(MRF);
1170 41 : RegisterMinidriver(OGCAPIMaps);
1171 41 : RegisterMinidriver(OGCAPICoverage);
1172 :
1173 10 : GDALDriver *poDriver = new GDALDriver();
1174 10 : WMSDriverSetCommonMetadata(poDriver);
1175 :
1176 10 : poDriver->pfnOpen = GDALWMSDataset::Open;
1177 10 : poDriver->pfnUnloadDriver = WMSDeregister;
1178 10 : poDriver->pfnCreateCopy = GDALWMSDataset::CreateCopy;
1179 :
1180 10 : GetGDALDriverManager()->RegisterDriver(poDriver);
1181 : }
|