Line data Source code
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 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "wmsmetadataset.h"
14 :
15 : #include "gdal_openinfo.h"
16 :
17 : int VersionStringToInt(const char *version);
18 :
19 : /************************************************************************/
20 : /* GDALWMSMetaDataset() */
21 : /************************************************************************/
22 :
23 : GDALWMSMetaDataset::GDALWMSMetaDataset() = default;
24 :
25 : /************************************************************************/
26 : /* ~GDALWMSMetaDataset() */
27 : /************************************************************************/
28 :
29 8 : GDALWMSMetaDataset::~GDALWMSMetaDataset()
30 : {
31 4 : CSLDestroy(papszSubDatasets);
32 8 : }
33 :
34 : /************************************************************************/
35 : /* AddSubDataset() */
36 : /************************************************************************/
37 :
38 14 : void GDALWMSMetaDataset::AddSubDataset(const char *pszName, const char *pszDesc)
39 : {
40 : char szName[80];
41 14 : int nCount = CSLCount(papszSubDatasets) / 2;
42 :
43 14 : snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
44 14 : papszSubDatasets = CSLSetNameValue(papszSubDatasets, szName, pszName);
45 :
46 14 : snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
47 14 : papszSubDatasets = CSLSetNameValue(papszSubDatasets, szName, pszDesc);
48 14 : }
49 :
50 : /************************************************************************/
51 : /* DownloadGetCapabilities() */
52 : /************************************************************************/
53 :
54 : GDALDataset *
55 1 : GDALWMSMetaDataset::DownloadGetCapabilities(GDALOpenInfo *poOpenInfo)
56 : {
57 1 : const char *pszURL = poOpenInfo->pszFilename;
58 1 : if (STARTS_WITH_CI(pszURL, "WMS:"))
59 0 : pszURL += 4;
60 :
61 2 : CPLString osFormat = CPLURLGetValue(pszURL, "FORMAT");
62 2 : CPLString osTransparent = CPLURLGetValue(pszURL, "TRANSPARENT");
63 2 : CPLString osVersion = CPLURLGetValue(pszURL, "VERSION");
64 2 : CPLString osPreferredSRS = CPLURLGetValue(pszURL, "SRS");
65 1 : if (osPreferredSRS.empty())
66 1 : osPreferredSRS = CPLURLGetValue(pszURL, "CRS");
67 :
68 1 : if (osVersion.empty())
69 1 : osVersion = "1.1.1";
70 :
71 2 : CPLString osURL(pszURL);
72 1 : osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
73 1 : osURL = CPLURLAddKVP(osURL, "VERSION", osVersion);
74 1 : osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
75 : /* Remove all other keywords */
76 1 : osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
77 1 : osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
78 1 : osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
79 1 : osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
80 1 : osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
81 1 : osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
82 1 : osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
83 1 : osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
84 1 : osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
85 :
86 1 : CPLHTTPResult *psResult = CPLHTTPFetch(osURL, nullptr);
87 1 : if (psResult == nullptr)
88 : {
89 0 : return nullptr;
90 : }
91 1 : if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
92 : {
93 0 : CPLError(CE_Failure, CPLE_AppDefined,
94 : "Error returned by server : %s (%d)",
95 0 : (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
96 : psResult->nStatus);
97 0 : CPLHTTPDestroyResult(psResult);
98 0 : return nullptr;
99 : }
100 1 : if (psResult->pabyData == nullptr)
101 : {
102 0 : CPLError(CE_Failure, CPLE_AppDefined,
103 : "Empty content returned by server");
104 0 : CPLHTTPDestroyResult(psResult);
105 0 : return nullptr;
106 : }
107 :
108 : CPLXMLNode *psXML =
109 1 : CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
110 1 : if (psXML == nullptr)
111 : {
112 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
113 : psResult->pabyData);
114 0 : CPLHTTPDestroyResult(psResult);
115 0 : return nullptr;
116 : }
117 :
118 : GDALDataset *poRet =
119 1 : AnalyzeGetCapabilities(psXML, osFormat, osTransparent, osPreferredSRS);
120 :
121 1 : CPLHTTPDestroyResult(psResult);
122 1 : CPLDestroyXMLNode(psXML);
123 :
124 1 : return poRet;
125 : }
126 :
127 : /************************************************************************/
128 : /* DownloadGetTileService() */
129 : /************************************************************************/
130 :
131 : GDALDataset *
132 0 : GDALWMSMetaDataset::DownloadGetTileService(GDALOpenInfo *poOpenInfo)
133 : {
134 0 : const char *pszURL = poOpenInfo->pszFilename;
135 0 : if (STARTS_WITH_CI(pszURL, "WMS:"))
136 0 : pszURL += 4;
137 :
138 0 : CPLString osURL(pszURL);
139 0 : osURL = CPLURLAddKVP(osURL, "SERVICE", "WMS");
140 0 : osURL = CPLURLAddKVP(osURL, "REQUEST", "GetTileService");
141 : /* Remove all other keywords */
142 0 : osURL = CPLURLAddKVP(osURL, "VERSION", nullptr);
143 0 : osURL = CPLURLAddKVP(osURL, "LAYERS", nullptr);
144 0 : osURL = CPLURLAddKVP(osURL, "SRS", nullptr);
145 0 : osURL = CPLURLAddKVP(osURL, "CRS", nullptr);
146 0 : osURL = CPLURLAddKVP(osURL, "BBOX", nullptr);
147 0 : osURL = CPLURLAddKVP(osURL, "FORMAT", nullptr);
148 0 : osURL = CPLURLAddKVP(osURL, "TRANSPARENT", nullptr);
149 0 : osURL = CPLURLAddKVP(osURL, "STYLES", nullptr);
150 0 : osURL = CPLURLAddKVP(osURL, "WIDTH", nullptr);
151 0 : osURL = CPLURLAddKVP(osURL, "HEIGHT", nullptr);
152 :
153 0 : CPLHTTPResult *psResult = CPLHTTPFetch(osURL, nullptr);
154 0 : if (psResult == nullptr)
155 : {
156 0 : return nullptr;
157 : }
158 0 : if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
159 : {
160 0 : CPLError(CE_Failure, CPLE_AppDefined,
161 : "Error returned by server : %s (%d)",
162 0 : (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
163 : psResult->nStatus);
164 0 : CPLHTTPDestroyResult(psResult);
165 0 : return nullptr;
166 : }
167 0 : if (psResult->pabyData == nullptr)
168 : {
169 0 : CPLError(CE_Failure, CPLE_AppDefined,
170 : "Empty content returned by server");
171 0 : CPLHTTPDestroyResult(psResult);
172 0 : return nullptr;
173 : }
174 :
175 : CPLXMLNode *psXML =
176 0 : CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
177 0 : if (psXML == nullptr)
178 : {
179 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
180 : psResult->pabyData);
181 0 : CPLHTTPDestroyResult(psResult);
182 0 : return nullptr;
183 : }
184 :
185 0 : GDALDataset *poRet = AnalyzeGetTileService(psXML, poOpenInfo);
186 :
187 0 : CPLHTTPDestroyResult(psResult);
188 0 : CPLDestroyXMLNode(psXML);
189 :
190 0 : return poRet;
191 : }
192 :
193 : /************************************************************************/
194 : /* GetMetadataDomainList() */
195 : /************************************************************************/
196 :
197 0 : char **GDALWMSMetaDataset::GetMetadataDomainList()
198 : {
199 0 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
200 0 : TRUE, GDAL_MDD_SUBDATASETS, nullptr);
201 : }
202 :
203 : /************************************************************************/
204 : /* GetMetadata() */
205 : /************************************************************************/
206 :
207 3 : CSLConstList GDALWMSMetaDataset::GetMetadata(const char *pszDomain)
208 :
209 : {
210 3 : if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
211 3 : return papszSubDatasets;
212 :
213 0 : return GDALPamDataset::GetMetadata(pszDomain);
214 : }
215 :
216 : /************************************************************************/
217 : /* AddSubDataset() */
218 : /************************************************************************/
219 :
220 10 : void GDALWMSMetaDataset::AddSubDataset(
221 : const char *pszLayerName, const char *pszTitle,
222 : CPL_UNUSED const char *pszAbstract, const char *pszSRS, const char *pszMinX,
223 : const char *pszMinY, const char *pszMaxX, const char *pszMaxY,
224 : const std::string &osFormat, const std::string &osTransparent)
225 : {
226 20 : CPLString osSubdatasetName = "WMS:";
227 10 : osSubdatasetName += osGetURL;
228 10 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SERVICE", "WMS");
229 10 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "VERSION", osVersion);
230 10 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "REQUEST", "GetMap");
231 10 : char *pszEscapedLayerName = CPLEscapeString(pszLayerName, -1, CPLES_URL);
232 : osSubdatasetName =
233 10 : CPLURLAddKVP(osSubdatasetName, "LAYERS", pszEscapedLayerName);
234 10 : CPLFree(pszEscapedLayerName);
235 10 : if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
236 : {
237 10 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "CRS", pszSRS);
238 : }
239 : else
240 0 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SRS", pszSRS);
241 20 : osSubdatasetName = CPLURLAddKVP(
242 : osSubdatasetName, "BBOX",
243 10 : CPLSPrintf("%s,%s,%s,%s", pszMinX, pszMinY, pszMaxX, pszMaxY));
244 10 : if (!osFormat.empty())
245 : osSubdatasetName =
246 0 : CPLURLAddKVP(osSubdatasetName, "FORMAT", osFormat.c_str());
247 10 : if (!osTransparent.empty())
248 0 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TRANSPARENT",
249 0 : osTransparent.c_str());
250 :
251 10 : if (pszTitle)
252 : {
253 20 : if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
254 10 : osXMLEncoding != "UTF-8")
255 : {
256 : char *pszRecodedTitle =
257 0 : CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
258 0 : if (pszRecodedTitle)
259 0 : AddSubDataset(osSubdatasetName, pszRecodedTitle);
260 : else
261 0 : AddSubDataset(osSubdatasetName, pszTitle);
262 0 : CPLFree(pszRecodedTitle);
263 : }
264 : else
265 : {
266 10 : AddSubDataset(osSubdatasetName, pszTitle);
267 : }
268 : }
269 : else
270 : {
271 0 : AddSubDataset(osSubdatasetName, pszLayerName);
272 : }
273 10 : }
274 :
275 : /************************************************************************/
276 : /* AddWMSCSubDataset() */
277 : /************************************************************************/
278 :
279 0 : void GDALWMSMetaDataset::AddWMSCSubDataset(WMSCTileSetDesc &oWMSCTileSetDesc,
280 : const char *pszTitle,
281 : const CPLString &osTransparent)
282 : {
283 0 : CPLString osSubdatasetName = "WMS:";
284 0 : osSubdatasetName += osGetURL;
285 0 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "SERVICE", "WMS");
286 0 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "VERSION", osVersion);
287 0 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "REQUEST", "GetMap");
288 : osSubdatasetName =
289 0 : CPLURLAddKVP(osSubdatasetName, "LAYERS", oWMSCTileSetDesc.osLayers);
290 0 : if (VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0"))
291 : osSubdatasetName =
292 0 : CPLURLAddKVP(osSubdatasetName, "CRS", oWMSCTileSetDesc.osSRS);
293 : else
294 : osSubdatasetName =
295 0 : CPLURLAddKVP(osSubdatasetName, "SRS", oWMSCTileSetDesc.osSRS);
296 : osSubdatasetName =
297 0 : CPLURLAddKVP(osSubdatasetName, "BBOX",
298 : CPLSPrintf("%s,%s,%s,%s", oWMSCTileSetDesc.osMinX.c_str(),
299 : oWMSCTileSetDesc.osMinY.c_str(),
300 : oWMSCTileSetDesc.osMaxX.c_str(),
301 0 : oWMSCTileSetDesc.osMaxY.c_str()));
302 :
303 : osSubdatasetName =
304 0 : CPLURLAddKVP(osSubdatasetName, "FORMAT", oWMSCTileSetDesc.osFormat);
305 0 : if (!osTransparent.empty())
306 : osSubdatasetName =
307 0 : CPLURLAddKVP(osSubdatasetName, "TRANSPARENT", osTransparent);
308 0 : if (oWMSCTileSetDesc.nTileWidth != oWMSCTileSetDesc.nTileHeight)
309 0 : CPLDebug("WMS", "Weird: nTileWidth != nTileHeight for %s",
310 : oWMSCTileSetDesc.osLayers.c_str());
311 : osSubdatasetName =
312 0 : CPLURLAddKVP(osSubdatasetName, "TILESIZE",
313 0 : CPLSPrintf("%d", oWMSCTileSetDesc.nTileWidth));
314 : osSubdatasetName =
315 0 : CPLURLAddKVP(osSubdatasetName, "OVERVIEWCOUNT",
316 0 : CPLSPrintf("%d", oWMSCTileSetDesc.nResolutions - 1));
317 : osSubdatasetName =
318 0 : CPLURLAddKVP(osSubdatasetName, "MINRESOLUTION",
319 0 : CPLSPrintf("%.16f", oWMSCTileSetDesc.dfMinResolution));
320 0 : osSubdatasetName = CPLURLAddKVP(osSubdatasetName, "TILED", "true");
321 :
322 0 : if (pszTitle)
323 : {
324 0 : if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
325 0 : osXMLEncoding != "UTF-8")
326 : {
327 : char *pszRecodedTitle =
328 0 : CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
329 0 : if (pszRecodedTitle)
330 0 : AddSubDataset(osSubdatasetName, pszRecodedTitle);
331 : else
332 0 : AddSubDataset(osSubdatasetName, pszTitle);
333 0 : CPLFree(pszRecodedTitle);
334 : }
335 : else
336 : {
337 0 : AddSubDataset(osSubdatasetName, pszTitle);
338 : }
339 : }
340 : else
341 : {
342 0 : AddSubDataset(osSubdatasetName, oWMSCTileSetDesc.osLayers);
343 : }
344 0 : }
345 :
346 : /************************************************************************/
347 : /* ExploreLayer() */
348 : /************************************************************************/
349 :
350 10 : void GDALWMSMetaDataset::ExploreLayer(CPLXMLNode *psXML,
351 : const CPLString &osFormat,
352 : const CPLString &osTransparent,
353 : const CPLString &osPreferredSRS,
354 : const char *pszSRS, const char *pszMinX,
355 : const char *pszMinY, const char *pszMaxX,
356 : const char *pszMaxY)
357 : {
358 10 : const char *pszName = CPLGetXMLValue(psXML, "Name", nullptr);
359 10 : const char *pszTitle = CPLGetXMLValue(psXML, "Title", nullptr);
360 10 : const char *pszAbstract = CPLGetXMLValue(psXML, "Abstract", nullptr);
361 :
362 10 : CPLXMLNode *psSRS = nullptr;
363 10 : const char *pszSRSLocal = nullptr;
364 10 : const char *pszMinXLocal = nullptr;
365 10 : const char *pszMinYLocal = nullptr;
366 10 : const char *pszMaxXLocal = nullptr;
367 10 : const char *pszMaxYLocal = nullptr;
368 :
369 : const bool bIsWMS130 =
370 10 : VersionStringToInt(osVersion.c_str()) >= VersionStringToInt("1.3.0");
371 :
372 20 : const auto GetCRS = [bIsWMS130](const CPLXMLNode *psNode)
373 : {
374 : const char *pszVal =
375 15 : CPLGetXMLValue(psNode, bIsWMS130 ? "CRS" : "SRS", nullptr);
376 15 : if (!pszVal && !bIsWMS130)
377 : {
378 : // Some WMS servers have a non-conformant WMS 1.1.1 implementation
379 : // that use "CRS" instead of "SRS"
380 : // like https://mapy.geoportal.gov.pl/wss/service/PZGIK/BDOO/WMS/aktualne?service=wms&request=getcapabilities
381 0 : pszVal = CPLGetXMLValue(psNode, "CRS", nullptr);
382 0 : if (pszVal)
383 : {
384 : static bool bWarned = false;
385 0 : if (!bWarned)
386 : {
387 0 : bWarned = true;
388 0 : CPLError(CE_Warning, CPLE_AppDefined,
389 : "WMS server uses non-standard CRS attribute for "
390 : "WMS 1.1.0. Things might mis-behave. Perhaps try "
391 : "using VERSION=1.3.0");
392 : }
393 : }
394 : }
395 15 : return pszVal;
396 10 : };
397 :
398 : /* Use local bounding box if available, otherwise use the one */
399 : /* that comes from an upper layer */
400 : /* such as in http://neowms.sci.gsfc.nasa.gov/wms/wms */
401 10 : CPLXMLNode *psIter = psXML->psChild;
402 303 : while (psIter != nullptr)
403 : {
404 298 : if (psIter->eType == CXT_Element &&
405 280 : strcmp(psIter->pszValue, "BoundingBox") == 0)
406 : {
407 5 : psSRS = psIter;
408 5 : pszSRSLocal = GetCRS(psSRS);
409 5 : if (osPreferredSRS.empty() || pszSRSLocal == nullptr)
410 5 : break;
411 0 : if (EQUAL(osPreferredSRS, pszSRSLocal))
412 0 : break;
413 0 : psSRS = nullptr;
414 0 : pszSRSLocal = nullptr;
415 : }
416 293 : psIter = psIter->psNext;
417 : }
418 :
419 10 : if (psSRS == nullptr)
420 : {
421 5 : psSRS = CPLGetXMLNode(psXML, "LatLonBoundingBox");
422 5 : pszSRSLocal = GetCRS(psSRS);
423 5 : if (pszSRSLocal == nullptr)
424 5 : pszSRSLocal = "EPSG:4326";
425 : }
426 :
427 10 : if (pszSRSLocal != nullptr && psSRS != nullptr)
428 : {
429 5 : pszMinXLocal = CPLGetXMLValue(psSRS, "minx", nullptr);
430 5 : pszMinYLocal = CPLGetXMLValue(psSRS, "miny", nullptr);
431 5 : pszMaxXLocal = CPLGetXMLValue(psSRS, "maxx", nullptr);
432 5 : pszMaxYLocal = CPLGetXMLValue(psSRS, "maxy", nullptr);
433 :
434 5 : if (pszMinXLocal && pszMinYLocal && pszMaxXLocal && pszMaxYLocal)
435 : {
436 5 : pszSRS = pszSRSLocal;
437 5 : pszMinX = pszMinXLocal;
438 5 : pszMinY = pszMinYLocal;
439 5 : pszMaxX = pszMaxXLocal;
440 5 : pszMaxY = pszMaxYLocal;
441 : }
442 : }
443 :
444 20 : std::string osMinX, osMinY, osMaxX, osMaxY;
445 10 : if (psSRS == nullptr)
446 : {
447 : const auto psExGeographicBoundingBox =
448 5 : CPLGetXMLNode(psXML, "EX_GeographicBoundingBox");
449 5 : if (psExGeographicBoundingBox)
450 : {
451 5 : pszMinXLocal = CPLGetXMLValue(psExGeographicBoundingBox,
452 : "westBoundLongitude", nullptr);
453 5 : pszMinYLocal = CPLGetXMLValue(psExGeographicBoundingBox,
454 : "southBoundLatitude", nullptr);
455 5 : pszMaxXLocal = CPLGetXMLValue(psExGeographicBoundingBox,
456 : "eastBoundLongitude", nullptr);
457 5 : pszMaxYLocal = CPLGetXMLValue(psExGeographicBoundingBox,
458 : "northBoundLatitude", nullptr);
459 :
460 5 : if (pszMinXLocal && pszMinYLocal && pszMaxXLocal && pszMaxYLocal)
461 : {
462 5 : pszSRS = "EPSG:4326";
463 5 : pszMinX = pszMinXLocal;
464 5 : pszMinY = pszMinYLocal;
465 5 : pszMaxX = pszMaxXLocal;
466 5 : pszMaxY = pszMaxYLocal;
467 :
468 : // Try to reproject the bbox to the default CRS
469 5 : pszSRSLocal = GetCRS(psXML);
470 5 : if (pszSRSLocal)
471 : {
472 10 : OGRSpatialReference oSrcSRS;
473 5 : oSrcSRS.importFromEPSG(4326);
474 5 : oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
475 :
476 10 : OGRSpatialReference oDstSRS;
477 5 : oDstSRS.SetFromUserInput(
478 : pszSRSLocal, OGRSpatialReference::
479 : SET_FROM_USER_INPUT_LIMITATIONS_get());
480 5 : oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
481 :
482 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
483 10 : OGRCreateCoordinateTransformation(&oSrcSRS, &oDstSRS));
484 5 : double dfMinX = 0;
485 5 : double dfMinY = 0;
486 5 : double dfMaxX = 0;
487 5 : double dfMaxY = 0;
488 10 : if (poCT &&
489 10 : poCT->TransformBounds(
490 : CPLAtof(pszMinXLocal), CPLAtof(pszMinYLocal),
491 : CPLAtof(pszMaxXLocal), CPLAtof(pszMaxYLocal),
492 : &dfMinX, &dfMinY, &dfMaxX, &dfMaxY,
493 10 : /* densify_pts = */ 21))
494 : {
495 5 : osMinX = CPLSPrintf("%.17g", dfMinX);
496 5 : osMinY = CPLSPrintf("%.17g", dfMinY);
497 5 : osMaxX = CPLSPrintf("%.17g", dfMaxX);
498 5 : osMaxY = CPLSPrintf("%.17g", dfMaxY);
499 5 : pszSRS = pszSRSLocal;
500 5 : pszMinX = osMinX.c_str();
501 5 : pszMinY = osMinY.c_str();
502 5 : pszMaxX = osMaxX.c_str();
503 5 : pszMaxY = osMaxY.c_str();
504 : }
505 : }
506 : }
507 : }
508 : }
509 :
510 10 : if (pszName != nullptr && pszSRS && pszMinX && pszMinY && pszMaxX &&
511 : pszMaxY)
512 : {
513 20 : CPLString osLocalTransparent(osTransparent);
514 10 : if (osLocalTransparent.empty())
515 : {
516 10 : const char *pszOpaque = CPLGetXMLValue(psXML, "opaque", "0");
517 10 : if (EQUAL(pszOpaque, "1"))
518 0 : osLocalTransparent = "FALSE";
519 : }
520 :
521 20 : WMSCKeyType oWMSCKey(pszName, pszSRS);
522 : std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter =
523 10 : osMapWMSCTileSet.find(oWMSCKey);
524 10 : if (oIter != osMapWMSCTileSet.end())
525 : {
526 0 : AddWMSCSubDataset(oIter->second, pszTitle, osLocalTransparent);
527 : }
528 : else
529 : {
530 10 : AddSubDataset(pszName, pszTitle, pszAbstract, pszSRS, pszMinX,
531 : pszMinY, pszMaxX, pszMaxY, osFormat,
532 : osLocalTransparent);
533 : }
534 : }
535 :
536 10 : psIter = psXML->psChild;
537 320 : for (; psIter != nullptr; psIter = psIter->psNext)
538 : {
539 310 : if (psIter->eType == CXT_Element)
540 : {
541 292 : if (EQUAL(psIter->pszValue, "Layer"))
542 8 : ExploreLayer(psIter, osFormat, osTransparent, osPreferredSRS,
543 : pszSRS, pszMinX, pszMinY, pszMaxX, pszMaxY);
544 : }
545 : }
546 10 : }
547 :
548 : /************************************************************************/
549 : /* ParseWMSCTileSets() */
550 : /************************************************************************/
551 :
552 0 : void GDALWMSMetaDataset::ParseWMSCTileSets(CPLXMLNode *psXML)
553 : {
554 0 : CPLXMLNode *psIter = psXML->psChild;
555 0 : for (; psIter; psIter = psIter->psNext)
556 : {
557 0 : if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileSet"))
558 : {
559 0 : const char *pszSRS = CPLGetXMLValue(psIter, "SRS", nullptr);
560 0 : if (pszSRS == nullptr)
561 0 : continue;
562 :
563 0 : CPLXMLNode *psBoundingBox = CPLGetXMLNode(psIter, "BoundingBox");
564 0 : if (psBoundingBox == nullptr)
565 0 : continue;
566 :
567 : const char *pszMinX =
568 0 : CPLGetXMLValue(psBoundingBox, "minx", nullptr);
569 : const char *pszMinY =
570 0 : CPLGetXMLValue(psBoundingBox, "miny", nullptr);
571 : const char *pszMaxX =
572 0 : CPLGetXMLValue(psBoundingBox, "maxx", nullptr);
573 : const char *pszMaxY =
574 0 : CPLGetXMLValue(psBoundingBox, "maxy", nullptr);
575 0 : if (pszMinX == nullptr || pszMinY == nullptr ||
576 0 : pszMaxX == nullptr || pszMaxY == nullptr)
577 0 : continue;
578 :
579 0 : double dfMinX = CPLAtofM(pszMinX);
580 0 : double dfMinY = CPLAtofM(pszMinY);
581 0 : double dfMaxX = CPLAtofM(pszMaxX);
582 0 : double dfMaxY = CPLAtofM(pszMaxY);
583 0 : if (dfMaxY <= dfMinY || dfMaxX <= dfMinX)
584 0 : continue;
585 :
586 0 : const char *pszFormat = CPLGetXMLValue(psIter, "Format", nullptr);
587 0 : if (pszFormat == nullptr)
588 0 : continue;
589 0 : if (strstr(pszFormat, "kml"))
590 0 : continue;
591 :
592 0 : const char *pszTileWidth = CPLGetXMLValue(psIter, "Width", nullptr);
593 : const char *pszTileHeight =
594 0 : CPLGetXMLValue(psIter, "Height", nullptr);
595 0 : if (pszTileWidth == nullptr || pszTileHeight == nullptr)
596 0 : continue;
597 :
598 0 : int nTileWidth = atoi(pszTileWidth);
599 0 : int nTileHeight = atoi(pszTileHeight);
600 0 : if (nTileWidth < 128 || nTileHeight < 128)
601 0 : continue;
602 :
603 0 : const char *pszLayers = CPLGetXMLValue(psIter, "Layers", nullptr);
604 0 : if (pszLayers == nullptr)
605 0 : continue;
606 :
607 : const char *pszResolutions =
608 0 : CPLGetXMLValue(psIter, "Resolutions", nullptr);
609 0 : if (pszResolutions == nullptr)
610 0 : continue;
611 : char **papszTokens =
612 0 : CSLTokenizeStringComplex(pszResolutions, " ", 0, 0);
613 0 : double dfMinResolution = 0;
614 : int i;
615 0 : for (i = 0; papszTokens && papszTokens[i]; i++)
616 : {
617 0 : double dfResolution = CPLAtofM(papszTokens[i]);
618 0 : if (i == 0 || dfResolution < dfMinResolution)
619 0 : dfMinResolution = dfResolution;
620 : }
621 0 : CSLDestroy(papszTokens);
622 0 : int nResolutions = i;
623 0 : if (nResolutions == 0)
624 0 : continue;
625 :
626 0 : const char *pszStyles = CPLGetXMLValue(psIter, "Styles", "");
627 :
628 : /* http://demo.opengeo.org/geoserver/gwc/service/wms?tiled=TRUE&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities
629 : */
630 : /* has different variations of formats for the same (formats, SRS)
631 : * tuple, so just */
632 : /* keep the first one which is a png format */
633 0 : WMSCKeyType oWMSCKey(pszLayers, pszSRS);
634 : std::map<WMSCKeyType, WMSCTileSetDesc>::iterator oIter =
635 0 : osMapWMSCTileSet.find(oWMSCKey);
636 0 : if (oIter != osMapWMSCTileSet.end())
637 0 : continue;
638 :
639 0 : WMSCTileSetDesc oWMSCTileSet;
640 0 : oWMSCTileSet.osLayers = pszLayers;
641 0 : oWMSCTileSet.osSRS = pszSRS;
642 0 : oWMSCTileSet.osMinX = pszMinX;
643 0 : oWMSCTileSet.osMinY = pszMinY;
644 0 : oWMSCTileSet.osMaxX = pszMaxX;
645 0 : oWMSCTileSet.osMaxY = pszMaxY;
646 0 : oWMSCTileSet.dfMinX = dfMinX;
647 0 : oWMSCTileSet.dfMinY = dfMinY;
648 0 : oWMSCTileSet.dfMaxX = dfMaxX;
649 0 : oWMSCTileSet.dfMaxY = dfMaxY;
650 0 : oWMSCTileSet.nResolutions = nResolutions;
651 0 : oWMSCTileSet.dfMinResolution = dfMinResolution;
652 0 : oWMSCTileSet.osFormat = pszFormat;
653 0 : oWMSCTileSet.osStyle = pszStyles;
654 0 : oWMSCTileSet.nTileWidth = nTileWidth;
655 0 : oWMSCTileSet.nTileHeight = nTileHeight;
656 :
657 0 : osMapWMSCTileSet[oWMSCKey] = std::move(oWMSCTileSet);
658 : }
659 : }
660 0 : }
661 :
662 : /************************************************************************/
663 : /* AnalyzeGetCapabilities() */
664 : /************************************************************************/
665 :
666 2 : GDALDataset *GDALWMSMetaDataset::AnalyzeGetCapabilities(
667 : CPLXMLNode *psXML, const std::string &osFormat,
668 : const std::string &osTransparent, const std::string &osPreferredSRS)
669 : {
670 2 : const char *pszEncoding = nullptr;
671 2 : if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
672 2 : pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
673 :
674 2 : CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=WMT_MS_Capabilities");
675 2 : if (psRoot == nullptr)
676 2 : psRoot = CPLGetXMLNode(psXML, "=WMS_Capabilities");
677 2 : if (psRoot == nullptr)
678 0 : return nullptr;
679 2 : CPLXMLNode *psCapability = CPLGetXMLNode(psRoot, "Capability");
680 2 : if (psCapability == nullptr)
681 0 : return nullptr;
682 :
683 2 : CPLXMLNode *psOnlineResource = CPLGetXMLNode(
684 : psCapability, "Request.GetMap.DCPType.HTTP.Get.OnlineResource");
685 2 : if (psOnlineResource == nullptr)
686 0 : return nullptr;
687 : const char *pszGetURL =
688 2 : CPLGetXMLValue(psOnlineResource, "xlink:href", nullptr);
689 2 : if (pszGetURL == nullptr)
690 0 : return nullptr;
691 :
692 2 : CPLXMLNode *psLayer = CPLGetXMLNode(psCapability, "Layer");
693 2 : if (psLayer == nullptr)
694 0 : return nullptr;
695 :
696 : CPLXMLNode *psVendorSpecificCapabilities =
697 2 : CPLGetXMLNode(psCapability, "VendorSpecificCapabilities");
698 :
699 2 : GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
700 2 : const char *pszVersion = CPLGetXMLValue(psRoot, "version", nullptr);
701 2 : if (pszVersion)
702 2 : poDS->osVersion = pszVersion;
703 : else
704 0 : poDS->osVersion = "1.1.1";
705 2 : poDS->osGetURL = pszGetURL;
706 2 : poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
707 2 : if (psVendorSpecificCapabilities)
708 0 : poDS->ParseWMSCTileSets(psVendorSpecificCapabilities);
709 2 : poDS->ExploreLayer(psLayer, osFormat, osTransparent, osPreferredSRS);
710 :
711 2 : return poDS;
712 : }
713 :
714 : /************************************************************************/
715 : /* AddTiledSubDataset() */
716 : /************************************************************************/
717 :
718 : // tiledWMS only
719 4 : void GDALWMSMetaDataset::AddTiledSubDataset(const char *pszTiledGroupName,
720 : const char *pszTitle,
721 : const char *const *papszChanges)
722 : {
723 : CPLString osSubdatasetName =
724 8 : "<GDAL_WMS><Service name=\"TiledWMS\"><ServerUrl>";
725 4 : osSubdatasetName += osGetURL;
726 4 : osSubdatasetName += "</ServerUrl><TiledGroupName>";
727 4 : osSubdatasetName += pszTiledGroupName;
728 4 : osSubdatasetName += "</TiledGroupName>";
729 :
730 4 : for (int i = 0; papszChanges != nullptr && papszChanges[i] != nullptr; i++)
731 : {
732 0 : char *key = nullptr;
733 0 : const char *value = CPLParseNameValue(papszChanges[i], &key);
734 0 : if (value != nullptr && key != nullptr)
735 : osSubdatasetName +=
736 0 : CPLSPrintf("<Change key=\"${%s}\">%s</Change>", key, value);
737 0 : CPLFree(key);
738 : }
739 :
740 4 : osSubdatasetName += "</Service></GDAL_WMS>";
741 :
742 4 : if (pszTitle)
743 : {
744 4 : if (!osXMLEncoding.empty() && osXMLEncoding != "utf-8" &&
745 0 : osXMLEncoding != "UTF-8")
746 : {
747 : char *pszRecodedTitle =
748 0 : CPLRecode(pszTitle, osXMLEncoding.c_str(), CPL_ENC_UTF8);
749 0 : if (pszRecodedTitle)
750 0 : AddSubDataset(osSubdatasetName, pszRecodedTitle);
751 : else
752 0 : AddSubDataset(osSubdatasetName, pszTitle);
753 0 : CPLFree(pszRecodedTitle);
754 : }
755 : else
756 : {
757 4 : AddSubDataset(osSubdatasetName, pszTitle);
758 : }
759 : }
760 : else
761 : {
762 0 : AddSubDataset(osSubdatasetName, pszTiledGroupName);
763 : }
764 4 : }
765 :
766 : /************************************************************************/
767 : /* AnalyzeGetTileServiceRecurse() */
768 : /************************************************************************/
769 : // tiledWMS only
770 2 : void GDALWMSMetaDataset::AnalyzeGetTileServiceRecurse(CPLXMLNode *psXML,
771 : GDALOpenInfo *poOpenInfo)
772 : {
773 : // Only list tiled groups that contain the string in the open option
774 : // TiledGroupName, if given
775 2 : char **papszLocalOpenOptions =
776 2 : poOpenInfo ? poOpenInfo->papszOpenOptions : nullptr;
777 : CPLString osMatch(
778 4 : CSLFetchNameValueDef(papszLocalOpenOptions, "TiledGroupName", ""));
779 2 : osMatch.toupper();
780 : // Also pass the change patterns, if provided
781 : char **papszChanges =
782 2 : CSLFetchNameValueMultiple(papszLocalOpenOptions, "Change");
783 :
784 2 : CPLXMLNode *psIter = psXML->psChild;
785 10 : for (; psIter != nullptr; psIter = psIter->psNext)
786 : {
787 8 : if (psIter->eType == CXT_Element &&
788 8 : EQUAL(psIter->pszValue, "TiledGroup"))
789 : {
790 4 : const char *pszName = CPLGetXMLValue(psIter, "Name", nullptr);
791 4 : if (pszName)
792 : {
793 4 : const char *pszTitle = CPLGetXMLValue(psIter, "Title", nullptr);
794 4 : if (osMatch.empty())
795 : {
796 4 : AddTiledSubDataset(pszName, pszTitle, papszChanges);
797 : }
798 : else
799 : {
800 0 : CPLString osNameUpper(pszName);
801 0 : osNameUpper.toupper();
802 0 : if (std::string::npos != osNameUpper.find(osMatch))
803 0 : AddTiledSubDataset(pszName, pszTitle, papszChanges);
804 : }
805 4 : }
806 : }
807 4 : else if (psIter->eType == CXT_Element &&
808 4 : EQUAL(psIter->pszValue, "TiledGroups"))
809 : {
810 0 : AnalyzeGetTileServiceRecurse(psIter, poOpenInfo);
811 : }
812 : }
813 2 : CPLFree(papszChanges);
814 2 : }
815 :
816 : /************************************************************************/
817 : /* AnalyzeGetTileService() */
818 : /************************************************************************/
819 : // tiledWMS only
820 2 : GDALDataset *GDALWMSMetaDataset::AnalyzeGetTileService(CPLXMLNode *psXML,
821 : GDALOpenInfo *poOpenInfo)
822 : {
823 2 : const char *pszEncoding = nullptr;
824 2 : if (psXML->eType == CXT_Element && strcmp(psXML->pszValue, "?xml") == 0)
825 0 : pszEncoding = CPLGetXMLValue(psXML, "encoding", nullptr);
826 :
827 2 : CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=WMS_Tile_Service");
828 2 : if (psRoot == nullptr)
829 0 : return nullptr;
830 2 : CPLXMLNode *psTiledPatterns = CPLGetXMLNode(psRoot, "TiledPatterns");
831 2 : if (psTiledPatterns == nullptr)
832 0 : return nullptr;
833 :
834 : const char *pszURL =
835 2 : CPLGetXMLValue(psTiledPatterns, "OnlineResource.xlink:href", nullptr);
836 2 : if (pszURL == nullptr)
837 0 : return nullptr;
838 :
839 2 : GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
840 2 : poDS->osGetURL = pszURL;
841 2 : poDS->osXMLEncoding = pszEncoding ? pszEncoding : "";
842 :
843 2 : poDS->AnalyzeGetTileServiceRecurse(psTiledPatterns, poOpenInfo);
844 :
845 2 : return poDS;
846 : }
847 :
848 : /************************************************************************/
849 : /* AnalyzeTileMapService() */
850 : /************************************************************************/
851 :
852 0 : GDALDataset *GDALWMSMetaDataset::AnalyzeTileMapService(CPLXMLNode *psXML)
853 : {
854 0 : CPLXMLNode *psRoot = CPLGetXMLNode(psXML, "=TileMapService");
855 0 : if (psRoot == nullptr)
856 0 : return nullptr;
857 0 : CPLXMLNode *psTileMaps = CPLGetXMLNode(psRoot, "TileMaps");
858 0 : if (psTileMaps == nullptr)
859 0 : return nullptr;
860 :
861 0 : GDALWMSMetaDataset *poDS = new GDALWMSMetaDataset();
862 :
863 0 : CPLXMLNode *psIter = psTileMaps->psChild;
864 0 : for (; psIter != nullptr; psIter = psIter->psNext)
865 : {
866 0 : if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "TileMap"))
867 : {
868 0 : const char *pszHref = CPLGetXMLValue(psIter, "href", nullptr);
869 0 : const char *pszTitle = CPLGetXMLValue(psIter, "title", nullptr);
870 0 : if (pszHref && pszTitle)
871 : {
872 0 : CPLString osHref(pszHref);
873 0 : const char *pszDup100 = strstr(pszHref, "1.0.0/1.0.0/");
874 0 : if (pszDup100)
875 : {
876 0 : osHref.resize(pszDup100 - pszHref);
877 0 : osHref += pszDup100 + strlen("1.0.0/");
878 : }
879 0 : poDS->AddSubDataset(osHref, pszTitle);
880 : }
881 : }
882 : }
883 :
884 0 : return poDS;
885 : }
|