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