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