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