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