1 /******************************************************************************
2  *
3  * Project:  WMS Client Driver
4  * Purpose:  Definition of GDALWMSMetaDataset class
5  * Author:   Even Rouault, <even dot rouault at spatialys.com>
6  *
7  ******************************************************************************
8  * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "wmsmetadataset.h"
30 
31 int VersionStringToInt(const char *version);
32 
33 /************************************************************************/
34 /*                          GDALWMSMetaDataset()                        */
35 /************************************************************************/
36 
GDALWMSMetaDataset()37 GDALWMSMetaDataset::GDALWMSMetaDataset() : papszSubDatasets(nullptr) {}
38 
39 /************************************************************************/
40 /*                         ~GDALWMSMetaDataset()                        */
41 /************************************************************************/
42 
~GDALWMSMetaDataset()43 GDALWMSMetaDataset::~GDALWMSMetaDataset()
44 {
45     CSLDestroy(papszSubDatasets);
46 }
47 
48 /************************************************************************/
49 /*                            AddSubDataset()                           */
50 /************************************************************************/
51 
AddSubDataset(const char * pszName,const char * pszDesc)52 void GDALWMSMetaDataset::AddSubDataset(const char* pszName,
53                                        const char* pszDesc)
54 {
55     char    szName[80];
56     int     nCount = CSLCount(papszSubDatasets ) / 2;
57 
58     snprintf( szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount+1 );
59     papszSubDatasets =
60         CSLSetNameValue( papszSubDatasets, szName, pszName );
61 
62     snprintf( szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount+1 );
63     papszSubDatasets =
64         CSLSetNameValue( papszSubDatasets, szName, pszDesc);
65 }
66 
67 /************************************************************************/
68 /*                        DownloadGetCapabilities()                     */
69 /************************************************************************/
70 
DownloadGetCapabilities(GDALOpenInfo * poOpenInfo)71 GDALDataset *GDALWMSMetaDataset::DownloadGetCapabilities(GDALOpenInfo *poOpenInfo)
72 {
73     const char* pszURL = poOpenInfo->pszFilename;
74     if (STARTS_WITH_CI(pszURL, "WMS:"))
75         pszURL += 4;
76 
77     CPLString osFormat = CPLURLGetValue(pszURL, "FORMAT");
78     CPLString osTransparent = CPLURLGetValue(pszURL, "TRANSPARENT");
79     CPLString osVersion = CPLURLGetValue(pszURL, "VERSION");
80     CPLString osPreferredSRS = CPLURLGetValue(pszURL, "SRS");
81     if( osPreferredSRS.empty() )
82         osPreferredSRS = CPLURLGetValue(pszURL, "CRS");
83 
84     if (osVersion.empty())
85         osVersion = "1.1.1";
86 
87     CPLString osURL(pszURL);
88     osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
89     osURL = CPLURLAddKVP(osURL, "VERSION", osVersion);
90     osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
91     /* Remove all other keywords */
92     osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
93     osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
94     osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
95     osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
96     osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
97     osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
98     osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
99     osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
100     osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
101 
102     CPLHTTPResult* psResult = CPLHTTPFetch( osURL, nullptr );
103     if (psResult == nullptr)
104     {
105         return nullptr;
106     }
107     if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
108     {
109         CPLError(CE_Failure, CPLE_AppDefined,
110                  "Error returned by server : %s (%d)",
111                  (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
112                  psResult->nStatus);
113         CPLHTTPDestroyResult(psResult);
114         return nullptr;
115     }
116     if (psResult->pabyData == nullptr)
117     {
118         CPLError(CE_Failure, CPLE_AppDefined,
119                  "Empty content returned by server");
120         CPLHTTPDestroyResult(psResult);
121         return nullptr;
122     }
123 
124     CPLXMLNode* psXML = CPLParseXMLString( (const char*) psResult->pabyData );
125     if (psXML == nullptr)
126     {
127         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
128                 psResult->pabyData);
129         CPLHTTPDestroyResult(psResult);
130         return nullptr;
131     }
132 
133     GDALDataset* poRet = AnalyzeGetCapabilities(psXML, osFormat, osTransparent, osPreferredSRS);
134 
135     CPLHTTPDestroyResult(psResult);
136     CPLDestroyXMLNode( psXML );
137 
138     return poRet;
139 }
140 
141 /************************************************************************/
142 /*                         DownloadGetTileService()                     */
143 /************************************************************************/
144 
DownloadGetTileService(GDALOpenInfo * poOpenInfo)145 GDALDataset *GDALWMSMetaDataset::DownloadGetTileService(GDALOpenInfo *poOpenInfo)
146 {
147     const char* pszURL = poOpenInfo->pszFilename;
148     if (STARTS_WITH_CI(pszURL, "WMS:"))
149         pszURL += 4;
150 
151     CPLString osURL(pszURL);
152     osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
153     osURL = CPLURLAddKVP(osURL, "REQUEST", "GetTileService");
154     /* Remove all other keywords */
155     osURL = CPLURLAddKVP(osURL, "VERSION", nullptr);
156     osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
157     osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
158     osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
159     osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
160     osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
161     osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
162     osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
163     osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
164     osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
165 
166     CPLHTTPResult* psResult = CPLHTTPFetch( osURL, nullptr );
167     if (psResult == nullptr)
168     {
169         return nullptr;
170     }
171     if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
172     {
173         CPLError(CE_Failure, CPLE_AppDefined,
174                  "Error returned by server : %s (%d)",
175                  (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
176                  psResult->nStatus);
177         CPLHTTPDestroyResult(psResult);
178         return nullptr;
179     }
180     if (psResult->pabyData == nullptr)
181     {
182         CPLError(CE_Failure, CPLE_AppDefined,
183                  "Empty content returned by server");
184         CPLHTTPDestroyResult(psResult);
185         return nullptr;
186     }
187 
188     CPLXMLNode* psXML = CPLParseXMLString( (const char*) psResult->pabyData );
189     if (psXML == nullptr)
190     {
191         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
192                 psResult->pabyData);
193         CPLHTTPDestroyResult(psResult);
194         return nullptr;
195     }
196 
197     GDALDataset* poRet = AnalyzeGetTileService(psXML, poOpenInfo);
198 
199     CPLHTTPDestroyResult(psResult);
200     CPLDestroyXMLNode( psXML );
201 
202     return poRet;
203 }
204 
205 /************************************************************************/
206 /*                      GetMetadataDomainList()                         */
207 /************************************************************************/
208 
GetMetadataDomainList()209 char **GDALWMSMetaDataset::GetMetadataDomainList()
210 {
211     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
212                                    TRUE,
213                                    "SUBDATASETS", nullptr);
214 }
215 
216 /************************************************************************/
217 /*                            GetMetadata()                             */
218 /************************************************************************/
219 
GetMetadata(const char * pszDomain)220 char **GDALWMSMetaDataset::GetMetadata( const char *pszDomain )
221 
222 {
223     if( pszDomain != nullptr && EQUAL(pszDomain,"SUBDATASETS") )
224         return papszSubDatasets;
225 
226     return GDALPamDataset::GetMetadata( pszDomain );
227 }
228 
229 /************************************************************************/
230 /*                           AddSubDataset()                            */
231 /************************************************************************/
232 
AddSubDataset(const char * pszLayerName,const char * pszTitle,CPL_UNUSED const char * pszAbstract,const char * pszSRS,const char * pszMinX,const char * pszMinY,const char * pszMaxX,const char * pszMaxY,CPLString osFormat,CPLString osTransparent)233 void GDALWMSMetaDataset::AddSubDataset( const char* pszLayerName,
234                                         const char* pszTitle,
235                                         CPL_UNUSED const char* pszAbstract,
236                                         const char* pszSRS,
237                                         const char* pszMinX,
238                                         const char* pszMinY,
239                                         const char* pszMaxX,
240                                         const char* pszMaxY,
241                                         CPLString osFormat,
242                                         CPLString osTransparent)
243 {
244     CPLString osSubdatasetName = "WMS:";
245     osSubdatasetName += osGetURL;
246     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SERVICE", "WMS");
247     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "VERSION", osVersion);
248     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "REQUEST", "GetMap");
249     char* pszEscapedLayerName = CPLEscapeString(pszLayerName, -1, CPLES_URL);
250     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "LAYERS", pszEscapedLayerName);
251     CPLFree(pszEscapedLayerName);
252     if(VersionStringToInt(osVersion.c_str())>= VersionStringToInt("1.3.0"))
253     {
254         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "CRS", pszSRS);
255     }
256     else
257         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SRS", pszSRS);
258     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "BBOX",
259              CPLSPrintf("%s,%s,%s,%s", pszMinX, pszMinY, pszMaxX, pszMaxY));
260     if (!osFormat.empty())
261         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "FORMAT",
262                                         osFormat);
263     if (!osTransparent.empty())
264         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TRANSPARENT",
265                                         osTransparent);
266 
267     if (pszTitle)
268     {
269         if (!osXMLEncoding.empty() &&
270             osXMLEncoding != "utf-8" &&
271             osXMLEncoding != "UTF-8")
272         {
273             char* pszRecodedTitle = CPLRecode(pszTitle, osXMLEncoding.c_str(),
274                                               CPL_ENC_UTF8);
275             if (pszRecodedTitle)
276                 AddSubDataset(osSubdatasetName, pszRecodedTitle);
277             else
278                 AddSubDataset(osSubdatasetName, pszTitle);
279             CPLFree(pszRecodedTitle);
280         }
281         else
282         {
283             AddSubDataset(osSubdatasetName, pszTitle);
284         }
285     }
286     else
287     {
288         AddSubDataset(osSubdatasetName, pszLayerName);
289     }
290 }
291 
292 /************************************************************************/
293 /*                         AddWMSCSubDataset()                          */
294 /************************************************************************/
295 
AddWMSCSubDataset(WMSCTileSetDesc & oWMSCTileSetDesc,const char * pszTitle,CPLString osTransparent)296 void GDALWMSMetaDataset::AddWMSCSubDataset(WMSCTileSetDesc& oWMSCTileSetDesc,
297                                           const char* pszTitle,
298                                           CPLString osTransparent)
299 {
300     CPLString osSubdatasetName = "WMS:";
301     osSubdatasetName += osGetURL;
302     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SERVICE", "WMS");
303     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "VERSION", osVersion);
304     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "REQUEST", "GetMap");
305     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "LAYERS", oWMSCTileSetDesc.osLayers);
306     if(VersionStringToInt(osVersion.c_str())>= VersionStringToInt("1.3.0"))
307         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "CRS", oWMSCTileSetDesc.osSRS);
308     else
309         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SRS", oWMSCTileSetDesc.osSRS);
310     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "BBOX",
311              CPLSPrintf("%s,%s,%s,%s", oWMSCTileSetDesc.osMinX.c_str(),
312                                        oWMSCTileSetDesc.osMinY.c_str(),
313                                        oWMSCTileSetDesc.osMaxX.c_str(),
314                                        oWMSCTileSetDesc.osMaxY.c_str()));
315 
316     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "FORMAT", oWMSCTileSetDesc.osFormat);
317     if (!osTransparent.empty())
318         osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TRANSPARENT",
319                                         osTransparent);
320     if (oWMSCTileSetDesc.nTileWidth != oWMSCTileSetDesc.nTileHeight)
321         CPLDebug("WMS", "Weird: nTileWidth != nTileHeight for %s",
322                  oWMSCTileSetDesc.osLayers.c_str());
323     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TILESIZE",
324                                     CPLSPrintf("%d", oWMSCTileSetDesc.nTileWidth));
325     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "OVERVIEWCOUNT",
326                                     CPLSPrintf("%d", oWMSCTileSetDesc.nResolutions - 1));
327     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "MINRESOLUTION",
328                                     CPLSPrintf("%.16f", oWMSCTileSetDesc.dfMinResolution));
329     osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TILED", "true");
330 
331     if (pszTitle)
332     {
333         if (!osXMLEncoding.empty() &&
334             osXMLEncoding != "utf-8" &&
335             osXMLEncoding != "UTF-8")
336         {
337             char* pszRecodedTitle = CPLRecode(pszTitle, osXMLEncoding.c_str(),
338                                               CPL_ENC_UTF8);
339             if (pszRecodedTitle)
340                 AddSubDataset(osSubdatasetName, pszRecodedTitle);
341             else
342                 AddSubDataset(osSubdatasetName, pszTitle);
343             CPLFree(pszRecodedTitle);
344         }
345         else
346         {
347             AddSubDataset(osSubdatasetName, pszTitle);
348         }
349     }
350     else
351     {
352         AddSubDataset(osSubdatasetName, oWMSCTileSetDesc.osLayers);
353     }
354 }
355 
356 /************************************************************************/
357 /*                             ExploreLayer()                           */
358 /************************************************************************/
359 
ExploreLayer(CPLXMLNode * psXML,CPLString osFormat,CPLString osTransparent,CPLString osPreferredSRS,const char * pszSRS,const char * pszMinX,const char * pszMinY,const char * pszMaxX,const char * pszMaxY)360 void GDALWMSMetaDataset::ExploreLayer(CPLXMLNode* psXML,
361                                       CPLString osFormat,
362                                       CPLString osTransparent,
363                                       CPLString osPreferredSRS,
364                                       const char* pszSRS,
365                                       const char* pszMinX,
366                                       const char* pszMinY,
367                                       const char* pszMaxX,
368                                       const char* pszMaxY)
369 {
370     const char* pszName = CPLGetXMLValue(psXML, "Name", nullptr);
371     const char* pszTitle = CPLGetXMLValue(psXML, "Title", nullptr);
372     const char* pszAbstract = CPLGetXMLValue(psXML, "Abstract", nullptr);
373 
374     CPLXMLNode* psSRS = nullptr;
375     const char* pszSRSLocal = nullptr;
376     const char* pszMinXLocal = nullptr;
377     const char* pszMinYLocal = nullptr;
378     const char* pszMaxXLocal = nullptr;
379     const char* pszMaxYLocal = nullptr;
380 
381     const char* pszSRSTagName =
382         VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0") ? "CRS" : "SRS";
383 
384     /* Use local bounding box if available, otherwise use the one */
385     /* that comes from an upper layer */
386     /* such as in http://neowms.sci.gsfc.nasa.gov/wms/wms */
387     CPLXMLNode* psIter = psXML->psChild;
388     while( psIter != nullptr )
389     {
390         if( psIter->eType == CXT_Element &&
391             strcmp(psIter->pszValue, "BoundingBox") == 0 )
392         {
393             psSRS = psIter;
394             pszSRSLocal = CPLGetXMLValue(psSRS, pszSRSTagName, nullptr);
395             if( osPreferredSRS.empty() || pszSRSLocal == nullptr )
396                 break;
397             if( EQUAL(osPreferredSRS, pszSRSLocal) )
398                 break;
399             psSRS = nullptr;
400             pszSRSLocal = nullptr;
401         }
402         psIter = psIter->psNext;
403     }
404 
405     if (psSRS == nullptr)
406     {
407         psSRS = CPLGetXMLNode( psXML, "LatLonBoundingBox" );
408         pszSRSLocal = CPLGetXMLValue(psXML, pszSRSTagName, nullptr);
409         if (pszSRSLocal == nullptr)
410             pszSRSLocal = "EPSG:4326";
411     }
412 
413     if (pszSRSLocal != nullptr && psSRS != nullptr)
414     {
415         pszMinXLocal = CPLGetXMLValue(psSRS, "minx", nullptr);
416         pszMinYLocal = CPLGetXMLValue(psSRS, "miny", nullptr);
417         pszMaxXLocal = CPLGetXMLValue(psSRS, "maxx", nullptr);
418         pszMaxYLocal = CPLGetXMLValue(psSRS, "maxy", nullptr);
419 
420         if (pszMinXLocal && pszMinYLocal && pszMaxXLocal && pszMaxYLocal)
421         {
422             pszSRS = pszSRSLocal;
423             pszMinX = pszMinXLocal;
424             pszMinY = pszMinYLocal;
425             pszMaxX = pszMaxXLocal;
426             pszMaxY = pszMaxYLocal;
427         }
428     }
429 
430     if (pszName != nullptr && pszSRS && pszMinX && pszMinY && pszMaxX && pszMaxY)
431     {
432         CPLString osLocalTransparent(osTransparent);
433         if (osLocalTransparent.empty())
434         {
435             const char* pszOpaque = CPLGetXMLValue(psXML, "opaque", "0");
436             if (EQUAL(pszOpaque, "1"))
437                 osLocalTransparent = "FALSE";
438         }
439 
440         WMSCKeyType oWMSCKey(pszName, pszSRS);
441         std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter = osMapWMSCTileSet.find(oWMSCKey);
442         if (oIter != osMapWMSCTileSet.end())
443         {
444             AddWMSCSubDataset(oIter->second, pszTitle, osLocalTransparent);
445         }
446         else
447         {
448             AddSubDataset(pszName, pszTitle, pszAbstract,
449                           pszSRS, pszMinX, pszMinY,
450                           pszMaxX, pszMaxY, osFormat, osLocalTransparent);
451         }
452     }
453 
454     psIter = psXML->psChild;
455     for(; psIter != nullptr; psIter = psIter->psNext)
456     {
457         if (psIter->eType == CXT_Element)
458         {
459             if (EQUAL(psIter->pszValue, "Layer"))
460                 ExploreLayer(psIter, osFormat, osTransparent, osPreferredSRS,
461                              pszSRS, pszMinX, pszMinY, pszMaxX, pszMaxY);
462         }
463     }
464 }
465 
466 /************************************************************************/
467 /*                         ParseWMSCTileSets()                          */
468 /************************************************************************/
469 
ParseWMSCTileSets(CPLXMLNode * psXML)470 void GDALWMSMetaDataset::ParseWMSCTileSets(CPLXMLNode* psXML)
471 {
472     CPLXMLNode* psIter = psXML->psChild;
473     for(;psIter;psIter = psIter->psNext)
474     {
475         if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
476         {
477             const char* pszSRS = CPLGetXMLValue(psIter, "SRS", nullptr);
478             if (pszSRS == nullptr)
479                 continue;
480 
481             CPLXMLNode* psBoundingBox = CPLGetXMLNode( psIter, "BoundingBox" );
482             if (psBoundingBox == nullptr)
483                 continue;
484 
485             const char* pszMinX = CPLGetXMLValue(psBoundingBox, "minx", nullptr);
486             const char* pszMinY = CPLGetXMLValue(psBoundingBox, "miny", nullptr);
487             const char* pszMaxX = CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
488             const char* pszMaxY = CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
489             if (pszMinX == nullptr || pszMinY == nullptr || pszMaxX == nullptr || pszMaxY == nullptr)
490                 continue;
491 
492             double dfMinX = CPLAtofM(pszMinX);
493             double dfMinY = CPLAtofM(pszMinY);
494             double dfMaxX = CPLAtofM(pszMaxX);
495             double dfMaxY = CPLAtofM(pszMaxY);
496             if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
497                 continue;
498 
499             const char* pszFormat = CPLGetXMLValue( psIter, "Format", nullptr );
500             if (pszFormat == nullptr)
501                 continue;
502             if (strstr(pszFormat, "kml"))
503                 continue;
504 
505             const char* pszTileWidth = CPLGetXMLValue(psIter, "Width", nullptr);
506             const char* pszTileHeight = CPLGetXMLValue(psIter, "Height", nullptr);
507             if (pszTileWidth == nullptr || pszTileHeight == nullptr)
508                 continue;
509 
510             int nTileWidth = atoi(pszTileWidth);
511             int nTileHeight = atoi(pszTileHeight);
512             if (nTileWidth < 128 || nTileHeight < 128)
513                 continue;
514 
515             const char* pszLayers = CPLGetXMLValue(psIter, "Layers", nullptr);
516             if (pszLayers == nullptr)
517                 continue;
518 
519             const char* pszResolutions = CPLGetXMLValue(psIter, "Resolutions", nullptr);
520             if (pszResolutions == nullptr)
521                 continue;
522             char** papszTokens = CSLTokenizeStringComplex(pszResolutions, " ", 0, 0);
523             double dfMinResolution = 0;
524             int i;
525             for(i=0; papszTokens && papszTokens[i]; i++)
526             {
527                 double dfResolution = CPLAtofM(papszTokens[i]);
528                 if (i==0 || dfResolution < dfMinResolution)
529                     dfMinResolution = dfResolution;
530             }
531             CSLDestroy(papszTokens);
532             int nResolutions = i;
533             if (nResolutions == 0)
534                 continue;
535 
536             const char* pszStyles = CPLGetXMLValue(psIter, "Styles", "");
537 
538             /* http://demo.opengeo.org/geoserver/gwc/service/wms?tiled=TRUE&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities */
539             /* has different variations of formats for the same (formats, SRS) tuple, so just */
540             /* keep the first one which is a png format */
541             WMSCKeyType oWMSCKey(pszLayers, pszSRS);
542             std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter = osMapWMSCTileSet.find(oWMSCKey);
543             if (oIter != osMapWMSCTileSet.end())
544                 continue;
545 
546             WMSCTileSetDesc oWMSCTileSet;
547             oWMSCTileSet.osLayers = pszLayers;
548             oWMSCTileSet.osSRS = pszSRS;
549             oWMSCTileSet.osMinX = pszMinX;
550             oWMSCTileSet.osMinY = pszMinY;
551             oWMSCTileSet.osMaxX = pszMaxX;
552             oWMSCTileSet.osMaxY = pszMaxY;
553             oWMSCTileSet.dfMinX = dfMinX;
554             oWMSCTileSet.dfMinY = dfMinY;
555             oWMSCTileSet.dfMaxX = dfMaxX;
556             oWMSCTileSet.dfMaxY = dfMaxY;
557             oWMSCTileSet.nResolutions = nResolutions;
558             oWMSCTileSet.dfMinResolution = dfMinResolution;
559             oWMSCTileSet.osFormat = pszFormat;
560             oWMSCTileSet.osStyle = pszStyles;
561             oWMSCTileSet.nTileWidth = nTileWidth;
562             oWMSCTileSet.nTileHeight = nTileHeight;
563 
564             osMapWMSCTileSet[oWMSCKey] = oWMSCTileSet;
565         }
566     }
567 }
568 
569 /************************************************************************/
570 /*                        AnalyzeGetCapabilities()                      */
571 /************************************************************************/
572 
AnalyzeGetCapabilities(CPLXMLNode * psXML,CPLString osFormat,CPLString osTransparent,CPLString osPreferredSRS)573 GDALDataset* GDALWMSMetaDataset::AnalyzeGetCapabilities(CPLXMLNode* psXML,
574                                                           CPLString osFormat,
575                                                           CPLString osTransparent,
576                                                           CPLString osPreferredSRS)
577 {
578     const char* pszEncoding = nullptr;
579     if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
580         pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
581 
582     CPLXMLNode* psRoot = CPLGetXMLNode( psXML, "=WMT_MS_Capabilities" );
583     if (psRoot == nullptr)
584         psRoot = CPLGetXMLNode( psXML, "=WMS_Capabilities" );
585     if (psRoot == nullptr)
586         return nullptr;
587     CPLXMLNode* psCapability = CPLGetXMLNode(psRoot, "Capability");
588     if (psCapability == nullptr)
589         return nullptr;
590 
591     CPLXMLNode* psOnlineResource = CPLGetXMLNode(psCapability,
592                              "Request.GetMap.DCPType.HTTP.Get.OnlineResource");
593     if (psOnlineResource == nullptr)
594         return nullptr;
595     const char* pszGetURL =
596         CPLGetXMLValue(psOnlineResource, "xlink:href", nullptr);
597     if (pszGetURL == nullptr)
598         return nullptr;
599 
600     CPLXMLNode* psLayer = CPLGetXMLNode(psCapability, "Layer");
601     if (psLayer == nullptr)
602         return nullptr;
603 
604     CPLXMLNode* psVendorSpecificCapabilities =
605         CPLGetXMLNode(psCapability, "VendorSpecificCapabilities");
606 
607     GDALWMSMetaDataset* poDS = new GDALWMSMetaDataset();
608     const char* pszVersion = CPLGetXMLValue(psRoot, "version", nullptr);
609     if (pszVersion)
610         poDS->osVersion = pszVersion;
611     else
612         poDS->osVersion = "1.1.1";
613     poDS->osGetURL = pszGetURL;
614     poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
615     if (psVendorSpecificCapabilities)
616         poDS->ParseWMSCTileSets(psVendorSpecificCapabilities);
617     poDS->ExploreLayer(psLayer, osFormat, osTransparent, osPreferredSRS);
618 
619     return poDS;
620 }
621 
622 /************************************************************************/
623 /*                          AddTiledSubDataset()                        */
624 /************************************************************************/
625 
626 // tiledWMS only
AddTiledSubDataset(const char * pszTiledGroupName,const char * pszTitle,const char * const * papszChanges)627 void GDALWMSMetaDataset::AddTiledSubDataset(const char* pszTiledGroupName,
628                                             const char* pszTitle,
629                                             const char* const* papszChanges)
630 {
631     CPLString osSubdatasetName = "<GDAL_WMS><Service name=\"TiledWMS\"><ServerUrl>";
632     osSubdatasetName += osGetURL;
633     osSubdatasetName += "</ServerUrl><TiledGroupName>";
634     osSubdatasetName += pszTiledGroupName;
635     osSubdatasetName += "</TiledGroupName>";
636 
637     for (int i = 0; papszChanges != nullptr && papszChanges[i] != nullptr; i++)
638     {
639         char* key = nullptr;
640         const char* value = CPLParseNameValue(papszChanges[i], &key);
641         if (value != nullptr && key != nullptr)
642             osSubdatasetName += CPLSPrintf("<Change key=\"${%s}\">%s</Change>", key, value);
643         CPLFree(key);
644     }
645 
646     osSubdatasetName += "</Service></GDAL_WMS>";
647 
648     if (pszTitle)
649     {
650         if (!osXMLEncoding.empty() &&
651             osXMLEncoding != "utf-8" &&
652             osXMLEncoding != "UTF-8")
653         {
654             char* pszRecodedTitle = CPLRecode(pszTitle, osXMLEncoding.c_str(),
655                                               CPL_ENC_UTF8);
656             if (pszRecodedTitle)
657                 AddSubDataset(osSubdatasetName, pszRecodedTitle);
658             else
659                 AddSubDataset(osSubdatasetName, pszTitle);
660             CPLFree(pszRecodedTitle);
661         }
662         else
663         {
664             AddSubDataset(osSubdatasetName, pszTitle);
665         }
666     }
667     else
668     {
669         AddSubDataset(osSubdatasetName, pszTiledGroupName);
670     }
671 }
672 
673 /************************************************************************/
674 /*                     AnalyzeGetTileServiceRecurse()                   */
675 /************************************************************************/
676 // tiledWMS only
AnalyzeGetTileServiceRecurse(CPLXMLNode * psXML,GDALOpenInfo * poOpenInfo)677 void GDALWMSMetaDataset::AnalyzeGetTileServiceRecurse(CPLXMLNode* psXML, GDALOpenInfo * poOpenInfo)
678 {
679     // Only list tiled groups that contain the string in the open option TiledGroupName, if given
680     char **papszLocalOpenOptions = poOpenInfo ? poOpenInfo->papszOpenOptions : nullptr;
681     CPLString osMatch(CSLFetchNameValueDef(papszLocalOpenOptions, "TiledGroupName",""));
682     osMatch.toupper();
683     // Also pass the change patterns, if provided
684     char **papszChanges = CSLFetchNameValueMultiple(papszLocalOpenOptions, "Change");
685 
686     CPLXMLNode* psIter = psXML->psChild;
687     for(; psIter != nullptr; psIter = psIter->psNext)
688     {
689         if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TiledGroup"))
690         {
691             const char *pszName = CPLGetXMLValue(psIter, "Name", nullptr);
692             if (pszName)
693             {
694                 const char* pszTitle = CPLGetXMLValue(psIter, "Title", nullptr);
695                 if (osMatch.empty())
696                 {
697                     AddTiledSubDataset(pszName, pszTitle, papszChanges);
698                 }
699                 else
700                 {
701                     CPLString osNameUpper(pszName);
702                     osNameUpper.toupper();
703                     if (std::string::npos != osNameUpper.find(osMatch))
704                         AddTiledSubDataset(pszName, pszTitle, papszChanges);
705                 }
706             }
707         }
708         else if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TiledGroups"))
709         {
710             AnalyzeGetTileServiceRecurse(psIter, poOpenInfo);
711         }
712     }
713     CPLFree(papszChanges);
714 }
715 
716 /************************************************************************/
717 /*                        AnalyzeGetTileService()                       */
718 /************************************************************************/
719 // tiledWMS only
AnalyzeGetTileService(CPLXMLNode * psXML,GDALOpenInfo * poOpenInfo)720 GDALDataset* GDALWMSMetaDataset::AnalyzeGetTileService(CPLXMLNode* psXML, GDALOpenInfo * poOpenInfo)
721 {
722     const char* pszEncoding = nullptr;
723     if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
724         pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
725 
726     CPLXMLNode* psRoot = CPLGetXMLNode( psXML, "=WMS_Tile_Service" );
727     if (psRoot == nullptr)
728         return nullptr;
729     CPLXMLNode* psTiledPatterns = CPLGetXMLNode(psRoot, "TiledPatterns");
730     if (psTiledPatterns == nullptr)
731         return nullptr;
732 
733     const char* pszURL = CPLGetXMLValue(psTiledPatterns,
734                                         "OnlineResource.xlink:href", nullptr);
735     if (pszURL == nullptr)
736         return nullptr;
737 
738     GDALWMSMetaDataset* poDS = new GDALWMSMetaDataset();
739     poDS->osGetURL = pszURL;
740     poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
741 
742     poDS->AnalyzeGetTileServiceRecurse(psTiledPatterns, poOpenInfo);
743 
744     return poDS;
745 }
746 
747 /************************************************************************/
748 /*                        AnalyzeTileMapService()                       */
749 /************************************************************************/
750 
AnalyzeTileMapService(CPLXMLNode * psXML)751 GDALDataset* GDALWMSMetaDataset::AnalyzeTileMapService(CPLXMLNode* psXML)
752 {
753     CPLXMLNode* psRoot = CPLGetXMLNode( psXML, "=TileMapService" );
754     if (psRoot == nullptr)
755         return nullptr;
756     CPLXMLNode* psTileMaps = CPLGetXMLNode(psRoot, "TileMaps");
757     if (psTileMaps == nullptr)
758         return nullptr;
759 
760     GDALWMSMetaDataset* poDS = new GDALWMSMetaDataset();
761 
762     CPLXMLNode* psIter = psTileMaps->psChild;
763     for(; psIter != nullptr; psIter = psIter->psNext)
764     {
765         if (psIter->eType == CXT_Element &&
766             EQUAL(psIter->pszValue, "TileMap"))
767         {
768             const char* pszHref = CPLGetXMLValue(psIter, "href", nullptr);
769             const char* pszTitle = CPLGetXMLValue(psIter, "title", nullptr);
770             if (pszHref && pszTitle)
771             {
772                 CPLString osHref(pszHref);
773                 const char* pszDup100 = strstr(pszHref, "1.0.0/1.0.0/");
774                 if (pszDup100)
775                 {
776                     osHref.resize(pszDup100 - pszHref);
777                     osHref += pszDup100 + strlen("1.0.0/");
778                 }
779                 poDS->AddSubDataset(osHref, pszTitle);
780             }
781         }
782     }
783 
784     return poDS;
785 }
786