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