/src/gdal/frmts/wmts/wmtsdataset.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL WMTS driver |
4 | | * Purpose: Implement GDAL WMTS support |
5 | | * Author: Even Rouault, <even dot rouault at spatialys dot com> |
6 | | * Funded by Land Information New Zealand (LINZ) |
7 | | * |
8 | | ********************************************************************** |
9 | | * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_http.h" |
15 | | #include "cpl_minixml.h" |
16 | | #include "gdal_frmts.h" |
17 | | #include "gdal_pam.h" |
18 | | #include "ogr_spatialref.h" |
19 | | #include "../vrt/gdal_vrt.h" |
20 | | #include "wmtsdrivercore.h" |
21 | | |
22 | | #include <algorithm> |
23 | | #include <array> |
24 | | #include <cmath> |
25 | | #include <map> |
26 | | #include <set> |
27 | | #include <vector> |
28 | | #include <limits> |
29 | | |
30 | | // g++ -g -Wall -fPIC frmts/wmts/wmtsdataset.cpp -shared -o gdal_WMTS.so -Iport |
31 | | // -Igcore -Iogr -Iogr/ogrsf_frmts -L. -lgdal |
32 | | |
33 | | /* Set in stone by WMTS spec. In pixel/meter */ |
34 | 365 | #define WMTS_PITCH 0.00028 |
35 | | |
36 | 30 | #define WMTS_WGS84_DEG_PER_METER (180 / M_PI / SRS_WGS84_SEMIMAJOR) |
37 | | |
38 | | typedef enum |
39 | | { |
40 | | AUTO, |
41 | | LAYER_BBOX, |
42 | | TILE_MATRIX_SET, |
43 | | MOST_PRECISE_TILE_MATRIX |
44 | | } ExtentMethod; |
45 | | |
46 | | /************************************************************************/ |
47 | | /* ==================================================================== */ |
48 | | /* WMTSTileMatrix */ |
49 | | /* ==================================================================== */ |
50 | | /************************************************************************/ |
51 | | |
52 | | class WMTSTileMatrix |
53 | | { |
54 | | public: |
55 | | CPLString osIdentifier{}; |
56 | | double dfScaleDenominator = 0; |
57 | | double dfPixelSize = 0; |
58 | | double dfTLX = 0; |
59 | | double dfTLY = 0; |
60 | | int nTileWidth = 0; |
61 | | int nTileHeight = 0; |
62 | | int nMatrixWidth = 0; |
63 | | int nMatrixHeight = 0; |
64 | | |
65 | | OGREnvelope GetExtent() const |
66 | 654 | { |
67 | 654 | OGREnvelope sExtent; |
68 | 654 | sExtent.MinX = dfTLX; |
69 | 654 | sExtent.MaxX = dfTLX + nMatrixWidth * dfPixelSize * nTileWidth; |
70 | 654 | sExtent.MaxY = dfTLY; |
71 | 654 | sExtent.MinY = dfTLY - nMatrixHeight * dfPixelSize * nTileHeight; |
72 | 654 | return sExtent; |
73 | 654 | } |
74 | | }; |
75 | | |
76 | | /************************************************************************/ |
77 | | /* ==================================================================== */ |
78 | | /* WMTSTileMatrixLimits */ |
79 | | /* ==================================================================== */ |
80 | | /************************************************************************/ |
81 | | |
82 | | class WMTSTileMatrixLimits |
83 | | { |
84 | | public: |
85 | | CPLString osIdentifier{}; |
86 | | int nMinTileRow = 0; |
87 | | int nMaxTileRow = 0; |
88 | | int nMinTileCol = 0; |
89 | | int nMaxTileCol = 0; |
90 | | |
91 | | OGREnvelope GetExtent(const WMTSTileMatrix &oTM) const |
92 | 0 | { |
93 | 0 | OGREnvelope sExtent; |
94 | 0 | const double dfTileWidthUnits = oTM.dfPixelSize * oTM.nTileWidth; |
95 | 0 | const double dfTileHeightUnits = oTM.dfPixelSize * oTM.nTileHeight; |
96 | 0 | sExtent.MinX = oTM.dfTLX + nMinTileCol * dfTileWidthUnits; |
97 | 0 | sExtent.MaxY = oTM.dfTLY - nMinTileRow * dfTileHeightUnits; |
98 | 0 | sExtent.MaxX = oTM.dfTLX + (nMaxTileCol + 1) * dfTileWidthUnits; |
99 | 0 | sExtent.MinY = oTM.dfTLY - (nMaxTileRow + 1) * dfTileHeightUnits; |
100 | 0 | return sExtent; |
101 | 0 | } |
102 | | }; |
103 | | |
104 | | /************************************************************************/ |
105 | | /* ==================================================================== */ |
106 | | /* WMTSTileMatrixSet */ |
107 | | /* ==================================================================== */ |
108 | | /************************************************************************/ |
109 | | |
110 | | class WMTSTileMatrixSet |
111 | | { |
112 | | public: |
113 | | OGRSpatialReference oSRS{}; |
114 | | CPLString osSRS{}; |
115 | | bool bBoundingBoxValid = false; |
116 | | OGREnvelope sBoundingBox{}; /* expressed in TMS SRS */ |
117 | | std::vector<WMTSTileMatrix> aoTM{}; |
118 | | |
119 | | WMTSTileMatrixSet() |
120 | 114 | { |
121 | 114 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
122 | 114 | } |
123 | | }; |
124 | | |
125 | | /************************************************************************/ |
126 | | /* ==================================================================== */ |
127 | | /* WMTSDataset */ |
128 | | /* ==================================================================== */ |
129 | | /************************************************************************/ |
130 | | |
131 | | class WMTSDataset final : public GDALPamDataset |
132 | | { |
133 | | friend class WMTSBand; |
134 | | |
135 | | CPLString osLayer{}; |
136 | | CPLString osTMS{}; |
137 | | CPLString osXML{}; |
138 | | CPLString osURLFeatureInfoTemplate{}; |
139 | | WMTSTileMatrixSet oTMS{}; |
140 | | |
141 | | CPLStringList m_aosHTTPOptions{}; |
142 | | |
143 | | std::vector<GDALDataset *> apoDatasets{}; |
144 | | OGRSpatialReference m_oSRS{}; |
145 | | GDALGeoTransform m_gt{}; |
146 | | |
147 | | CPLString osLastGetFeatureInfoURL{}; |
148 | | CPLString osMetadataItemGetFeatureInfo{}; |
149 | | |
150 | | static CPLStringList BuildHTTPRequestOpts(CPLString osOtherXML); |
151 | | static CPLXMLNode *GetCapabilitiesResponse(const CPLString &osFilename, |
152 | | CSLConstList papszHTTPOptions); |
153 | | static CPLString FixCRSName(const char *pszCRS); |
154 | | static CPLString Replace(const CPLString &osStr, const char *pszOld, |
155 | | const char *pszNew); |
156 | | static CPLString GetOperationKVPURL(CPLXMLNode *psXML, |
157 | | const char *pszOperation); |
158 | | static int ReadTMS(CPLXMLNode *psContents, const CPLString &osIdentifier, |
159 | | const CPLString &osMaxTileMatrixIdentifier, |
160 | | int nMaxZoomLevel, WMTSTileMatrixSet &oTMS, |
161 | | bool &bHasWarnedAutoSwap); |
162 | | static int ReadTMLimits( |
163 | | CPLXMLNode *psTMSLimits, |
164 | | std::map<CPLString, WMTSTileMatrixLimits> &aoMapTileMatrixLimits); |
165 | | |
166 | | public: |
167 | | WMTSDataset(); |
168 | | ~WMTSDataset() override; |
169 | | |
170 | | CPLErr GetGeoTransform(GDALGeoTransform >) const override; |
171 | | const OGRSpatialReference *GetSpatialRef() const override; |
172 | | virtual const char *GetMetadataItem(const char *pszName, |
173 | | const char *pszDomain) override; |
174 | | |
175 | | static GDALDataset *Open(GDALOpenInfo *); |
176 | | static GDALDataset *CreateCopy(const char *pszFilename, |
177 | | GDALDataset *poSrcDS, CPL_UNUSED int bStrict, |
178 | | CPL_UNUSED CSLConstList papszOptions, |
179 | | CPL_UNUSED GDALProgressFunc pfnProgress, |
180 | | CPL_UNUSED void *pProgressData); |
181 | | |
182 | | protected: |
183 | | int CloseDependentDatasets() override; |
184 | | |
185 | | CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, |
186 | | int nYSize, void *pData, int nBufXSize, int nBufYSize, |
187 | | GDALDataType eBufType, int nBandCount, |
188 | | BANDMAP_TYPE panBandMap, GSpacing nPixelSpace, |
189 | | GSpacing nLineSpace, GSpacing nBandSpace, |
190 | | GDALRasterIOExtraArg *psExtraArg) override; |
191 | | }; |
192 | | |
193 | | /************************************************************************/ |
194 | | /* ==================================================================== */ |
195 | | /* WMTSBand */ |
196 | | /* ==================================================================== */ |
197 | | /************************************************************************/ |
198 | | |
199 | | class WMTSBand final : public GDALPamRasterBand |
200 | | { |
201 | | public: |
202 | | WMTSBand(WMTSDataset *poDS, int nBand, GDALDataType eDataType); |
203 | | |
204 | | GDALRasterBand *GetOverview(int nLevel) override; |
205 | | int GetOverviewCount() override; |
206 | | GDALColorInterp GetColorInterpretation() override; |
207 | | virtual const char *GetMetadataItem(const char *pszName, |
208 | | const char *pszDomain) override; |
209 | | |
210 | | protected: |
211 | | CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override; |
212 | | CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int, |
213 | | GDALDataType, GSpacing, GSpacing, |
214 | | GDALRasterIOExtraArg *psExtraArg) override; |
215 | | }; |
216 | | |
217 | | /************************************************************************/ |
218 | | /* WMTSBand() */ |
219 | | /************************************************************************/ |
220 | | |
221 | | WMTSBand::WMTSBand(WMTSDataset *poDSIn, int nBandIn, GDALDataType eDataTypeIn) |
222 | 148 | { |
223 | 148 | poDS = poDSIn; |
224 | 148 | nBand = nBandIn; |
225 | 148 | eDataType = eDataTypeIn; |
226 | 148 | poDSIn->apoDatasets[0]->GetRasterBand(1)->GetBlockSize(&nBlockXSize, |
227 | 148 | &nBlockYSize); |
228 | 148 | } |
229 | | |
230 | | /************************************************************************/ |
231 | | /* IReadBlock() */ |
232 | | /************************************************************************/ |
233 | | |
234 | | CPLErr WMTSBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) |
235 | 0 | { |
236 | 0 | WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS); |
237 | 0 | return poGDS->apoDatasets[0]->GetRasterBand(nBand)->ReadBlock( |
238 | 0 | nBlockXOff, nBlockYOff, pImage); |
239 | 0 | } |
240 | | |
241 | | /************************************************************************/ |
242 | | /* IRasterIO() */ |
243 | | /************************************************************************/ |
244 | | |
245 | | CPLErr WMTSBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, |
246 | | int nYSize, void *pData, int nBufXSize, |
247 | | int nBufYSize, GDALDataType eBufType, |
248 | | GSpacing nPixelSpace, GSpacing nLineSpace, |
249 | | GDALRasterIOExtraArg *psExtraArg) |
250 | 480 | { |
251 | 480 | WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS); |
252 | | |
253 | 480 | if ((nBufXSize < nXSize || nBufYSize < nYSize) && |
254 | 0 | poGDS->apoDatasets.size() > 1 && eRWFlag == GF_Read) |
255 | 0 | { |
256 | 0 | int bTried; |
257 | 0 | CPLErr eErr = TryOverviewRasterIO( |
258 | 0 | eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, |
259 | 0 | eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried); |
260 | 0 | if (bTried) |
261 | 0 | return eErr; |
262 | 0 | } |
263 | | |
264 | 480 | return poGDS->apoDatasets[0]->GetRasterBand(nBand)->RasterIO( |
265 | 480 | eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, |
266 | 480 | eBufType, nPixelSpace, nLineSpace, psExtraArg); |
267 | 480 | } |
268 | | |
269 | | /************************************************************************/ |
270 | | /* GetOverviewCount() */ |
271 | | /************************************************************************/ |
272 | | |
273 | | int WMTSBand::GetOverviewCount() |
274 | 219 | { |
275 | 219 | WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS); |
276 | | |
277 | 219 | if (poGDS->apoDatasets.size() > 1) |
278 | 199 | return static_cast<int>(poGDS->apoDatasets.size()) - 1; |
279 | 20 | else |
280 | 20 | return 0; |
281 | 219 | } |
282 | | |
283 | | /************************************************************************/ |
284 | | /* GetOverview() */ |
285 | | /************************************************************************/ |
286 | | |
287 | | GDALRasterBand *WMTSBand::GetOverview(int nLevel) |
288 | 182 | { |
289 | 182 | WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS); |
290 | | |
291 | 182 | if (nLevel < 0 || nLevel >= GetOverviewCount()) |
292 | 0 | return nullptr; |
293 | | |
294 | 182 | GDALDataset *poOvrDS = poGDS->apoDatasets[nLevel + 1]; |
295 | 182 | if (poOvrDS) |
296 | 182 | return poOvrDS->GetRasterBand(nBand); |
297 | 0 | else |
298 | 0 | return nullptr; |
299 | 182 | } |
300 | | |
301 | | /************************************************************************/ |
302 | | /* GetColorInterpretation() */ |
303 | | /************************************************************************/ |
304 | | |
305 | | GDALColorInterp WMTSBand::GetColorInterpretation() |
306 | 37 | { |
307 | 37 | WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS); |
308 | 37 | if (poGDS->nBands == 1) |
309 | 0 | { |
310 | 0 | return GCI_GrayIndex; |
311 | 0 | } |
312 | 37 | else if (poGDS->nBands == 3 || poGDS->nBands == 4) |
313 | 37 | { |
314 | 37 | if (nBand == 1) |
315 | 0 | return GCI_RedBand; |
316 | 37 | else if (nBand == 2) |
317 | 0 | return GCI_GreenBand; |
318 | 37 | else if (nBand == 3) |
319 | 0 | return GCI_BlueBand; |
320 | 37 | else if (nBand == 4) |
321 | 37 | return GCI_AlphaBand; |
322 | 37 | } |
323 | | |
324 | 0 | return GCI_Undefined; |
325 | 37 | } |
326 | | |
327 | | /************************************************************************/ |
328 | | /* GetMetadataItem() */ |
329 | | /************************************************************************/ |
330 | | |
331 | | const char *WMTSBand::GetMetadataItem(const char *pszName, |
332 | | const char *pszDomain) |
333 | 37 | { |
334 | 37 | WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS); |
335 | | |
336 | | /* ==================================================================== */ |
337 | | /* LocationInfo handling. */ |
338 | | /* ==================================================================== */ |
339 | 37 | if (pszDomain != nullptr && EQUAL(pszDomain, "LocationInfo") && |
340 | 37 | pszName != nullptr && STARTS_WITH_CI(pszName, "Pixel_") && |
341 | 0 | !poGDS->oTMS.aoTM.empty() && !poGDS->osURLFeatureInfoTemplate.empty()) |
342 | 0 | { |
343 | 0 | int iPixel, iLine; |
344 | | |
345 | | /* -------------------------------------------------------------------- |
346 | | */ |
347 | | /* What pixel are we aiming at? */ |
348 | | /* -------------------------------------------------------------------- |
349 | | */ |
350 | 0 | if (sscanf(pszName + 6, "%d_%d", &iPixel, &iLine) != 2) |
351 | 0 | return nullptr; |
352 | | |
353 | 0 | const WMTSTileMatrix &oTM = poGDS->oTMS.aoTM.back(); |
354 | |
|
355 | 0 | iPixel += static_cast<int>( |
356 | 0 | std::round((poGDS->m_gt.xorig - oTM.dfTLX) / oTM.dfPixelSize)); |
357 | 0 | iLine += static_cast<int>( |
358 | 0 | std::round((oTM.dfTLY - poGDS->m_gt.yorig) / oTM.dfPixelSize)); |
359 | |
|
360 | 0 | CPLString osURL(poGDS->osURLFeatureInfoTemplate); |
361 | 0 | osURL = WMTSDataset::Replace(osURL, "{TileMatrixSet}", poGDS->osTMS); |
362 | 0 | osURL = WMTSDataset::Replace(osURL, "{TileMatrix}", oTM.osIdentifier); |
363 | 0 | osURL = WMTSDataset::Replace(osURL, "{TileCol}", |
364 | 0 | CPLSPrintf("%d", iPixel / oTM.nTileWidth)); |
365 | 0 | osURL = WMTSDataset::Replace(osURL, "{TileRow}", |
366 | 0 | CPLSPrintf("%d", iLine / oTM.nTileHeight)); |
367 | 0 | osURL = WMTSDataset::Replace(osURL, "{I}", |
368 | 0 | CPLSPrintf("%d", iPixel % oTM.nTileWidth)); |
369 | 0 | osURL = WMTSDataset::Replace(osURL, "{J}", |
370 | 0 | CPLSPrintf("%d", iLine % oTM.nTileHeight)); |
371 | |
|
372 | 0 | if (poGDS->osLastGetFeatureInfoURL.compare(osURL) != 0) |
373 | 0 | { |
374 | 0 | poGDS->osLastGetFeatureInfoURL = osURL; |
375 | 0 | poGDS->osMetadataItemGetFeatureInfo = ""; |
376 | 0 | char *pszRes = nullptr; |
377 | 0 | CPLHTTPResult *psResult = |
378 | 0 | CPLHTTPFetch(osURL, poGDS->m_aosHTTPOptions.List()); |
379 | 0 | if (psResult && psResult->nStatus == 0 && psResult->pabyData) |
380 | 0 | pszRes = CPLStrdup( |
381 | 0 | reinterpret_cast<const char *>(psResult->pabyData)); |
382 | 0 | CPLHTTPDestroyResult(psResult); |
383 | |
|
384 | 0 | if (pszRes) |
385 | 0 | { |
386 | 0 | poGDS->osMetadataItemGetFeatureInfo = "<LocationInfo>"; |
387 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
388 | 0 | CPLXMLNode *psXML = CPLParseXMLString(pszRes); |
389 | 0 | CPLPopErrorHandler(); |
390 | 0 | if (psXML != nullptr && psXML->eType == CXT_Element) |
391 | 0 | { |
392 | 0 | if (strcmp(psXML->pszValue, "?xml") == 0) |
393 | 0 | { |
394 | 0 | if (psXML->psNext) |
395 | 0 | { |
396 | 0 | char *pszXML = CPLSerializeXMLTree(psXML->psNext); |
397 | 0 | poGDS->osMetadataItemGetFeatureInfo += pszXML; |
398 | 0 | CPLFree(pszXML); |
399 | 0 | } |
400 | 0 | } |
401 | 0 | else |
402 | 0 | { |
403 | 0 | poGDS->osMetadataItemGetFeatureInfo += pszRes; |
404 | 0 | } |
405 | 0 | } |
406 | 0 | else |
407 | 0 | { |
408 | 0 | char *pszEscapedXML = |
409 | 0 | CPLEscapeString(pszRes, -1, CPLES_XML_BUT_QUOTES); |
410 | 0 | poGDS->osMetadataItemGetFeatureInfo += pszEscapedXML; |
411 | 0 | CPLFree(pszEscapedXML); |
412 | 0 | } |
413 | 0 | if (psXML != nullptr) |
414 | 0 | CPLDestroyXMLNode(psXML); |
415 | |
|
416 | 0 | poGDS->osMetadataItemGetFeatureInfo += "</LocationInfo>"; |
417 | 0 | CPLFree(pszRes); |
418 | 0 | } |
419 | 0 | } |
420 | 0 | return poGDS->osMetadataItemGetFeatureInfo.c_str(); |
421 | 0 | } |
422 | | |
423 | 37 | return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain); |
424 | 37 | } |
425 | | |
426 | | /************************************************************************/ |
427 | | /* WMTSDataset() */ |
428 | | /************************************************************************/ |
429 | | |
430 | | WMTSDataset::WMTSDataset() |
431 | 57 | { |
432 | 57 | m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
433 | 57 | } |
434 | | |
435 | | /************************************************************************/ |
436 | | /* ~WMTSDataset() */ |
437 | | /************************************************************************/ |
438 | | |
439 | | WMTSDataset::~WMTSDataset() |
440 | 57 | { |
441 | 57 | WMTSDataset::CloseDependentDatasets(); |
442 | 57 | } |
443 | | |
444 | | /************************************************************************/ |
445 | | /* CloseDependentDatasets() */ |
446 | | /************************************************************************/ |
447 | | |
448 | | int WMTSDataset::CloseDependentDatasets() |
449 | 57 | { |
450 | 57 | int bRet = GDALPamDataset::CloseDependentDatasets(); |
451 | 57 | if (!apoDatasets.empty()) |
452 | 42 | { |
453 | 294 | for (size_t i = 0; i < apoDatasets.size(); i++) |
454 | 252 | delete apoDatasets[i]; |
455 | 42 | apoDatasets.resize(0); |
456 | 42 | bRet = TRUE; |
457 | 42 | } |
458 | 57 | return bRet; |
459 | 57 | } |
460 | | |
461 | | /************************************************************************/ |
462 | | /* IRasterIO() */ |
463 | | /************************************************************************/ |
464 | | |
465 | | CPLErr WMTSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, |
466 | | int nXSize, int nYSize, void *pData, |
467 | | int nBufXSize, int nBufYSize, |
468 | | GDALDataType eBufType, int nBandCount, |
469 | | BANDMAP_TYPE panBandMap, GSpacing nPixelSpace, |
470 | | GSpacing nLineSpace, GSpacing nBandSpace, |
471 | | GDALRasterIOExtraArg *psExtraArg) |
472 | 0 | { |
473 | 0 | if ((nBufXSize < nXSize || nBufYSize < nYSize) && apoDatasets.size() > 1 && |
474 | 0 | eRWFlag == GF_Read) |
475 | 0 | { |
476 | 0 | int bTried; |
477 | 0 | CPLErr eErr = TryOverviewRasterIO( |
478 | 0 | eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, |
479 | 0 | eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, |
480 | 0 | nBandSpace, psExtraArg, &bTried); |
481 | 0 | if (bTried) |
482 | 0 | return eErr; |
483 | 0 | } |
484 | | |
485 | 0 | return apoDatasets[0]->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, |
486 | 0 | pData, nBufXSize, nBufYSize, eBufType, |
487 | 0 | nBandCount, panBandMap, nPixelSpace, |
488 | 0 | nLineSpace, nBandSpace, psExtraArg); |
489 | 0 | } |
490 | | |
491 | | /************************************************************************/ |
492 | | /* GetGeoTransform() */ |
493 | | /************************************************************************/ |
494 | | |
495 | | CPLErr WMTSDataset::GetGeoTransform(GDALGeoTransform >) const |
496 | 37 | { |
497 | 37 | gt = m_gt; |
498 | 37 | return CE_None; |
499 | 37 | } |
500 | | |
501 | | /************************************************************************/ |
502 | | /* GetSpatialRef() */ |
503 | | /************************************************************************/ |
504 | | |
505 | | const OGRSpatialReference *WMTSDataset::GetSpatialRef() const |
506 | 37 | { |
507 | 37 | return m_oSRS.IsEmpty() ? nullptr : &m_oSRS; |
508 | 37 | } |
509 | | |
510 | | /************************************************************************/ |
511 | | /* WMTSEscapeXML() */ |
512 | | /************************************************************************/ |
513 | | |
514 | | static CPLString WMTSEscapeXML(const char *pszUnescapedXML) |
515 | 408 | { |
516 | 408 | CPLString osRet; |
517 | 408 | char *pszTmp = CPLEscapeString(pszUnescapedXML, -1, CPLES_XML); |
518 | 408 | osRet = pszTmp; |
519 | 408 | CPLFree(pszTmp); |
520 | 408 | return osRet; |
521 | 408 | } |
522 | | |
523 | | /************************************************************************/ |
524 | | /* GetMetadataItem() */ |
525 | | /************************************************************************/ |
526 | | |
527 | | const char *WMTSDataset::GetMetadataItem(const char *pszName, |
528 | | const char *pszDomain) |
529 | 255 | { |
530 | 255 | if (pszName != nullptr && EQUAL(pszName, "XML") && pszDomain != nullptr && |
531 | 0 | EQUAL(pszDomain, "WMTS")) |
532 | 0 | { |
533 | 0 | return osXML.c_str(); |
534 | 0 | } |
535 | | |
536 | 255 | return GDALPamDataset::GetMetadataItem(pszName, pszDomain); |
537 | 255 | } |
538 | | |
539 | | /************************************************************************/ |
540 | | /* QuoteIfNecessary() */ |
541 | | /************************************************************************/ |
542 | | |
543 | | static CPLString QuoteIfNecessary(const char *pszVal) |
544 | 57 | { |
545 | 57 | if (strchr(pszVal, ' ') || strchr(pszVal, ',') || strchr(pszVal, '=')) |
546 | 1 | { |
547 | 1 | CPLString osVal; |
548 | 1 | osVal += "\""; |
549 | 1 | osVal += pszVal; |
550 | 1 | osVal += "\""; |
551 | 1 | return osVal; |
552 | 1 | } |
553 | 56 | else |
554 | 56 | return pszVal; |
555 | 57 | } |
556 | | |
557 | | /************************************************************************/ |
558 | | /* FixCRSName() */ |
559 | | /************************************************************************/ |
560 | | |
561 | | CPLString WMTSDataset::FixCRSName(const char *pszCRS) |
562 | 154 | { |
563 | 154 | while (*pszCRS == ' ' || *pszCRS == '\r' || *pszCRS == '\n') |
564 | 0 | pszCRS++; |
565 | | |
566 | | /* http://maps.wien.gv.at/wmts/1.0.0/WMTSCapabilities.xml uses |
567 | | * urn:ogc:def:crs:EPSG:6.18:3:3857 */ |
568 | | /* instead of urn:ogc:def:crs:EPSG:6.18.3:3857. Coming from an incorrect |
569 | | * example of URN in WMTS spec */ |
570 | | /* https://portal.opengeospatial.org/files/?artifact_id=50398 */ |
571 | 154 | if (STARTS_WITH_CI(pszCRS, "urn:ogc:def:crs:EPSG:6.18:3:")) |
572 | 0 | { |
573 | 0 | return CPLSPrintf("urn:ogc:def:crs:EPSG::%s", |
574 | 0 | pszCRS + strlen("urn:ogc:def:crs:EPSG:6.18:3:")); |
575 | 0 | } |
576 | | |
577 | 154 | if (EQUAL(pszCRS, "urn:ogc:def:crs:EPSG::102100")) |
578 | 0 | return "EPSG:3857"; |
579 | | |
580 | 154 | CPLString osRet(pszCRS); |
581 | 154 | while (osRet.size() && (osRet.back() == ' ' || osRet.back() == '\r' || |
582 | 154 | osRet.back() == '\n')) |
583 | 0 | { |
584 | 0 | osRet.pop_back(); |
585 | 0 | } |
586 | 154 | return osRet; |
587 | 154 | } |
588 | | |
589 | | /************************************************************************/ |
590 | | /* ReadTMS() */ |
591 | | /************************************************************************/ |
592 | | |
593 | | int WMTSDataset::ReadTMS(CPLXMLNode *psContents, const CPLString &osIdentifier, |
594 | | const CPLString &osMaxTileMatrixIdentifier, |
595 | | int nMaxZoomLevel, WMTSTileMatrixSet &oTMS, |
596 | | bool &bHasWarnedAutoSwap) |
597 | 57 | { |
598 | 120 | for (CPLXMLNode *psIter = psContents->psChild; psIter != nullptr; |
599 | 63 | psIter = psIter->psNext) |
600 | 120 | { |
601 | 120 | if (psIter->eType != CXT_Element || |
602 | 114 | strcmp(psIter->pszValue, "TileMatrixSet") != 0) |
603 | 63 | continue; |
604 | 57 | const char *pszIdentifier = CPLGetXMLValue(psIter, "Identifier", ""); |
605 | 57 | if (!EQUAL(osIdentifier, pszIdentifier)) |
606 | 0 | continue; |
607 | 57 | const char *pszSupportedCRS = |
608 | 57 | CPLGetXMLValue(psIter, "SupportedCRS", nullptr); |
609 | 57 | if (pszSupportedCRS == nullptr) |
610 | 0 | { |
611 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Missing SupportedCRS"); |
612 | 0 | return FALSE; |
613 | 0 | } |
614 | 57 | oTMS.osSRS = pszSupportedCRS; |
615 | 57 | if (oTMS.oSRS.SetFromUserInput( |
616 | 57 | FixCRSName(pszSupportedCRS), |
617 | 57 | OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) != |
618 | 57 | OGRERR_NONE) |
619 | 0 | { |
620 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS '%s'", |
621 | 0 | pszSupportedCRS); |
622 | 0 | return FALSE; |
623 | 0 | } |
624 | 57 | const bool bSwap = |
625 | 57 | !STARTS_WITH_CI(pszSupportedCRS, "EPSG:") && |
626 | 34 | (CPL_TO_BOOL(oTMS.oSRS.EPSGTreatsAsLatLong()) || |
627 | 31 | CPL_TO_BOOL(oTMS.oSRS.EPSGTreatsAsNorthingEasting())); |
628 | 57 | CPLXMLNode *psBB = CPLGetXMLNode(psIter, "BoundingBox"); |
629 | 57 | oTMS.bBoundingBoxValid = false; |
630 | 57 | if (psBB != nullptr) |
631 | 31 | { |
632 | 31 | CPLString osCRS = CPLGetXMLValue(psBB, "crs", ""); |
633 | 31 | if (EQUAL(osCRS, "") || EQUAL(osCRS, pszSupportedCRS)) |
634 | 29 | { |
635 | 29 | CPLString osLowerCorner = |
636 | 29 | CPLGetXMLValue(psBB, "LowerCorner", ""); |
637 | 29 | CPLString osUpperCorner = |
638 | 29 | CPLGetXMLValue(psBB, "UpperCorner", ""); |
639 | 29 | if (!osLowerCorner.empty() && !osUpperCorner.empty()) |
640 | 29 | { |
641 | 29 | char **papszLC = CSLTokenizeString(osLowerCorner); |
642 | 29 | char **papszUC = CSLTokenizeString(osUpperCorner); |
643 | 29 | if (CSLCount(papszLC) == 2 && CSLCount(papszUC) == 2) |
644 | 29 | { |
645 | 29 | oTMS.sBoundingBox.MinX = |
646 | 29 | CPLAtof(papszLC[(bSwap) ? 1 : 0]); |
647 | 29 | oTMS.sBoundingBox.MinY = |
648 | 29 | CPLAtof(papszLC[(bSwap) ? 0 : 1]); |
649 | 29 | oTMS.sBoundingBox.MaxX = |
650 | 29 | CPLAtof(papszUC[(bSwap) ? 1 : 0]); |
651 | 29 | oTMS.sBoundingBox.MaxY = |
652 | 29 | CPLAtof(papszUC[(bSwap) ? 0 : 1]); |
653 | 29 | oTMS.bBoundingBoxValid = true; |
654 | 29 | } |
655 | 29 | CSLDestroy(papszLC); |
656 | 29 | CSLDestroy(papszUC); |
657 | 29 | } |
658 | 29 | } |
659 | 31 | } |
660 | 26 | else |
661 | 26 | { |
662 | 26 | const char *pszWellKnownScaleSet = |
663 | 26 | CPLGetXMLValue(psIter, "WellKnownScaleSet", ""); |
664 | 26 | if (EQUAL(pszIdentifier, "GoogleCRS84Quad") || |
665 | 26 | EQUAL(pszWellKnownScaleSet, |
666 | 26 | "urn:ogc:def:wkss:OGC:1.0:GoogleCRS84Quad") || |
667 | 26 | EQUAL(pszIdentifier, "GlobalCRS84Scale") || |
668 | 26 | EQUAL(pszWellKnownScaleSet, |
669 | 26 | "urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Scale")) |
670 | 0 | { |
671 | 0 | oTMS.sBoundingBox.MinX = -180; |
672 | 0 | oTMS.sBoundingBox.MinY = -90; |
673 | 0 | oTMS.sBoundingBox.MaxX = 180; |
674 | 0 | oTMS.sBoundingBox.MaxY = 90; |
675 | 0 | oTMS.bBoundingBoxValid = true; |
676 | 0 | } |
677 | 26 | } |
678 | | |
679 | 57 | bool bFoundTileMatrix = false; |
680 | 609 | for (CPLXMLNode *psSubIter = psIter->psChild; psSubIter != nullptr; |
681 | 552 | psSubIter = psSubIter->psNext) |
682 | 557 | { |
683 | 557 | if (psSubIter->eType != CXT_Element || |
684 | 550 | strcmp(psSubIter->pszValue, "TileMatrix") != 0) |
685 | 190 | continue; |
686 | 367 | const char *l_pszIdentifier = |
687 | 367 | CPLGetXMLValue(psSubIter, "Identifier", nullptr); |
688 | 367 | const char *pszScaleDenominator = |
689 | 367 | CPLGetXMLValue(psSubIter, "ScaleDenominator", nullptr); |
690 | 367 | const char *pszTopLeftCorner = |
691 | 367 | CPLGetXMLValue(psSubIter, "TopLeftCorner", nullptr); |
692 | 367 | const char *pszTileWidth = |
693 | 367 | CPLGetXMLValue(psSubIter, "TileWidth", nullptr); |
694 | 367 | const char *pszTileHeight = |
695 | 367 | CPLGetXMLValue(psSubIter, "TileHeight", nullptr); |
696 | 367 | const char *pszMatrixWidth = |
697 | 367 | CPLGetXMLValue(psSubIter, "MatrixWidth", nullptr); |
698 | 367 | const char *pszMatrixHeight = |
699 | 367 | CPLGetXMLValue(psSubIter, "MatrixHeight", nullptr); |
700 | 367 | if (l_pszIdentifier == nullptr || pszScaleDenominator == nullptr || |
701 | 367 | pszTopLeftCorner == nullptr || |
702 | 367 | strchr(pszTopLeftCorner, ' ') == nullptr || |
703 | 367 | pszTileWidth == nullptr || pszTileHeight == nullptr || |
704 | 366 | pszMatrixWidth == nullptr || pszMatrixHeight == nullptr) |
705 | 2 | { |
706 | 2 | CPLError(CE_Failure, CPLE_AppDefined, |
707 | 2 | "Missing required element in TileMatrix element"); |
708 | 2 | return FALSE; |
709 | 2 | } |
710 | 365 | WMTSTileMatrix oTM; |
711 | 365 | oTM.osIdentifier = l_pszIdentifier; |
712 | 365 | oTM.dfScaleDenominator = CPLAtof(pszScaleDenominator); |
713 | 365 | oTM.dfPixelSize = oTM.dfScaleDenominator * WMTS_PITCH; |
714 | 365 | if (oTM.dfPixelSize <= 0.0) |
715 | 1 | { |
716 | 1 | CPLError(CE_Failure, CPLE_AppDefined, |
717 | 1 | "Invalid ScaleDenominator"); |
718 | 1 | return FALSE; |
719 | 1 | } |
720 | 364 | if (oTMS.oSRS.IsGeographic()) |
721 | 30 | oTM.dfPixelSize *= WMTS_WGS84_DEG_PER_METER; |
722 | 364 | double dfVal1 = CPLAtof(pszTopLeftCorner); |
723 | 364 | double dfVal2 = CPLAtof(strchr(pszTopLeftCorner, ' ') + 1); |
724 | 364 | if (!bSwap) |
725 | 334 | { |
726 | 334 | oTM.dfTLX = dfVal1; |
727 | 334 | oTM.dfTLY = dfVal2; |
728 | 334 | } |
729 | 30 | else |
730 | 30 | { |
731 | 30 | oTM.dfTLX = dfVal2; |
732 | 30 | oTM.dfTLY = dfVal1; |
733 | 30 | } |
734 | | |
735 | | // Hack for http://osm.geobretagne.fr/gwc01/service/wmts?request=getcapabilities |
736 | | // or https://trek.nasa.gov/tiles/Mars/EQ/THEMIS_NightIR_ControlledMosaics_100m_v2_oct2018/1.0.0/WMTSCapabilities.xml |
737 | 364 | if (oTM.dfTLY == -180.0 && |
738 | 30 | (STARTS_WITH_CI(l_pszIdentifier, "EPSG:4326:") || |
739 | 30 | (oTMS.oSRS.IsGeographic() && oTM.dfTLX == 90))) |
740 | 30 | { |
741 | 30 | if (!bHasWarnedAutoSwap) |
742 | 3 | { |
743 | 3 | bHasWarnedAutoSwap = true; |
744 | 3 | CPLError(CE_Warning, CPLE_AppDefined, |
745 | 3 | "Auto-correcting wrongly swapped " |
746 | 3 | "TileMatrix.TopLeftCorner coordinates. " |
747 | 3 | "They should be in latitude, longitude order " |
748 | 3 | "but are presented in longitude, latitude order. " |
749 | 3 | "This should be reported to the server " |
750 | 3 | "administrator."); |
751 | 3 | } |
752 | 30 | std::swap(oTM.dfTLX, oTM.dfTLY); |
753 | 30 | } |
754 | | |
755 | | // Hack for "https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetCapabilities&VERSION=1.0.0&tk=ec899a50c7830ea2416ca182285236f3" |
756 | | // which returns swapped coordinates for WebMercator |
757 | 364 | if (std::fabs(oTM.dfTLX - 20037508.3427892) < 1e-4 && |
758 | 2 | std::fabs(oTM.dfTLY - (-20037508.3427892)) < 1e-4) |
759 | 0 | { |
760 | 0 | if (!bHasWarnedAutoSwap) |
761 | 0 | { |
762 | 0 | bHasWarnedAutoSwap = true; |
763 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
764 | 0 | "Auto-correcting wrongly swapped " |
765 | 0 | "TileMatrix.TopLeftCorner coordinates. This " |
766 | 0 | "should be reported to the server administrator."); |
767 | 0 | } |
768 | 0 | std::swap(oTM.dfTLX, oTM.dfTLY); |
769 | 0 | } |
770 | | |
771 | 364 | oTM.nTileWidth = atoi(pszTileWidth); |
772 | 364 | oTM.nTileHeight = atoi(pszTileHeight); |
773 | 364 | if (oTM.nTileWidth <= 0 || oTM.nTileWidth > 4096 || |
774 | 364 | oTM.nTileHeight <= 0 || oTM.nTileHeight > 4096) |
775 | 2 | { |
776 | 2 | CPLError(CE_Failure, CPLE_AppDefined, |
777 | 2 | "Invalid TileWidth/TileHeight element"); |
778 | 2 | return FALSE; |
779 | 2 | } |
780 | 362 | oTM.nMatrixWidth = atoi(pszMatrixWidth); |
781 | 362 | oTM.nMatrixHeight = atoi(pszMatrixHeight); |
782 | | // http://datacarto.geonormandie.fr/mapcache/wmts?SERVICE=WMTS&REQUEST=GetCapabilities |
783 | | // has a TileMatrix 0 with MatrixWidth = MatrixHeight = 0 |
784 | 362 | if (oTM.nMatrixWidth < 1 || oTM.nMatrixHeight < 1) |
785 | 2 | continue; |
786 | 360 | oTMS.aoTM.push_back(std::move(oTM)); |
787 | 360 | if ((nMaxZoomLevel >= 0 && |
788 | 0 | static_cast<int>(oTMS.aoTM.size()) - 1 == nMaxZoomLevel) || |
789 | 360 | (!osMaxTileMatrixIdentifier.empty() && |
790 | 0 | EQUAL(osMaxTileMatrixIdentifier, l_pszIdentifier))) |
791 | 0 | { |
792 | 0 | bFoundTileMatrix = true; |
793 | 0 | break; |
794 | 0 | } |
795 | 360 | } |
796 | 52 | if (nMaxZoomLevel >= 0 && !bFoundTileMatrix) |
797 | 0 | { |
798 | 0 | CPLError( |
799 | 0 | CE_Failure, CPLE_AppDefined, |
800 | 0 | "Cannot find TileMatrix of zoom level %d in TileMatrixSet '%s'", |
801 | 0 | nMaxZoomLevel, osIdentifier.c_str()); |
802 | 0 | return FALSE; |
803 | 0 | } |
804 | 52 | if (!osMaxTileMatrixIdentifier.empty() && !bFoundTileMatrix) |
805 | 0 | { |
806 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
807 | 0 | "Cannot find TileMatrix '%s' in TileMatrixSet '%s'", |
808 | 0 | osMaxTileMatrixIdentifier.c_str(), osIdentifier.c_str()); |
809 | 0 | return FALSE; |
810 | 0 | } |
811 | 52 | if (oTMS.aoTM.empty()) |
812 | 0 | { |
813 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
814 | 0 | "Cannot find TileMatrix in TileMatrixSet '%s'", |
815 | 0 | osIdentifier.c_str()); |
816 | 0 | return FALSE; |
817 | 0 | } |
818 | 52 | return TRUE; |
819 | 52 | } |
820 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find TileMatrixSet '%s'", |
821 | 0 | osIdentifier.c_str()); |
822 | 0 | return FALSE; |
823 | 57 | } |
824 | | |
825 | | /************************************************************************/ |
826 | | /* ReadTMLimits() */ |
827 | | /************************************************************************/ |
828 | | |
829 | | int WMTSDataset::ReadTMLimits( |
830 | | CPLXMLNode *psTMSLimits, |
831 | | std::map<CPLString, WMTSTileMatrixLimits> &aoMapTileMatrixLimits) |
832 | 0 | { |
833 | 0 | for (CPLXMLNode *psIter = psTMSLimits->psChild; psIter; |
834 | 0 | psIter = psIter->psNext) |
835 | 0 | { |
836 | 0 | if (psIter->eType != CXT_Element || |
837 | 0 | strcmp(psIter->pszValue, "TileMatrixLimits") != 0) |
838 | 0 | continue; |
839 | 0 | WMTSTileMatrixLimits oTMLimits; |
840 | 0 | const char *pszTileMatrix = |
841 | 0 | CPLGetXMLValue(psIter, "TileMatrix", nullptr); |
842 | 0 | const char *pszMinTileRow = |
843 | 0 | CPLGetXMLValue(psIter, "MinTileRow", nullptr); |
844 | 0 | const char *pszMaxTileRow = |
845 | 0 | CPLGetXMLValue(psIter, "MaxTileRow", nullptr); |
846 | 0 | const char *pszMinTileCol = |
847 | 0 | CPLGetXMLValue(psIter, "MinTileCol", nullptr); |
848 | 0 | const char *pszMaxTileCol = |
849 | 0 | CPLGetXMLValue(psIter, "MaxTileCol", nullptr); |
850 | 0 | if (pszTileMatrix == nullptr || pszMinTileRow == nullptr || |
851 | 0 | pszMaxTileRow == nullptr || pszMinTileCol == nullptr || |
852 | 0 | pszMaxTileCol == nullptr) |
853 | 0 | { |
854 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
855 | 0 | "Missing required element in TileMatrixLimits element"); |
856 | 0 | return FALSE; |
857 | 0 | } |
858 | 0 | oTMLimits.osIdentifier = pszTileMatrix; |
859 | 0 | oTMLimits.nMinTileRow = atoi(pszMinTileRow); |
860 | 0 | oTMLimits.nMaxTileRow = atoi(pszMaxTileRow); |
861 | 0 | oTMLimits.nMinTileCol = atoi(pszMinTileCol); |
862 | 0 | oTMLimits.nMaxTileCol = atoi(pszMaxTileCol); |
863 | 0 | aoMapTileMatrixLimits[pszTileMatrix] = std::move(oTMLimits); |
864 | 0 | } |
865 | 0 | return TRUE; |
866 | 0 | } |
867 | | |
868 | | /************************************************************************/ |
869 | | /* Replace() */ |
870 | | /************************************************************************/ |
871 | | |
872 | | CPLString WMTSDataset::Replace(const CPLString &osStr, const char *pszOld, |
873 | | const char *pszNew) |
874 | 485 | { |
875 | 485 | size_t nPos = osStr.ifind(pszOld); |
876 | 485 | if (nPos == std::string::npos) |
877 | 68 | return osStr; |
878 | 417 | CPLString osRet(osStr.substr(0, nPos)); |
879 | 417 | osRet += pszNew; |
880 | 417 | osRet += osStr.substr(nPos + strlen(pszOld)); |
881 | 417 | return osRet; |
882 | 485 | } |
883 | | |
884 | | /************************************************************************/ |
885 | | /* GetCapabilitiesResponse() */ |
886 | | /************************************************************************/ |
887 | | |
888 | | CPLXMLNode *WMTSDataset::GetCapabilitiesResponse(const CPLString &osFilename, |
889 | | CSLConstList papszHTTPOptions) |
890 | 19 | { |
891 | 19 | CPLXMLNode *psXML; |
892 | 19 | VSIStatBufL sStat; |
893 | 19 | if (VSIStatL(osFilename, &sStat) == 0) |
894 | 0 | psXML = CPLParseXMLFile(osFilename); |
895 | 19 | else |
896 | 19 | { |
897 | 19 | CPLHTTPResult *psResult = CPLHTTPFetch(osFilename, papszHTTPOptions); |
898 | 19 | if (psResult == nullptr) |
899 | 0 | return nullptr; |
900 | 19 | if (psResult->pabyData == nullptr) |
901 | 19 | { |
902 | 19 | CPLHTTPDestroyResult(psResult); |
903 | 19 | return nullptr; |
904 | 19 | } |
905 | 0 | psXML = CPLParseXMLString( |
906 | 0 | reinterpret_cast<const char *>(psResult->pabyData)); |
907 | 0 | CPLHTTPDestroyResult(psResult); |
908 | 0 | } |
909 | 0 | return psXML; |
910 | 19 | } |
911 | | |
912 | | /************************************************************************/ |
913 | | /* WMTSAddOtherXML() */ |
914 | | /************************************************************************/ |
915 | | |
916 | | static void WMTSAddOtherXML(CPLXMLNode *psRoot, const char *pszElement, |
917 | | CPLString &osOtherXML) |
918 | 0 | { |
919 | 0 | CPLXMLNode *psElement = CPLGetXMLNode(psRoot, pszElement); |
920 | 0 | if (psElement) |
921 | 0 | { |
922 | 0 | CPLXMLNode *psNext = psElement->psNext; |
923 | 0 | psElement->psNext = nullptr; |
924 | 0 | char *pszTmp = CPLSerializeXMLTree(psElement); |
925 | 0 | osOtherXML += pszTmp; |
926 | 0 | CPLFree(pszTmp); |
927 | 0 | psElement->psNext = psNext; |
928 | 0 | } |
929 | 0 | } |
930 | | |
931 | | /************************************************************************/ |
932 | | /* GetOperationKVPURL() */ |
933 | | /************************************************************************/ |
934 | | |
935 | | CPLString WMTSDataset::GetOperationKVPURL(CPLXMLNode *psXML, |
936 | | const char *pszOperation) |
937 | 66 | { |
938 | 66 | CPLString osRet; |
939 | 66 | CPLXMLNode *psOM = CPLGetXMLNode(psXML, "=Capabilities.OperationsMetadata"); |
940 | 145 | for (CPLXMLNode *psIter = psOM ? psOM->psChild : nullptr; psIter != nullptr; |
941 | 79 | psIter = psIter->psNext) |
942 | 79 | { |
943 | 79 | if (psIter->eType != CXT_Element || |
944 | 78 | strcmp(psIter->pszValue, "Operation") != 0 || |
945 | 78 | !EQUAL(CPLGetXMLValue(psIter, "name", ""), pszOperation)) |
946 | 42 | { |
947 | 42 | continue; |
948 | 42 | } |
949 | 37 | CPLXMLNode *psHTTP = CPLGetXMLNode(psIter, "DCP.HTTP"); |
950 | 37 | for (CPLXMLNode *psGet = psHTTP ? psHTTP->psChild : nullptr; |
951 | 75 | psGet != nullptr; psGet = psGet->psNext) |
952 | 38 | { |
953 | 38 | if (psGet->eType != CXT_Element || |
954 | 37 | strcmp(psGet->pszValue, "Get") != 0) |
955 | 1 | { |
956 | 1 | continue; |
957 | 1 | } |
958 | 37 | if (!EQUAL(CPLGetXMLValue(psGet, "Constraint.AllowedValues.Value", |
959 | 37 | "KVP"), |
960 | 37 | "KVP")) |
961 | 37 | continue; |
962 | 0 | osRet = CPLGetXMLValue(psGet, "href", ""); |
963 | 0 | } |
964 | 37 | } |
965 | 66 | return osRet; |
966 | 66 | } |
967 | | |
968 | | /************************************************************************/ |
969 | | /* BuildHTTPRequestOpts() */ |
970 | | /************************************************************************/ |
971 | | |
972 | | CPLStringList WMTSDataset::BuildHTTPRequestOpts(CPLString osOtherXML) |
973 | 76 | { |
974 | 76 | osOtherXML = "<Root>" + osOtherXML + "</Root>"; |
975 | 76 | CPLXMLNode *psXML = CPLParseXMLString(osOtherXML); |
976 | 76 | CPLStringList opts; |
977 | 76 | for (const char *pszOptionName : |
978 | 76 | {"Timeout", "UserAgent", "Accept", "Referer", "UserPwd"}) |
979 | 380 | { |
980 | 380 | if (const char *pszVal = CPLGetXMLValue(psXML, pszOptionName, nullptr)) |
981 | 0 | { |
982 | 0 | opts.SetNameValue(CPLString(pszOptionName).toupper(), pszVal); |
983 | 0 | } |
984 | 380 | } |
985 | 76 | if (CPLTestBool(CPLGetXMLValue(psXML, "UnsafeSSL", "false"))) |
986 | 76 | { |
987 | 76 | opts.SetNameValue("UNSAFESSL", "1"); |
988 | 76 | } |
989 | 76 | CPLDestroyXMLNode(psXML); |
990 | 76 | return opts; |
991 | 76 | } |
992 | | |
993 | | /************************************************************************/ |
994 | | /* Open() */ |
995 | | /************************************************************************/ |
996 | | |
997 | | GDALDataset *WMTSDataset::Open(GDALOpenInfo *poOpenInfo) |
998 | 1.38k | { |
999 | 1.38k | if (!WMTSDriverIdentify(poOpenInfo)) |
1000 | 0 | return nullptr; |
1001 | | |
1002 | 1.38k | CPLXMLNode *psXML = nullptr; |
1003 | 1.38k | CPLString osTileFormat; |
1004 | 1.38k | CPLString osInfoFormat; |
1005 | | |
1006 | 1.38k | CPLString osGetCapabilitiesURL = |
1007 | 1.38k | CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "URL", ""); |
1008 | 1.38k | CPLString osLayer = |
1009 | 1.38k | CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "LAYER", ""); |
1010 | 1.38k | CPLString osTMS = |
1011 | 1.38k | CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "TILEMATRIXSET", ""); |
1012 | 1.38k | CPLString osMaxTileMatrixIdentifier = |
1013 | 1.38k | CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "TILEMATRIX", ""); |
1014 | 1.38k | int nUserMaxZoomLevel = atoi(CSLFetchNameValueDef( |
1015 | 1.38k | poOpenInfo->papszOpenOptions, "ZOOM_LEVEL", |
1016 | 1.38k | CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ZOOMLEVEL", "-1"))); |
1017 | 1.38k | CPLString osStyle = |
1018 | 1.38k | CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "STYLE", ""); |
1019 | | |
1020 | 1.38k | int bExtendBeyondDateLine = CPLFetchBool(poOpenInfo->papszOpenOptions, |
1021 | 1.38k | "EXTENDBEYONDDATELINE", false); |
1022 | | |
1023 | 1.38k | CPLString osOtherXML = |
1024 | 1.38k | "<Cache />" |
1025 | 1.38k | "<UnsafeSSL>true</UnsafeSSL>" |
1026 | 1.38k | "<ZeroBlockHttpCodes>204,404</ZeroBlockHttpCodes>" |
1027 | 1.38k | "<ZeroBlockOnServerException>true</ZeroBlockOnServerException>"; |
1028 | | |
1029 | 1.38k | if (STARTS_WITH_CI(poOpenInfo->pszFilename, "WMTS:")) |
1030 | 19 | { |
1031 | 19 | char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename + 5, |
1032 | 19 | ",", CSLT_HONOURSTRINGS); |
1033 | 19 | if (papszTokens && papszTokens[0]) |
1034 | 19 | { |
1035 | 19 | osGetCapabilitiesURL = papszTokens[0]; |
1036 | 40 | for (char **papszIter = papszTokens + 1; *papszIter; papszIter++) |
1037 | 21 | { |
1038 | 21 | char *pszKey = nullptr; |
1039 | 21 | const char *pszValue = CPLParseNameValue(*papszIter, &pszKey); |
1040 | 21 | if (pszKey && pszValue) |
1041 | 10 | { |
1042 | 10 | if (EQUAL(pszKey, "layer")) |
1043 | 0 | osLayer = pszValue; |
1044 | 10 | else if (EQUAL(pszKey, "tilematrixset")) |
1045 | 0 | osTMS = pszValue; |
1046 | 10 | else if (EQUAL(pszKey, "tilematrix")) |
1047 | 0 | osMaxTileMatrixIdentifier = pszValue; |
1048 | 10 | else if (EQUAL(pszKey, "zoom_level") || |
1049 | 10 | EQUAL(pszKey, "zoomlevel")) |
1050 | 0 | nUserMaxZoomLevel = atoi(pszValue); |
1051 | 10 | else if (EQUAL(pszKey, "style")) |
1052 | 0 | osStyle = pszValue; |
1053 | 10 | else if (EQUAL(pszKey, "extendbeyonddateline")) |
1054 | 0 | bExtendBeyondDateLine = CPLTestBool(pszValue); |
1055 | 10 | else |
1056 | 10 | CPLError(CE_Warning, CPLE_AppDefined, |
1057 | 10 | "Unknown parameter: %s'", pszKey); |
1058 | 10 | } |
1059 | 21 | CPLFree(pszKey); |
1060 | 21 | } |
1061 | 19 | } |
1062 | 19 | CSLDestroy(papszTokens); |
1063 | | |
1064 | 19 | const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML)); |
1065 | 19 | psXML = GetCapabilitiesResponse(osGetCapabilitiesURL, |
1066 | 19 | aosHTTPOptions.List()); |
1067 | 19 | } |
1068 | 1.36k | else if (poOpenInfo->IsSingleAllowedDriver("WMTS") && |
1069 | 0 | (STARTS_WITH(poOpenInfo->pszFilename, "http://") || |
1070 | 0 | STARTS_WITH(poOpenInfo->pszFilename, "https://"))) |
1071 | 0 | { |
1072 | 0 | const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML)); |
1073 | 0 | psXML = GetCapabilitiesResponse(poOpenInfo->pszFilename, |
1074 | 0 | aosHTTPOptions.List()); |
1075 | 0 | } |
1076 | | |
1077 | 1.38k | int bHasAOI = FALSE; |
1078 | 1.38k | OGREnvelope sAOI; |
1079 | 1.38k | int nBands = 4; |
1080 | 1.38k | GDALDataType eDataType = GDT_UInt8; |
1081 | 1.38k | CPLString osProjection; |
1082 | 1.38k | CPLString osExtraQueryParameters; |
1083 | | |
1084 | 1.38k | if ((psXML != nullptr && CPLGetXMLNode(psXML, "=GDAL_WMTS") != nullptr) || |
1085 | 1.38k | STARTS_WITH_CI(poOpenInfo->pszFilename, "<GDAL_WMTS") || |
1086 | 1.37k | (poOpenInfo->nHeaderBytes > 0 && |
1087 | 1.36k | strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader), |
1088 | 1.36k | "<GDAL_WMTS"))) |
1089 | 1.27k | { |
1090 | 1.27k | CPLXMLNode *psGDALWMTS; |
1091 | 1.27k | if (psXML != nullptr && CPLGetXMLNode(psXML, "=GDAL_WMTS") != nullptr) |
1092 | 0 | psGDALWMTS = CPLCloneXMLTree(psXML); |
1093 | 1.27k | else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "<GDAL_WMTS")) |
1094 | 3 | psGDALWMTS = CPLParseXMLString(poOpenInfo->pszFilename); |
1095 | 1.26k | else |
1096 | 1.26k | psGDALWMTS = CPLParseXMLFile(poOpenInfo->pszFilename); |
1097 | 1.27k | if (psGDALWMTS == nullptr) |
1098 | 1.09k | return nullptr; |
1099 | 171 | CPLXMLNode *psRoot = CPLGetXMLNode(psGDALWMTS, "=GDAL_WMTS"); |
1100 | 171 | if (psRoot == nullptr) |
1101 | 170 | { |
1102 | 170 | CPLError(CE_Failure, CPLE_AppDefined, |
1103 | 170 | "Cannot find root <GDAL_WMTS>"); |
1104 | 170 | CPLDestroyXMLNode(psGDALWMTS); |
1105 | 170 | return nullptr; |
1106 | 170 | } |
1107 | 1 | osGetCapabilitiesURL = CPLGetXMLValue(psRoot, "GetCapabilitiesUrl", ""); |
1108 | 1 | if (osGetCapabilitiesURL.empty()) |
1109 | 1 | { |
1110 | 1 | CPLError(CE_Failure, CPLE_AppDefined, |
1111 | 1 | "Missing <GetCapabilitiesUrl>"); |
1112 | 1 | CPLDestroyXMLNode(psGDALWMTS); |
1113 | 1 | return nullptr; |
1114 | 1 | } |
1115 | 0 | osExtraQueryParameters = |
1116 | 0 | CPLGetXMLValue(psRoot, "ExtraQueryParameters", ""); |
1117 | 0 | if (!osExtraQueryParameters.empty() && osExtraQueryParameters[0] != '&') |
1118 | 0 | osExtraQueryParameters = '&' + osExtraQueryParameters; |
1119 | |
|
1120 | 0 | osGetCapabilitiesURL += osExtraQueryParameters; |
1121 | |
|
1122 | 0 | osLayer = CPLGetXMLValue(psRoot, "Layer", osLayer); |
1123 | 0 | osTMS = CPLGetXMLValue(psRoot, "TileMatrixSet", osTMS); |
1124 | 0 | osMaxTileMatrixIdentifier = |
1125 | 0 | CPLGetXMLValue(psRoot, "TileMatrix", osMaxTileMatrixIdentifier); |
1126 | 0 | nUserMaxZoomLevel = atoi(CPLGetXMLValue( |
1127 | 0 | psRoot, "ZoomLevel", CPLSPrintf("%d", nUserMaxZoomLevel))); |
1128 | 0 | osStyle = CPLGetXMLValue(psRoot, "Style", osStyle); |
1129 | 0 | osTileFormat = CPLGetXMLValue(psRoot, "Format", osTileFormat); |
1130 | 0 | osInfoFormat = CPLGetXMLValue(psRoot, "InfoFormat", osInfoFormat); |
1131 | 0 | osProjection = CPLGetXMLValue(psRoot, "Projection", osProjection); |
1132 | 0 | bExtendBeyondDateLine = CPLTestBool( |
1133 | 0 | CPLGetXMLValue(psRoot, "ExtendBeyondDateLine", |
1134 | 0 | (bExtendBeyondDateLine) ? "true" : "false")); |
1135 | |
|
1136 | 0 | osOtherXML = ""; |
1137 | 0 | for (const char *pszXMLElement : |
1138 | 0 | {"Cache", "MaxConnections", "Timeout", "OfflineMode", "UserAgent", |
1139 | 0 | "Accept", "UserPwd", "UnsafeSSL", "Referer", "ZeroBlockHttpCodes", |
1140 | 0 | "ZeroBlockOnServerException"}) |
1141 | 0 | { |
1142 | 0 | WMTSAddOtherXML(psRoot, pszXMLElement, osOtherXML); |
1143 | 0 | } |
1144 | |
|
1145 | 0 | nBands = atoi(CPLGetXMLValue(psRoot, "BandsCount", "4")); |
1146 | 0 | const char *pszDataType = CPLGetXMLValue(psRoot, "DataType", "Byte"); |
1147 | 0 | eDataType = GDALGetDataTypeByName(pszDataType); |
1148 | 0 | if ((eDataType == GDT_Unknown) || (eDataType >= GDT_TypeCount)) |
1149 | 0 | { |
1150 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1151 | 0 | "GDALWMTS: Invalid value in DataType. Data type \"%s\" is " |
1152 | 0 | "not supported.", |
1153 | 0 | pszDataType); |
1154 | 0 | CPLDestroyXMLNode(psGDALWMTS); |
1155 | 0 | return nullptr; |
1156 | 0 | } |
1157 | | |
1158 | 0 | const char *pszULX = |
1159 | 0 | CPLGetXMLValue(psRoot, "DataWindow.UpperLeftX", nullptr); |
1160 | 0 | const char *pszULY = |
1161 | 0 | CPLGetXMLValue(psRoot, "DataWindow.UpperLeftY", nullptr); |
1162 | 0 | const char *pszLRX = |
1163 | 0 | CPLGetXMLValue(psRoot, "DataWindow.LowerRightX", nullptr); |
1164 | 0 | const char *pszLRY = |
1165 | 0 | CPLGetXMLValue(psRoot, "DataWindow.LowerRightY", nullptr); |
1166 | 0 | if (pszULX && pszULY && pszLRX && pszLRY) |
1167 | 0 | { |
1168 | 0 | sAOI.MinX = CPLAtof(pszULX); |
1169 | 0 | sAOI.MaxY = CPLAtof(pszULY); |
1170 | 0 | sAOI.MaxX = CPLAtof(pszLRX); |
1171 | 0 | sAOI.MinY = CPLAtof(pszLRY); |
1172 | 0 | bHasAOI = TRUE; |
1173 | 0 | } |
1174 | |
|
1175 | 0 | CPLDestroyXMLNode(psGDALWMTS); |
1176 | |
|
1177 | 0 | CPLDestroyXMLNode(psXML); |
1178 | 0 | const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML)); |
1179 | 0 | psXML = GetCapabilitiesResponse(osGetCapabilitiesURL, |
1180 | 0 | aosHTTPOptions.List()); |
1181 | 0 | } |
1182 | 112 | else if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WMTS:") && |
1183 | 93 | !STARTS_WITH(poOpenInfo->pszFilename, "http://") && |
1184 | 93 | !STARTS_WITH(poOpenInfo->pszFilename, "https://")) |
1185 | 93 | { |
1186 | 93 | osGetCapabilitiesURL = poOpenInfo->pszFilename; |
1187 | 93 | psXML = CPLParseXMLFile(poOpenInfo->pszFilename); |
1188 | 93 | } |
1189 | 112 | if (psXML == nullptr) |
1190 | 53 | return nullptr; |
1191 | 59 | CPLStripXMLNamespace(psXML, nullptr, TRUE); |
1192 | | |
1193 | 59 | CPLXMLNode *psContents = CPLGetXMLNode(psXML, "=Capabilities.Contents"); |
1194 | 59 | if (psContents == nullptr) |
1195 | 0 | { |
1196 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1197 | 0 | "Missing Capabilities.Contents element"); |
1198 | 0 | CPLDestroyXMLNode(psXML); |
1199 | 0 | return nullptr; |
1200 | 0 | } |
1201 | | |
1202 | 59 | if (STARTS_WITH(osGetCapabilitiesURL, "/vsimem/")) |
1203 | 59 | { |
1204 | 59 | osGetCapabilitiesURL = GetOperationKVPURL(psXML, "GetCapabilities"); |
1205 | 59 | if (osGetCapabilitiesURL.empty()) |
1206 | 59 | { |
1207 | | // (ERO) I'm not even sure this is correct at all... |
1208 | 59 | const char *pszHref = CPLGetXMLValue( |
1209 | 59 | psXML, "=Capabilities.ServiceMetadataURL.href", nullptr); |
1210 | 59 | if (pszHref) |
1211 | 34 | osGetCapabilitiesURL = pszHref; |
1212 | 59 | } |
1213 | 0 | else |
1214 | 0 | { |
1215 | 0 | osGetCapabilitiesURL = |
1216 | 0 | CPLURLAddKVP(osGetCapabilitiesURL, "service", "WMTS"); |
1217 | 0 | osGetCapabilitiesURL = CPLURLAddKVP(osGetCapabilitiesURL, "request", |
1218 | 0 | "GetCapabilities"); |
1219 | 0 | } |
1220 | 59 | } |
1221 | 59 | CPLString osCapabilitiesFilename(osGetCapabilitiesURL); |
1222 | 59 | if (!STARTS_WITH_CI(osCapabilitiesFilename, "WMTS:")) |
1223 | 59 | osCapabilitiesFilename = "WMTS:" + osGetCapabilitiesURL; |
1224 | | |
1225 | 59 | int nLayerCount = 0; |
1226 | 59 | CPLStringList aosSubDatasets; |
1227 | 59 | CPLString osSelectLayer(osLayer), osSelectTMS(osTMS), |
1228 | 59 | osSelectStyle(osStyle); |
1229 | 59 | CPLString osSelectLayerTitle, osSelectLayerAbstract; |
1230 | 59 | CPLString osSelectTileFormat(osTileFormat), |
1231 | 59 | osSelectInfoFormat(osInfoFormat); |
1232 | 59 | int nCountTileFormat = 0; |
1233 | 59 | int nCountInfoFormat = 0; |
1234 | 59 | CPLString osURLTileTemplate; |
1235 | 59 | CPLString osURLFeatureInfoTemplate; |
1236 | 59 | std::set<CPLString> aoSetLayers; |
1237 | 59 | std::map<CPLString, OGREnvelope> aoMapBoundingBox; |
1238 | 59 | std::map<CPLString, WMTSTileMatrixLimits> aoMapTileMatrixLimits; |
1239 | 59 | std::map<CPLString, CPLString> aoMapDimensions; |
1240 | 59 | bool bHasWarnedAutoSwap = false; |
1241 | 59 | bool bHasWarnedAutoSwapBoundingBox = false; |
1242 | | |
1243 | | // Collect TileMatrixSet identifiers |
1244 | 59 | std::set<std::string> oSetTMSIdentifiers; |
1245 | 183 | for (CPLXMLNode *psIter = psContents->psChild; psIter != nullptr; |
1246 | 124 | psIter = psIter->psNext) |
1247 | 124 | { |
1248 | 124 | if (psIter->eType != CXT_Element || |
1249 | 118 | strcmp(psIter->pszValue, "TileMatrixSet") != 0) |
1250 | 65 | continue; |
1251 | 59 | const char *pszIdentifier = |
1252 | 59 | CPLGetXMLValue(psIter, "Identifier", nullptr); |
1253 | 59 | if (pszIdentifier) |
1254 | 59 | oSetTMSIdentifiers.insert(pszIdentifier); |
1255 | 59 | } |
1256 | | |
1257 | 183 | for (CPLXMLNode *psIter = psContents->psChild; psIter != nullptr; |
1258 | 124 | psIter = psIter->psNext) |
1259 | 124 | { |
1260 | 124 | if (psIter->eType != CXT_Element || |
1261 | 118 | strcmp(psIter->pszValue, "Layer") != 0) |
1262 | 65 | continue; |
1263 | 59 | const char *pszIdentifier = CPLGetXMLValue(psIter, "Identifier", ""); |
1264 | 59 | if (aoSetLayers.find(pszIdentifier) != aoSetLayers.end()) |
1265 | 0 | { |
1266 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1267 | 0 | "Several layers with identifier '%s'. Only first one kept", |
1268 | 0 | pszIdentifier); |
1269 | 0 | } |
1270 | 59 | aoSetLayers.insert(pszIdentifier); |
1271 | 59 | if (!osLayer.empty() && strcmp(osLayer, pszIdentifier) != 0) |
1272 | 0 | continue; |
1273 | 59 | const char *pszTitle = CPLGetXMLValue(psIter, "Title", nullptr); |
1274 | 59 | if (osSelectLayer.empty()) |
1275 | 59 | { |
1276 | 59 | osSelectLayer = pszIdentifier; |
1277 | 59 | } |
1278 | 59 | if (strcmp(osSelectLayer, pszIdentifier) == 0) |
1279 | 59 | { |
1280 | 59 | if (pszTitle != nullptr) |
1281 | 59 | osSelectLayerTitle = pszTitle; |
1282 | 59 | const char *pszAbstract = |
1283 | 59 | CPLGetXMLValue(psIter, "Abstract", nullptr); |
1284 | 59 | if (pszAbstract != nullptr) |
1285 | 2 | osSelectLayerAbstract = pszAbstract; |
1286 | 59 | } |
1287 | | |
1288 | 59 | std::vector<CPLString> aosTMS; |
1289 | 59 | std::vector<CPLString> aosStylesIdentifier; |
1290 | 59 | std::vector<CPLString> aosStylesTitle; |
1291 | | |
1292 | 59 | CPLXMLNode *psSubIter = psIter->psChild; |
1293 | 506 | for (; psSubIter != nullptr; psSubIter = psSubIter->psNext) |
1294 | 447 | { |
1295 | 447 | if (psSubIter->eType != CXT_Element) |
1296 | 2 | continue; |
1297 | 445 | if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1298 | 445 | strcmp(psSubIter->pszValue, "Format") == 0) |
1299 | 59 | { |
1300 | 59 | const char *pszValue = CPLGetXMLValue(psSubIter, "", ""); |
1301 | 59 | if (!osTileFormat.empty() && |
1302 | 0 | strcmp(osTileFormat, pszValue) != 0) |
1303 | 0 | continue; |
1304 | 59 | nCountTileFormat++; |
1305 | 59 | if (osSelectTileFormat.empty() || EQUAL(pszValue, "image/png")) |
1306 | 59 | { |
1307 | 59 | osSelectTileFormat = pszValue; |
1308 | 59 | } |
1309 | 59 | } |
1310 | 386 | else if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1311 | 386 | strcmp(psSubIter->pszValue, "InfoFormat") == 0) |
1312 | 0 | { |
1313 | 0 | const char *pszValue = CPLGetXMLValue(psSubIter, "", ""); |
1314 | 0 | if (!osInfoFormat.empty() && |
1315 | 0 | strcmp(osInfoFormat, pszValue) != 0) |
1316 | 0 | continue; |
1317 | 0 | nCountInfoFormat++; |
1318 | 0 | if (osSelectInfoFormat.empty() || |
1319 | 0 | (EQUAL(pszValue, "application/vnd.ogc.gml") && |
1320 | 0 | !EQUAL(osSelectInfoFormat, |
1321 | 0 | "application/vnd.ogc.gml/3.1.1")) || |
1322 | 0 | EQUAL(pszValue, "application/vnd.ogc.gml/3.1.1")) |
1323 | 0 | { |
1324 | 0 | osSelectInfoFormat = pszValue; |
1325 | 0 | } |
1326 | 0 | } |
1327 | 386 | else if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1328 | 386 | strcmp(psSubIter->pszValue, "Dimension") == 0) |
1329 | 0 | { |
1330 | | /* Cf http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml */ |
1331 | 0 | const char *pszDimensionIdentifier = |
1332 | 0 | CPLGetXMLValue(psSubIter, "Identifier", nullptr); |
1333 | 0 | const char *pszDefault = |
1334 | 0 | CPLGetXMLValue(psSubIter, "Default", ""); |
1335 | 0 | if (pszDimensionIdentifier != nullptr) |
1336 | 0 | aoMapDimensions[pszDimensionIdentifier] = pszDefault; |
1337 | 0 | } |
1338 | 386 | else if (strcmp(psSubIter->pszValue, "TileMatrixSetLink") == 0) |
1339 | 59 | { |
1340 | 59 | const char *pszTMS = |
1341 | 59 | CPLGetXMLValue(psSubIter, "TileMatrixSet", ""); |
1342 | 59 | if (oSetTMSIdentifiers.find(pszTMS) == oSetTMSIdentifiers.end()) |
1343 | 2 | { |
1344 | 2 | CPLDebug("WMTS", |
1345 | 2 | "Layer %s has a TileMatrixSetLink to %s, " |
1346 | 2 | "but it is not defined as a TileMatrixSet", |
1347 | 2 | pszIdentifier, pszTMS); |
1348 | 2 | continue; |
1349 | 2 | } |
1350 | 57 | if (!osTMS.empty() && strcmp(osTMS, pszTMS) != 0) |
1351 | 0 | continue; |
1352 | 57 | if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1353 | 57 | osSelectTMS.empty()) |
1354 | 57 | { |
1355 | 57 | osSelectTMS = pszTMS; |
1356 | 57 | } |
1357 | 57 | if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1358 | 57 | strcmp(osSelectTMS, pszTMS) == 0) |
1359 | 57 | { |
1360 | 57 | CPLXMLNode *psTMSLimits = |
1361 | 57 | CPLGetXMLNode(psSubIter, "TileMatrixSetLimits"); |
1362 | 57 | if (psTMSLimits) |
1363 | 0 | ReadTMLimits(psTMSLimits, aoMapTileMatrixLimits); |
1364 | 57 | } |
1365 | 57 | aosTMS.push_back(pszTMS); |
1366 | 57 | } |
1367 | 327 | else if (strcmp(psSubIter->pszValue, "Style") == 0) |
1368 | 59 | { |
1369 | 59 | int bIsDefault = CPLTestBool( |
1370 | 59 | CPLGetXMLValue(psSubIter, "isDefault", "false")); |
1371 | 59 | const char *l_pszIdentifier = |
1372 | 59 | CPLGetXMLValue(psSubIter, "Identifier", ""); |
1373 | 59 | if (!osStyle.empty() && strcmp(osStyle, l_pszIdentifier) != 0) |
1374 | 0 | continue; |
1375 | 59 | const char *pszStyleTitle = |
1376 | 59 | CPLGetXMLValue(psSubIter, "Title", l_pszIdentifier); |
1377 | 59 | if (bIsDefault) |
1378 | 34 | { |
1379 | 34 | aosStylesIdentifier.insert(aosStylesIdentifier.begin(), |
1380 | 34 | CPLString(l_pszIdentifier)); |
1381 | 34 | aosStylesTitle.insert(aosStylesTitle.begin(), |
1382 | 34 | CPLString(pszStyleTitle)); |
1383 | 34 | if (strcmp(osSelectLayer, l_pszIdentifier) == 0 && |
1384 | 0 | osSelectStyle.empty()) |
1385 | 0 | { |
1386 | 0 | osSelectStyle = l_pszIdentifier; |
1387 | 0 | } |
1388 | 34 | } |
1389 | 25 | else |
1390 | 25 | { |
1391 | 25 | aosStylesIdentifier.push_back(l_pszIdentifier); |
1392 | 25 | aosStylesTitle.push_back(pszStyleTitle); |
1393 | 25 | } |
1394 | 59 | } |
1395 | 268 | else if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1396 | 268 | (strcmp(psSubIter->pszValue, "BoundingBox") == 0 || |
1397 | 265 | strcmp(psSubIter->pszValue, "WGS84BoundingBox") == 0)) |
1398 | 62 | { |
1399 | 62 | CPLString osCRS = CPLGetXMLValue(psSubIter, "crs", ""); |
1400 | 62 | if (osCRS.empty()) |
1401 | 25 | { |
1402 | 25 | if (strcmp(psSubIter->pszValue, "WGS84BoundingBox") == 0) |
1403 | 25 | { |
1404 | 25 | osCRS = "EPSG:4326"; |
1405 | 25 | } |
1406 | 0 | else |
1407 | 0 | { |
1408 | 0 | int nCountTileMatrixSet = 0; |
1409 | 0 | CPLString osSingleTileMatrixSet; |
1410 | 0 | for (CPLXMLNode *psIter3 = psContents->psChild; |
1411 | 0 | psIter3 != nullptr; psIter3 = psIter3->psNext) |
1412 | 0 | { |
1413 | 0 | if (psIter3->eType != CXT_Element || |
1414 | 0 | strcmp(psIter3->pszValue, "TileMatrixSet") != 0) |
1415 | 0 | continue; |
1416 | 0 | nCountTileMatrixSet++; |
1417 | 0 | if (nCountTileMatrixSet == 1) |
1418 | 0 | osSingleTileMatrixSet = |
1419 | 0 | CPLGetXMLValue(psIter3, "Identifier", ""); |
1420 | 0 | } |
1421 | 0 | if (nCountTileMatrixSet == 1) |
1422 | 0 | { |
1423 | | // For |
1424 | | // 13-082_WMTS_Simple_Profile/schemas/wmts/1.0/profiles/WMTSSimple/examples/wmtsGetCapabilities_response_OSM.xml |
1425 | 0 | WMTSTileMatrixSet oTMS; |
1426 | 0 | if (ReadTMS(psContents, osSingleTileMatrixSet, |
1427 | 0 | CPLString(), -1, oTMS, |
1428 | 0 | bHasWarnedAutoSwap)) |
1429 | 0 | { |
1430 | 0 | osCRS = oTMS.osSRS; |
1431 | 0 | } |
1432 | 0 | } |
1433 | 0 | } |
1434 | 25 | } |
1435 | 62 | CPLString osLowerCorner = |
1436 | 62 | CPLGetXMLValue(psSubIter, "LowerCorner", ""); |
1437 | 62 | CPLString osUpperCorner = |
1438 | 62 | CPLGetXMLValue(psSubIter, "UpperCorner", ""); |
1439 | 62 | OGRSpatialReference oSRS; |
1440 | 62 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1441 | 62 | if (!osCRS.empty() && !osLowerCorner.empty() && |
1442 | 62 | !osUpperCorner.empty() && |
1443 | 62 | oSRS.SetFromUserInput(FixCRSName(osCRS)) == OGRERR_NONE) |
1444 | 51 | { |
1445 | 51 | const bool bSwap = |
1446 | 51 | !STARTS_WITH_CI(osCRS, "EPSG:") && |
1447 | 26 | (CPL_TO_BOOL(oSRS.EPSGTreatsAsLatLong()) || |
1448 | 23 | CPL_TO_BOOL(oSRS.EPSGTreatsAsNorthingEasting())); |
1449 | 51 | char **papszLC = CSLTokenizeString(osLowerCorner); |
1450 | 51 | char **papszUC = CSLTokenizeString(osUpperCorner); |
1451 | 51 | if (CSLCount(papszLC) == 2 && CSLCount(papszUC) == 2) |
1452 | 47 | { |
1453 | 47 | OGREnvelope sEnvelope; |
1454 | 47 | sEnvelope.MinX = CPLAtof(papszLC[(bSwap) ? 1 : 0]); |
1455 | 47 | sEnvelope.MinY = CPLAtof(papszLC[(bSwap) ? 0 : 1]); |
1456 | 47 | sEnvelope.MaxX = CPLAtof(papszUC[(bSwap) ? 1 : 0]); |
1457 | 47 | sEnvelope.MaxY = CPLAtof(papszUC[(bSwap) ? 0 : 1]); |
1458 | | |
1459 | 47 | if (bSwap && oSRS.IsGeographic() && |
1460 | 3 | (std::fabs(sEnvelope.MinY) > 90 || |
1461 | 0 | std::fabs(sEnvelope.MaxY) > 90)) |
1462 | 3 | { |
1463 | 3 | if (!bHasWarnedAutoSwapBoundingBox) |
1464 | 3 | { |
1465 | 3 | bHasWarnedAutoSwapBoundingBox = true; |
1466 | 3 | CPLError( |
1467 | 3 | CE_Warning, CPLE_AppDefined, |
1468 | 3 | "Auto-correcting wrongly swapped " |
1469 | 3 | "ows:%s coordinates. " |
1470 | 3 | "They should be in latitude, longitude " |
1471 | 3 | "order " |
1472 | 3 | "but are presented in longitude, latitude " |
1473 | 3 | "order. " |
1474 | 3 | "This should be reported to the server " |
1475 | 3 | "administrator.", |
1476 | 3 | psSubIter->pszValue); |
1477 | 3 | } |
1478 | 3 | std::swap(sEnvelope.MinX, sEnvelope.MinY); |
1479 | 3 | std::swap(sEnvelope.MaxX, sEnvelope.MaxY); |
1480 | 3 | } |
1481 | | |
1482 | 47 | aoMapBoundingBox[osCRS] = sEnvelope; |
1483 | 47 | } |
1484 | 51 | CSLDestroy(papszLC); |
1485 | 51 | CSLDestroy(papszUC); |
1486 | 51 | } |
1487 | 62 | } |
1488 | 206 | else if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1489 | 206 | strcmp(psSubIter->pszValue, "ResourceURL") == 0) |
1490 | 56 | { |
1491 | 56 | if (EQUAL(CPLGetXMLValue(psSubIter, "resourceType", ""), |
1492 | 56 | "tile")) |
1493 | 55 | { |
1494 | 55 | const char *pszFormat = |
1495 | 55 | CPLGetXMLValue(psSubIter, "format", ""); |
1496 | 55 | if (!osTileFormat.empty() && |
1497 | 0 | strcmp(osTileFormat, pszFormat) != 0) |
1498 | 0 | continue; |
1499 | 55 | if (osURLTileTemplate.empty()) |
1500 | 55 | osURLTileTemplate = |
1501 | 55 | CPLGetXMLValue(psSubIter, "template", ""); |
1502 | 55 | } |
1503 | 1 | else if (EQUAL(CPLGetXMLValue(psSubIter, "resourceType", ""), |
1504 | 1 | "FeatureInfo")) |
1505 | 0 | { |
1506 | 0 | const char *pszFormat = |
1507 | 0 | CPLGetXMLValue(psSubIter, "format", ""); |
1508 | 0 | if (!osInfoFormat.empty() && |
1509 | 0 | strcmp(osInfoFormat, pszFormat) != 0) |
1510 | 0 | continue; |
1511 | 0 | if (osURLFeatureInfoTemplate.empty()) |
1512 | 0 | osURLFeatureInfoTemplate = |
1513 | 0 | CPLGetXMLValue(psSubIter, "template", ""); |
1514 | 0 | } |
1515 | 56 | } |
1516 | 445 | } |
1517 | 59 | if (strcmp(osSelectLayer, pszIdentifier) == 0 && |
1518 | 59 | osSelectStyle.empty() && !aosStylesIdentifier.empty()) |
1519 | 59 | { |
1520 | 59 | osSelectStyle = aosStylesIdentifier[0]; |
1521 | 59 | } |
1522 | 116 | for (size_t i = 0; i < aosTMS.size(); i++) |
1523 | 57 | { |
1524 | 114 | for (size_t j = 0; j < aosStylesIdentifier.size(); j++) |
1525 | 57 | { |
1526 | 57 | int nIdx = 1 + aosSubDatasets.size() / 2; |
1527 | 57 | CPLString osName(osCapabilitiesFilename); |
1528 | 57 | osName += ",layer="; |
1529 | 57 | osName += QuoteIfNecessary(pszIdentifier); |
1530 | 57 | if (aosTMS.size() > 1) |
1531 | 0 | { |
1532 | 0 | osName += ",tilematrixset="; |
1533 | 0 | osName += QuoteIfNecessary(aosTMS[i]); |
1534 | 0 | } |
1535 | 57 | if (aosStylesIdentifier.size() > 1) |
1536 | 0 | { |
1537 | 0 | osName += ",style="; |
1538 | 0 | osName += QuoteIfNecessary(aosStylesIdentifier[j]); |
1539 | 0 | } |
1540 | 57 | aosSubDatasets.AddNameValue( |
1541 | 57 | CPLSPrintf("SUBDATASET_%d_NAME", nIdx), osName); |
1542 | | |
1543 | 57 | CPLString osDesc("Layer "); |
1544 | 57 | osDesc += pszTitle ? pszTitle : pszIdentifier; |
1545 | 57 | if (aosTMS.size() > 1) |
1546 | 0 | { |
1547 | 0 | osDesc += ", tile matrix set "; |
1548 | 0 | osDesc += aosTMS[i]; |
1549 | 0 | } |
1550 | 57 | if (aosStylesIdentifier.size() > 1) |
1551 | 0 | { |
1552 | 0 | osDesc += ", style "; |
1553 | 0 | osDesc += QuoteIfNecessary(aosStylesTitle[j]); |
1554 | 0 | } |
1555 | 57 | aosSubDatasets.AddNameValue( |
1556 | 57 | CPLSPrintf("SUBDATASET_%d_DESC", nIdx), osDesc); |
1557 | 57 | } |
1558 | 57 | } |
1559 | 59 | if (!aosTMS.empty() && !aosStylesIdentifier.empty()) |
1560 | 57 | nLayerCount++; |
1561 | 2 | else |
1562 | 2 | CPLError(CE_Failure, CPLE_AppDefined, |
1563 | 2 | "Missing TileMatrixSetLink and/or Style"); |
1564 | 59 | } |
1565 | | |
1566 | 59 | if (nLayerCount == 0) |
1567 | 2 | { |
1568 | 2 | CPLDestroyXMLNode(psXML); |
1569 | 2 | return nullptr; |
1570 | 2 | } |
1571 | | |
1572 | 57 | WMTSDataset *poDS = new WMTSDataset(); |
1573 | | |
1574 | 57 | if (aosSubDatasets.size() > 2) |
1575 | 0 | poDS->SetMetadata(aosSubDatasets.List(), GDAL_MDD_SUBDATASETS); |
1576 | | |
1577 | 57 | if (nLayerCount == 1) |
1578 | 57 | { |
1579 | 57 | if (!osSelectLayerTitle.empty()) |
1580 | 57 | poDS->SetMetadataItem("TITLE", osSelectLayerTitle); |
1581 | 57 | if (!osSelectLayerAbstract.empty()) |
1582 | 2 | poDS->SetMetadataItem("ABSTRACT", osSelectLayerAbstract); |
1583 | | |
1584 | 57 | poDS->m_aosHTTPOptions = BuildHTTPRequestOpts(osOtherXML); |
1585 | 57 | poDS->osLayer = osSelectLayer; |
1586 | 57 | poDS->osTMS = osSelectTMS; |
1587 | | |
1588 | 57 | WMTSTileMatrixSet oTMS; |
1589 | 57 | if (!ReadTMS(psContents, osSelectTMS, osMaxTileMatrixIdentifier, |
1590 | 57 | nUserMaxZoomLevel, oTMS, bHasWarnedAutoSwap)) |
1591 | 5 | { |
1592 | 5 | CPLDestroyXMLNode(psXML); |
1593 | 5 | delete poDS; |
1594 | 5 | return nullptr; |
1595 | 5 | } |
1596 | | |
1597 | 52 | const char *pszExtentMethod = CSLFetchNameValueDef( |
1598 | 52 | poOpenInfo->papszOpenOptions, "EXTENT_METHOD", "AUTO"); |
1599 | 52 | ExtentMethod eExtentMethod = AUTO; |
1600 | 52 | if (EQUAL(pszExtentMethod, "LAYER_BBOX")) |
1601 | 0 | eExtentMethod = LAYER_BBOX; |
1602 | 52 | else if (EQUAL(pszExtentMethod, "TILE_MATRIX_SET")) |
1603 | 0 | eExtentMethod = TILE_MATRIX_SET; |
1604 | 52 | else if (EQUAL(pszExtentMethod, "MOST_PRECISE_TILE_MATRIX")) |
1605 | 0 | eExtentMethod = MOST_PRECISE_TILE_MATRIX; |
1606 | | |
1607 | 52 | bool bAOIFromLayer = false; |
1608 | | |
1609 | | // Use in priority layer bounding box expressed in the SRS of the TMS |
1610 | 52 | if ((!bHasAOI || bExtendBeyondDateLine) && |
1611 | 52 | (eExtentMethod == AUTO || eExtentMethod == LAYER_BBOX) && |
1612 | 52 | aoMapBoundingBox.find(oTMS.osSRS) != aoMapBoundingBox.end()) |
1613 | 3 | { |
1614 | 3 | if (!bHasAOI) |
1615 | 3 | { |
1616 | 3 | sAOI = aoMapBoundingBox[oTMS.osSRS]; |
1617 | 3 | bAOIFromLayer = true; |
1618 | 3 | bHasAOI = TRUE; |
1619 | 3 | } |
1620 | | |
1621 | 3 | int bRecomputeAOI = FALSE; |
1622 | 3 | if (bExtendBeyondDateLine) |
1623 | 0 | { |
1624 | 0 | bExtendBeyondDateLine = FALSE; |
1625 | |
|
1626 | 0 | OGRSpatialReference oWGS84; |
1627 | 0 | oWGS84.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); |
1628 | 0 | oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1629 | 0 | OGRCoordinateTransformation *poCT = |
1630 | 0 | OGRCreateCoordinateTransformation(&oTMS.oSRS, &oWGS84); |
1631 | 0 | if (poCT != nullptr) |
1632 | 0 | { |
1633 | 0 | double dfX1 = sAOI.MinX; |
1634 | 0 | double dfY1 = sAOI.MinY; |
1635 | 0 | double dfX2 = sAOI.MaxX; |
1636 | 0 | double dfY2 = sAOI.MaxY; |
1637 | 0 | if (poCT->Transform(1, &dfX1, &dfY1) && |
1638 | 0 | poCT->Transform(1, &dfX2, &dfY2)) |
1639 | 0 | { |
1640 | 0 | if (fabs(dfX1 + 180) < 1e-8 && fabs(dfX2 - 180) < 1e-8) |
1641 | 0 | { |
1642 | 0 | bExtendBeyondDateLine = TRUE; |
1643 | 0 | bRecomputeAOI = TRUE; |
1644 | 0 | } |
1645 | 0 | else if (dfX2 < dfX1) |
1646 | 0 | { |
1647 | 0 | bExtendBeyondDateLine = TRUE; |
1648 | 0 | } |
1649 | 0 | else |
1650 | 0 | { |
1651 | 0 | CPLError( |
1652 | 0 | CE_Warning, CPLE_AppDefined, |
1653 | 0 | "ExtendBeyondDateLine disabled, since " |
1654 | 0 | "longitudes of %s " |
1655 | 0 | "BoundingBox do not span from -180 to 180 but " |
1656 | 0 | "from %.16g to %.16g, " |
1657 | 0 | "or longitude of upper right corner is not " |
1658 | 0 | "lesser than the one of lower left corner", |
1659 | 0 | oTMS.osSRS.c_str(), dfX1, dfX2); |
1660 | 0 | } |
1661 | 0 | } |
1662 | 0 | delete poCT; |
1663 | 0 | } |
1664 | 0 | } |
1665 | 3 | if (bExtendBeyondDateLine && bRecomputeAOI) |
1666 | 0 | { |
1667 | 0 | bExtendBeyondDateLine = FALSE; |
1668 | |
|
1669 | 0 | std::map<CPLString, OGREnvelope>::iterator oIter = |
1670 | 0 | aoMapBoundingBox.begin(); |
1671 | 0 | for (; oIter != aoMapBoundingBox.end(); ++oIter) |
1672 | 0 | { |
1673 | 0 | OGRSpatialReference oSRS; |
1674 | 0 | if (oSRS.SetFromUserInput( |
1675 | 0 | FixCRSName(oIter->first), |
1676 | 0 | OGRSpatialReference:: |
1677 | 0 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == |
1678 | 0 | OGRERR_NONE) |
1679 | 0 | { |
1680 | 0 | OGRSpatialReference oWGS84; |
1681 | 0 | oWGS84.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG); |
1682 | 0 | oWGS84.SetAxisMappingStrategy( |
1683 | 0 | OAMS_TRADITIONAL_GIS_ORDER); |
1684 | 0 | auto poCT = |
1685 | 0 | std::unique_ptr<OGRCoordinateTransformation>( |
1686 | 0 | OGRCreateCoordinateTransformation(&oSRS, |
1687 | 0 | &oWGS84)); |
1688 | 0 | double dfX1 = oIter->second.MinX; |
1689 | 0 | double dfY1 = oIter->second.MinY; |
1690 | 0 | double dfX2 = oIter->second.MaxX; |
1691 | 0 | double dfY2 = oIter->second.MaxY; |
1692 | 0 | if (poCT != nullptr && |
1693 | 0 | poCT->Transform(1, &dfX1, &dfY1) && |
1694 | 0 | poCT->Transform(1, &dfX2, &dfY2) && dfX2 < dfX1) |
1695 | 0 | { |
1696 | 0 | dfX2 += 360; |
1697 | 0 | OGRSpatialReference oWGS84_with_over; |
1698 | 0 | oWGS84_with_over.SetFromUserInput( |
1699 | 0 | "+proj=longlat +datum=WGS84 +over +wktext"); |
1700 | 0 | char *pszProj4 = nullptr; |
1701 | 0 | oTMS.oSRS.exportToProj4(&pszProj4); |
1702 | 0 | oSRS.SetFromUserInput( |
1703 | 0 | CPLSPrintf("%s +over +wktext", pszProj4)); |
1704 | 0 | CPLFree(pszProj4); |
1705 | 0 | poCT.reset(OGRCreateCoordinateTransformation( |
1706 | 0 | &oWGS84_with_over, &oSRS)); |
1707 | 0 | if (poCT && poCT->Transform(1, &dfX1, &dfY1) && |
1708 | 0 | poCT->Transform(1, &dfX2, &dfY2)) |
1709 | 0 | { |
1710 | 0 | bExtendBeyondDateLine = TRUE; |
1711 | 0 | sAOI.MinX = std::min(dfX1, dfX2); |
1712 | 0 | sAOI.MinY = std::min(dfY1, dfY2); |
1713 | 0 | sAOI.MaxX = std::max(dfX1, dfX2); |
1714 | 0 | sAOI.MaxY = std::max(dfY1, dfY2); |
1715 | 0 | CPLDebug("WMTS", |
1716 | 0 | "ExtendBeyondDateLine using %s " |
1717 | 0 | "bounding box", |
1718 | 0 | oIter->first.c_str()); |
1719 | 0 | } |
1720 | 0 | break; |
1721 | 0 | } |
1722 | 0 | } |
1723 | 0 | } |
1724 | 0 | } |
1725 | 3 | } |
1726 | 49 | else |
1727 | 49 | { |
1728 | 49 | if (bExtendBeyondDateLine) |
1729 | 0 | { |
1730 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1731 | 0 | "ExtendBeyondDateLine disabled, since BoundingBox of " |
1732 | 0 | "%s is missing", |
1733 | 0 | oTMS.osSRS.c_str()); |
1734 | 0 | bExtendBeyondDateLine = FALSE; |
1735 | 0 | } |
1736 | 49 | } |
1737 | | |
1738 | | // Otherwise default to reproject a layer bounding box expressed in |
1739 | | // another SRS |
1740 | 52 | if (!bHasAOI && !aoMapBoundingBox.empty() && |
1741 | 35 | (eExtentMethod == AUTO || eExtentMethod == LAYER_BBOX)) |
1742 | 35 | { |
1743 | 35 | std::map<CPLString, OGREnvelope>::iterator oIter = |
1744 | 35 | aoMapBoundingBox.begin(); |
1745 | 35 | for (; oIter != aoMapBoundingBox.end(); ++oIter) |
1746 | 35 | { |
1747 | 35 | OGRSpatialReference oSRS; |
1748 | 35 | oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1749 | 35 | if (oSRS.SetFromUserInput( |
1750 | 35 | FixCRSName(oIter->first), |
1751 | 35 | OGRSpatialReference:: |
1752 | 35 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == |
1753 | 35 | OGRERR_NONE) |
1754 | 35 | { |
1755 | | // Check if this doesn't match the most precise tile matrix |
1756 | | // by densifying its contour |
1757 | 35 | const WMTSTileMatrix &oTM = oTMS.aoTM.back(); |
1758 | | |
1759 | 35 | bool bMatchFound = false; |
1760 | 35 | const char *pszProjectionTMS = |
1761 | 35 | oTMS.oSRS.GetAttrValue("PROJECTION"); |
1762 | 35 | const char *pszProjectionBBOX = |
1763 | 35 | oSRS.GetAttrValue("PROJECTION"); |
1764 | 35 | const bool bIsTMerc = |
1765 | 35 | (pszProjectionTMS != nullptr && |
1766 | 35 | EQUAL(pszProjectionTMS, SRS_PT_TRANSVERSE_MERCATOR)) || |
1767 | 16 | (pszProjectionBBOX != nullptr && |
1768 | 0 | EQUAL(pszProjectionBBOX, SRS_PT_TRANSVERSE_MERCATOR)); |
1769 | | // If one of the 2 SRS is a TMerc, try with classical tmerc |
1770 | | // or etmerc. |
1771 | 89 | for (int j = 0; j < (bIsTMerc ? 2 : 1); j++) |
1772 | 54 | { |
1773 | 54 | CPLString osOldVal = CPLGetThreadLocalConfigOption( |
1774 | 54 | "OSR_USE_APPROX_TMERC", ""); |
1775 | 54 | if (bIsTMerc) |
1776 | 38 | { |
1777 | 38 | CPLSetThreadLocalConfigOption( |
1778 | 38 | "OSR_USE_APPROX_TMERC", |
1779 | 38 | (j == 0) ? "NO" : "YES"); |
1780 | 38 | } |
1781 | 54 | OGRCoordinateTransformation *poRevCT = |
1782 | 54 | OGRCreateCoordinateTransformation(&oTMS.oSRS, |
1783 | 54 | &oSRS); |
1784 | 54 | if (bIsTMerc) |
1785 | 38 | { |
1786 | 38 | CPLSetThreadLocalConfigOption( |
1787 | 38 | "OSR_USE_APPROX_TMERC", |
1788 | 38 | osOldVal.empty() ? nullptr : osOldVal.c_str()); |
1789 | 38 | } |
1790 | 54 | if (poRevCT != nullptr) |
1791 | 54 | { |
1792 | 54 | const auto sTMExtent = oTM.GetExtent(); |
1793 | 54 | const double dfX0 = sTMExtent.MinX; |
1794 | 54 | const double dfY1 = sTMExtent.MaxY; |
1795 | 54 | const double dfX1 = sTMExtent.MaxX; |
1796 | 54 | const double dfY0 = sTMExtent.MinY; |
1797 | 54 | double dfXMin = |
1798 | 54 | std::numeric_limits<double>::infinity(); |
1799 | 54 | double dfYMin = |
1800 | 54 | std::numeric_limits<double>::infinity(); |
1801 | 54 | double dfXMax = |
1802 | 54 | -std::numeric_limits<double>::infinity(); |
1803 | 54 | double dfYMax = |
1804 | 54 | -std::numeric_limits<double>::infinity(); |
1805 | | |
1806 | 54 | const int NSTEPS = 20; |
1807 | 1.18k | for (int i = 0; i <= NSTEPS; i++) |
1808 | 1.13k | { |
1809 | 1.13k | double dfX = dfX0 + (dfX1 - dfX0) * i / NSTEPS; |
1810 | 1.13k | double dfY = dfY0; |
1811 | 1.13k | if (poRevCT->Transform(1, &dfX, &dfY)) |
1812 | 1.13k | { |
1813 | 1.13k | dfXMin = std::min(dfXMin, dfX); |
1814 | 1.13k | dfYMin = std::min(dfYMin, dfY); |
1815 | 1.13k | dfXMax = std::max(dfXMax, dfX); |
1816 | 1.13k | dfYMax = std::max(dfYMax, dfY); |
1817 | 1.13k | } |
1818 | | |
1819 | 1.13k | dfX = dfX0 + (dfX1 - dfX0) * i / NSTEPS; |
1820 | 1.13k | dfY = dfY1; |
1821 | 1.13k | if (poRevCT->Transform(1, &dfX, &dfY)) |
1822 | 1.13k | { |
1823 | 1.13k | dfXMin = std::min(dfXMin, dfX); |
1824 | 1.13k | dfYMin = std::min(dfYMin, dfY); |
1825 | 1.13k | dfXMax = std::max(dfXMax, dfX); |
1826 | 1.13k | dfYMax = std::max(dfYMax, dfY); |
1827 | 1.13k | } |
1828 | | |
1829 | 1.13k | dfX = dfX0; |
1830 | 1.13k | dfY = dfY0 + (dfY1 - dfY0) * i / NSTEPS; |
1831 | 1.13k | if (poRevCT->Transform(1, &dfX, &dfY)) |
1832 | 1.13k | { |
1833 | 1.13k | dfXMin = std::min(dfXMin, dfX); |
1834 | 1.13k | dfYMin = std::min(dfYMin, dfY); |
1835 | 1.13k | dfXMax = std::max(dfXMax, dfX); |
1836 | 1.13k | dfYMax = std::max(dfYMax, dfY); |
1837 | 1.13k | } |
1838 | | |
1839 | 1.13k | dfX = dfX1; |
1840 | 1.13k | dfY = dfY0 + (dfY1 - dfY0) * i / NSTEPS; |
1841 | 1.13k | if (poRevCT->Transform(1, &dfX, &dfY)) |
1842 | 1.13k | { |
1843 | 1.13k | dfXMin = std::min(dfXMin, dfX); |
1844 | 1.13k | dfYMin = std::min(dfYMin, dfY); |
1845 | 1.13k | dfXMax = std::max(dfXMax, dfX); |
1846 | 1.13k | dfYMax = std::max(dfYMax, dfY); |
1847 | 1.13k | } |
1848 | 1.13k | } |
1849 | | |
1850 | 54 | delete poRevCT; |
1851 | | #ifdef DEBUG_VERBOSE |
1852 | | CPLDebug( |
1853 | | "WMTS", |
1854 | | "Reprojected densified bbox of most " |
1855 | | "precise tile matrix in %s: %.8g %8g %8g %8g", |
1856 | | oIter->first.c_str(), dfXMin, dfYMin, dfXMax, |
1857 | | dfYMax); |
1858 | | #endif |
1859 | 54 | if (fabs(oIter->second.MinX - dfXMin) < |
1860 | 54 | 1e-5 * std::max(fabs(oIter->second.MinX), |
1861 | 54 | fabs(dfXMin)) && |
1862 | 34 | fabs(oIter->second.MinY - dfYMin) < |
1863 | 34 | 1e-5 * std::max(fabs(oIter->second.MinY), |
1864 | 34 | fabs(dfYMin)) && |
1865 | 30 | fabs(oIter->second.MaxX - dfXMax) < |
1866 | 30 | 1e-5 * std::max(fabs(oIter->second.MaxX), |
1867 | 30 | fabs(dfXMax)) && |
1868 | 30 | fabs(oIter->second.MaxY - dfYMax) < |
1869 | 30 | 1e-5 * std::max(fabs(oIter->second.MaxY), |
1870 | 30 | fabs(dfYMax))) |
1871 | 0 | { |
1872 | 0 | bMatchFound = true; |
1873 | | #ifdef DEBUG_VERBOSE |
1874 | | CPLDebug("WMTS", |
1875 | | "Matches layer bounding box, so " |
1876 | | "that one is not significant"); |
1877 | | #endif |
1878 | 0 | break; |
1879 | 0 | } |
1880 | 54 | } |
1881 | 54 | } |
1882 | | |
1883 | 35 | if (bMatchFound) |
1884 | 0 | { |
1885 | 0 | if (eExtentMethod == LAYER_BBOX) |
1886 | 0 | eExtentMethod = MOST_PRECISE_TILE_MATRIX; |
1887 | 0 | break; |
1888 | 0 | } |
1889 | | |
1890 | | // Otherwise try to reproject the bounding box of the |
1891 | | // layer from its SRS to the TMS SRS. Except in some cases |
1892 | | // where this would result in non-sense. (this could be |
1893 | | // improved !) |
1894 | 35 | if (!(bIsTMerc && oSRS.IsGeographic() && |
1895 | 19 | fabs(oIter->second.MinX - -180) < 1e-8 && |
1896 | 0 | fabs(oIter->second.MaxX - 180) < 1e-8)) |
1897 | 35 | { |
1898 | 35 | OGRCoordinateTransformation *poCT = |
1899 | 35 | OGRCreateCoordinateTransformation(&oSRS, |
1900 | 35 | &oTMS.oSRS); |
1901 | 35 | if (poCT != nullptr) |
1902 | 35 | { |
1903 | 35 | double dfX1 = oIter->second.MinX; |
1904 | 35 | double dfY1 = oIter->second.MinY; |
1905 | 35 | double dfX2 = oIter->second.MaxX; |
1906 | 35 | double dfY2 = oIter->second.MinY; |
1907 | 35 | double dfX3 = oIter->second.MaxX; |
1908 | 35 | double dfY3 = oIter->second.MaxY; |
1909 | 35 | double dfX4 = oIter->second.MinX; |
1910 | 35 | double dfY4 = oIter->second.MaxY; |
1911 | 35 | if (poCT->Transform(1, &dfX1, &dfY1) && |
1912 | 35 | poCT->Transform(1, &dfX2, &dfY2) && |
1913 | 34 | poCT->Transform(1, &dfX3, &dfY3) && |
1914 | 34 | poCT->Transform(1, &dfX4, &dfY4)) |
1915 | 34 | { |
1916 | 34 | sAOI.MinX = std::min(std::min(dfX1, dfX2), |
1917 | 34 | std::min(dfX3, dfX4)); |
1918 | 34 | sAOI.MinY = std::min(std::min(dfY1, dfY2), |
1919 | 34 | std::min(dfY3, dfY4)); |
1920 | 34 | sAOI.MaxX = std::max(std::max(dfX1, dfX2), |
1921 | 34 | std::max(dfX3, dfX4)); |
1922 | 34 | sAOI.MaxY = std::max(std::max(dfY1, dfY2), |
1923 | 34 | std::max(dfY3, dfY4)); |
1924 | 34 | bHasAOI = TRUE; |
1925 | 34 | bAOIFromLayer = true; |
1926 | 34 | } |
1927 | 35 | delete poCT; |
1928 | 35 | } |
1929 | 35 | } |
1930 | 35 | break; |
1931 | 35 | } |
1932 | 35 | } |
1933 | 35 | } |
1934 | | |
1935 | | // Clip the computed AOI with the union of the extent of the tile |
1936 | | // matrices |
1937 | 52 | if (bHasAOI && !bExtendBeyondDateLine) |
1938 | 37 | { |
1939 | 37 | OGREnvelope sUnionTM; |
1940 | 37 | for (const WMTSTileMatrix &oTM : oTMS.aoTM) |
1941 | 323 | { |
1942 | 323 | if (!sUnionTM.IsInit()) |
1943 | 37 | sUnionTM = oTM.GetExtent(); |
1944 | 286 | else |
1945 | 286 | sUnionTM.Merge(oTM.GetExtent()); |
1946 | 323 | } |
1947 | 37 | sAOI.Intersect(sUnionTM); |
1948 | 37 | } |
1949 | | |
1950 | | // Otherwise default to BoundingBox of the TMS |
1951 | 52 | if (!bHasAOI && oTMS.bBoundingBoxValid && |
1952 | 13 | (eExtentMethod == AUTO || eExtentMethod == TILE_MATRIX_SET)) |
1953 | 13 | { |
1954 | 13 | CPLDebug("WMTS", "Using TMS bounding box as layer extent"); |
1955 | 13 | sAOI = oTMS.sBoundingBox; |
1956 | 13 | bHasAOI = TRUE; |
1957 | 13 | } |
1958 | | |
1959 | | // Otherwise default to implied BoundingBox of the most precise TM |
1960 | 52 | if (!bHasAOI && (eExtentMethod == AUTO || |
1961 | 0 | eExtentMethod == MOST_PRECISE_TILE_MATRIX)) |
1962 | 2 | { |
1963 | 2 | const WMTSTileMatrix &oTM = oTMS.aoTM.back(); |
1964 | 2 | CPLDebug("WMTS", "Using TM level %s bounding box as layer extent", |
1965 | 2 | oTM.osIdentifier.c_str()); |
1966 | | |
1967 | 2 | sAOI = oTM.GetExtent(); |
1968 | 2 | bHasAOI = TRUE; |
1969 | 2 | } |
1970 | | |
1971 | 52 | if (!bHasAOI) |
1972 | 0 | { |
1973 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1974 | 0 | "Could not determine raster extent"); |
1975 | 0 | CPLDestroyXMLNode(psXML); |
1976 | 0 | delete poDS; |
1977 | 0 | return nullptr; |
1978 | 0 | } |
1979 | | |
1980 | 52 | if (CPLTestBool(CSLFetchNameValueDef( |
1981 | 52 | poOpenInfo->papszOpenOptions, |
1982 | 52 | "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX", |
1983 | 52 | bAOIFromLayer ? "NO" : "YES"))) |
1984 | 15 | { |
1985 | | // Clip with implied BoundingBox of the most precise TM |
1986 | | // Useful for http://tileserver.maptiler.com/wmts |
1987 | 15 | const WMTSTileMatrix &oTM = oTMS.aoTM.back(); |
1988 | 15 | const OGREnvelope sTMExtent = oTM.GetExtent(); |
1989 | 15 | OGREnvelope sAOINew(sAOI); |
1990 | | |
1991 | | // For |
1992 | | // https://data.linz.govt.nz/services;key=XXXXXXXX/wmts/1.0.0/set/69/WMTSCapabilities.xml |
1993 | | // only clip in Y since there's a warp over dateline. |
1994 | | // Update: it seems that the content of the server has changed since |
1995 | | // initial coding. So do X clipping in default mode. |
1996 | 15 | if (!bExtendBeyondDateLine) |
1997 | 15 | { |
1998 | 15 | sAOINew.MinX = std::max(sAOI.MinX, sTMExtent.MinX); |
1999 | 15 | sAOINew.MaxX = std::min(sAOI.MaxX, sTMExtent.MaxX); |
2000 | 15 | } |
2001 | 15 | sAOINew.MaxY = std::min(sAOI.MaxY, sTMExtent.MaxY); |
2002 | 15 | sAOINew.MinY = std::max(sAOI.MinY, sTMExtent.MinY); |
2003 | 15 | if (sAOI != sAOINew) |
2004 | 2 | { |
2005 | 2 | CPLDebug( |
2006 | 2 | "WMTS", |
2007 | 2 | "Layer extent has been restricted from " |
2008 | 2 | "(%f,%f,%f,%f) to (%f,%f,%f,%f) using the " |
2009 | 2 | "implied bounding box of the most precise tile matrix. " |
2010 | 2 | "You may disable this by specifying the " |
2011 | 2 | "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX open option " |
2012 | 2 | "to NO.", |
2013 | 2 | sAOI.MinX, sAOI.MinY, sAOI.MaxX, sAOI.MaxY, sAOINew.MinX, |
2014 | 2 | sAOINew.MinY, sAOINew.MaxX, sAOINew.MaxY); |
2015 | 2 | } |
2016 | 15 | sAOI = sAOINew; |
2017 | 15 | } |
2018 | | |
2019 | | // Clip with limits of most precise TM when available |
2020 | 52 | if (CPLTestBool(CSLFetchNameValueDef( |
2021 | 52 | poOpenInfo->papszOpenOptions, |
2022 | 52 | "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX_LIMITS", |
2023 | 52 | bAOIFromLayer ? "NO" : "YES"))) |
2024 | 15 | { |
2025 | 15 | const WMTSTileMatrix &oTM = oTMS.aoTM.back(); |
2026 | 15 | if (aoMapTileMatrixLimits.find(oTM.osIdentifier) != |
2027 | 15 | aoMapTileMatrixLimits.end()) |
2028 | 0 | { |
2029 | 0 | OGREnvelope sAOINew(sAOI); |
2030 | |
|
2031 | 0 | const WMTSTileMatrixLimits &oTMLimits = |
2032 | 0 | aoMapTileMatrixLimits[oTM.osIdentifier]; |
2033 | 0 | const OGREnvelope sTMLimitsExtent = oTMLimits.GetExtent(oTM); |
2034 | 0 | sAOINew.Intersect(sTMLimitsExtent); |
2035 | |
|
2036 | 0 | if (sAOI != sAOINew) |
2037 | 0 | { |
2038 | 0 | CPLDebug( |
2039 | 0 | "WMTS", |
2040 | 0 | "Layer extent has been restricted from " |
2041 | 0 | "(%f,%f,%f,%f) to (%f,%f,%f,%f) using the " |
2042 | 0 | "implied bounding box of the most precise tile matrix. " |
2043 | 0 | "You may disable this by specifying the " |
2044 | 0 | "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX_LIMITS open " |
2045 | 0 | "option " |
2046 | 0 | "to NO.", |
2047 | 0 | sAOI.MinX, sAOI.MinY, sAOI.MaxX, sAOI.MaxY, |
2048 | 0 | sAOINew.MinX, sAOINew.MinY, sAOINew.MaxX, sAOINew.MaxY); |
2049 | 0 | } |
2050 | 0 | sAOI = sAOINew; |
2051 | 0 | } |
2052 | 15 | } |
2053 | | |
2054 | 52 | if (!osProjection.empty()) |
2055 | 0 | { |
2056 | 0 | poDS->m_oSRS.SetFromUserInput( |
2057 | 0 | osProjection, |
2058 | 0 | OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()); |
2059 | 0 | } |
2060 | 52 | if (poDS->m_oSRS.IsEmpty()) |
2061 | 52 | { |
2062 | 52 | poDS->m_oSRS = oTMS.oSRS; |
2063 | 52 | } |
2064 | | |
2065 | 52 | if (osURLTileTemplate.empty()) |
2066 | 7 | { |
2067 | 7 | osURLTileTemplate = GetOperationKVPURL(psXML, "GetTile"); |
2068 | 7 | if (osURLTileTemplate.empty()) |
2069 | 7 | { |
2070 | 7 | CPLError(CE_Failure, CPLE_AppDefined, |
2071 | 7 | "No RESTful nor KVP GetTile operation found"); |
2072 | 7 | CPLDestroyXMLNode(psXML); |
2073 | 7 | delete poDS; |
2074 | 7 | return nullptr; |
2075 | 7 | } |
2076 | 0 | osURLTileTemplate = |
2077 | 0 | CPLURLAddKVP(osURLTileTemplate, "service", "WMTS"); |
2078 | 0 | osURLTileTemplate = |
2079 | 0 | CPLURLAddKVP(osURLTileTemplate, "request", "GetTile"); |
2080 | 0 | osURLTileTemplate = |
2081 | 0 | CPLURLAddKVP(osURLTileTemplate, "version", "1.0.0"); |
2082 | 0 | osURLTileTemplate = |
2083 | 0 | CPLURLAddKVP(osURLTileTemplate, "layer", osSelectLayer); |
2084 | 0 | osURLTileTemplate = |
2085 | 0 | CPLURLAddKVP(osURLTileTemplate, "style", osSelectStyle); |
2086 | 0 | osURLTileTemplate = |
2087 | 0 | CPLURLAddKVP(osURLTileTemplate, "format", osSelectTileFormat); |
2088 | 0 | osURLTileTemplate = |
2089 | 0 | CPLURLAddKVP(osURLTileTemplate, "TileMatrixSet", osSelectTMS); |
2090 | 0 | osURLTileTemplate += "&TileMatrix={TileMatrix}"; |
2091 | 0 | osURLTileTemplate += "&TileRow=${y}"; |
2092 | 0 | osURLTileTemplate += "&TileCol=${x}"; |
2093 | |
|
2094 | 0 | std::map<CPLString, CPLString>::iterator oIter = |
2095 | 0 | aoMapDimensions.begin(); |
2096 | 0 | for (; oIter != aoMapDimensions.end(); ++oIter) |
2097 | 0 | { |
2098 | 0 | osURLTileTemplate = CPLURLAddKVP(osURLTileTemplate, |
2099 | 0 | oIter->first, oIter->second); |
2100 | 0 | } |
2101 | | // CPLDebug("WMTS", "osURLTileTemplate = %s", |
2102 | | // osURLTileTemplate.c_str()); |
2103 | 0 | } |
2104 | 45 | else |
2105 | 45 | { |
2106 | 45 | osURLTileTemplate = |
2107 | 45 | Replace(osURLTileTemplate, "{Style}", osSelectStyle); |
2108 | 45 | osURLTileTemplate = |
2109 | 45 | Replace(osURLTileTemplate, "{TileMatrixSet}", osSelectTMS); |
2110 | 45 | osURLTileTemplate = Replace(osURLTileTemplate, "{TileCol}", "${x}"); |
2111 | 45 | osURLTileTemplate = Replace(osURLTileTemplate, "{TileRow}", "${y}"); |
2112 | | |
2113 | 45 | std::map<CPLString, CPLString>::iterator oIter = |
2114 | 45 | aoMapDimensions.begin(); |
2115 | 45 | for (; oIter != aoMapDimensions.end(); ++oIter) |
2116 | 0 | { |
2117 | 0 | osURLTileTemplate = Replace( |
2118 | 0 | osURLTileTemplate, CPLSPrintf("{%s}", oIter->first.c_str()), |
2119 | 0 | oIter->second); |
2120 | 0 | } |
2121 | 45 | } |
2122 | 45 | osURLTileTemplate += osExtraQueryParameters; |
2123 | | |
2124 | 45 | if (osURLFeatureInfoTemplate.empty() && !osSelectInfoFormat.empty()) |
2125 | 0 | { |
2126 | 0 | osURLFeatureInfoTemplate = |
2127 | 0 | GetOperationKVPURL(psXML, "GetFeatureInfo"); |
2128 | 0 | if (!osURLFeatureInfoTemplate.empty()) |
2129 | 0 | { |
2130 | 0 | osURLFeatureInfoTemplate = |
2131 | 0 | CPLURLAddKVP(osURLFeatureInfoTemplate, "service", "WMTS"); |
2132 | 0 | osURLFeatureInfoTemplate = CPLURLAddKVP( |
2133 | 0 | osURLFeatureInfoTemplate, "request", "GetFeatureInfo"); |
2134 | 0 | osURLFeatureInfoTemplate = |
2135 | 0 | CPLURLAddKVP(osURLFeatureInfoTemplate, "version", "1.0.0"); |
2136 | 0 | osURLFeatureInfoTemplate = CPLURLAddKVP( |
2137 | 0 | osURLFeatureInfoTemplate, "layer", osSelectLayer); |
2138 | 0 | osURLFeatureInfoTemplate = CPLURLAddKVP( |
2139 | 0 | osURLFeatureInfoTemplate, "style", osSelectStyle); |
2140 | | // osURLFeatureInfoTemplate = |
2141 | | // CPLURLAddKVP(osURLFeatureInfoTemplate, "format", |
2142 | | // osSelectTileFormat); |
2143 | 0 | osURLFeatureInfoTemplate = CPLURLAddKVP( |
2144 | 0 | osURLFeatureInfoTemplate, "InfoFormat", osSelectInfoFormat); |
2145 | 0 | osURLFeatureInfoTemplate += "&TileMatrixSet={TileMatrixSet}"; |
2146 | 0 | osURLFeatureInfoTemplate += "&TileMatrix={TileMatrix}"; |
2147 | 0 | osURLFeatureInfoTemplate += "&TileRow={TileRow}"; |
2148 | 0 | osURLFeatureInfoTemplate += "&TileCol={TileCol}"; |
2149 | 0 | osURLFeatureInfoTemplate += "&J={J}"; |
2150 | 0 | osURLFeatureInfoTemplate += "&I={I}"; |
2151 | |
|
2152 | 0 | std::map<CPLString, CPLString>::iterator oIter = |
2153 | 0 | aoMapDimensions.begin(); |
2154 | 0 | for (; oIter != aoMapDimensions.end(); ++oIter) |
2155 | 0 | { |
2156 | 0 | osURLFeatureInfoTemplate = CPLURLAddKVP( |
2157 | 0 | osURLFeatureInfoTemplate, oIter->first, oIter->second); |
2158 | 0 | } |
2159 | | // CPLDebug("WMTS", "osURLFeatureInfoTemplate = %s", |
2160 | | // osURLFeatureInfoTemplate.c_str()); |
2161 | 0 | } |
2162 | 0 | } |
2163 | 45 | else |
2164 | 45 | { |
2165 | 45 | osURLFeatureInfoTemplate = |
2166 | 45 | Replace(osURLFeatureInfoTemplate, "{Style}", osSelectStyle); |
2167 | | |
2168 | 45 | std::map<CPLString, CPLString>::iterator oIter = |
2169 | 45 | aoMapDimensions.begin(); |
2170 | 45 | for (; oIter != aoMapDimensions.end(); ++oIter) |
2171 | 0 | { |
2172 | 0 | osURLFeatureInfoTemplate = Replace( |
2173 | 0 | osURLFeatureInfoTemplate, |
2174 | 0 | CPLSPrintf("{%s}", oIter->first.c_str()), oIter->second); |
2175 | 0 | } |
2176 | 45 | } |
2177 | 45 | if (!osURLFeatureInfoTemplate.empty()) |
2178 | 0 | osURLFeatureInfoTemplate += osExtraQueryParameters; |
2179 | 45 | poDS->osURLFeatureInfoTemplate = osURLFeatureInfoTemplate; |
2180 | 45 | CPL_IGNORE_RET_VAL(osURLFeatureInfoTemplate); |
2181 | | |
2182 | | // Build all TMS datasets, wrapped in VRT datasets |
2183 | 299 | for (int i = static_cast<int>(oTMS.aoTM.size() - 1); i >= 0; i--) |
2184 | 281 | { |
2185 | 281 | const WMTSTileMatrix &oTM = oTMS.aoTM[i]; |
2186 | 281 | double dfRasterXSize = (sAOI.MaxX - sAOI.MinX) / oTM.dfPixelSize; |
2187 | 281 | double dfRasterYSize = (sAOI.MaxY - sAOI.MinY) / oTM.dfPixelSize; |
2188 | 281 | if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX) |
2189 | 2 | { |
2190 | 2 | continue; |
2191 | 2 | } |
2192 | | |
2193 | 279 | if (poDS->apoDatasets.empty()) |
2194 | 45 | { |
2195 | | // Align AOI on pixel boundaries with respect to TopLeftCorner |
2196 | | // of this tile matrix |
2197 | 45 | poDS->m_gt.xorig = |
2198 | 45 | oTM.dfTLX + |
2199 | 45 | floor((sAOI.MinX - oTM.dfTLX) / oTM.dfPixelSize + 1e-10) * |
2200 | 45 | oTM.dfPixelSize; |
2201 | 45 | poDS->m_gt.xscale = oTM.dfPixelSize; |
2202 | 45 | poDS->m_gt.xrot = 0.0; |
2203 | 45 | poDS->m_gt.yorig = |
2204 | 45 | oTM.dfTLY + |
2205 | 45 | ceil((sAOI.MaxY - oTM.dfTLY) / oTM.dfPixelSize - 1e-10) * |
2206 | 45 | oTM.dfPixelSize; |
2207 | 45 | poDS->m_gt.yrot = 0.0; |
2208 | 45 | poDS->m_gt.yscale = -oTM.dfPixelSize; |
2209 | 45 | poDS->nRasterXSize = |
2210 | 45 | int(0.5 + (sAOI.MaxX - poDS->m_gt.xorig) / oTM.dfPixelSize); |
2211 | 45 | poDS->nRasterYSize = |
2212 | 45 | int(0.5 + (poDS->m_gt.yorig - sAOI.MinY) / oTM.dfPixelSize); |
2213 | 45 | } |
2214 | | |
2215 | 279 | const int nRasterXSize = int( |
2216 | 279 | 0.5 + poDS->nRasterXSize / oTM.dfPixelSize * poDS->m_gt.xscale); |
2217 | 279 | const int nRasterYSize = int( |
2218 | 279 | 0.5 + poDS->nRasterYSize / oTM.dfPixelSize * poDS->m_gt.xscale); |
2219 | 279 | if (!poDS->apoDatasets.empty() && |
2220 | 234 | (nRasterXSize < 128 || nRasterYSize < 128)) |
2221 | 19 | { |
2222 | 19 | break; |
2223 | 19 | } |
2224 | 260 | CPLString osURL( |
2225 | 260 | Replace(osURLTileTemplate, "{TileMatrix}", oTM.osIdentifier)); |
2226 | | |
2227 | 260 | const double dfTileWidthUnits = oTM.dfPixelSize * oTM.nTileWidth; |
2228 | 260 | const double dfTileHeightUnits = oTM.dfPixelSize * oTM.nTileHeight; |
2229 | | |
2230 | | // Get bounds of this tile matrix / tile matrix limits |
2231 | 260 | auto sTMExtent = oTM.GetExtent(); |
2232 | 260 | if (aoMapTileMatrixLimits.find(oTM.osIdentifier) != |
2233 | 260 | aoMapTileMatrixLimits.end()) |
2234 | 0 | { |
2235 | 0 | const WMTSTileMatrixLimits &oTMLimits = |
2236 | 0 | aoMapTileMatrixLimits[oTM.osIdentifier]; |
2237 | 0 | sTMExtent.Intersect(oTMLimits.GetExtent(oTM)); |
2238 | 0 | } |
2239 | | |
2240 | | // Compute the shift in terms of tiles between AOI and TM origin |
2241 | 260 | const int nTileX = static_cast<int>( |
2242 | 260 | floor(std::max(sTMExtent.MinX, poDS->m_gt.xorig) - oTM.dfTLX + |
2243 | 260 | 1e-10) / |
2244 | 260 | dfTileWidthUnits); |
2245 | 260 | const int nTileY = static_cast<int>( |
2246 | 260 | floor(oTM.dfTLY - std::min(poDS->m_gt.yorig, sTMExtent.MaxY) + |
2247 | 260 | 1e-10) / |
2248 | 260 | dfTileHeightUnits); |
2249 | | |
2250 | | // Compute extent of this zoom level slightly larger than the AOI |
2251 | | // and aligned on tile boundaries at this TM |
2252 | 260 | double dfULX = oTM.dfTLX + nTileX * dfTileWidthUnits; |
2253 | 260 | double dfULY = oTM.dfTLY - nTileY * dfTileHeightUnits; |
2254 | 260 | double dfLRX = |
2255 | 260 | poDS->m_gt.xorig + poDS->nRasterXSize * poDS->m_gt.xscale; |
2256 | 260 | double dfLRY = |
2257 | 260 | poDS->m_gt.yorig + poDS->nRasterYSize * poDS->m_gt.yscale; |
2258 | 260 | dfLRX = dfULX + ceil((dfLRX - dfULX) / dfTileWidthUnits - 1e-10) * |
2259 | 260 | dfTileWidthUnits; |
2260 | 260 | dfLRY = dfULY + floor((dfLRY - dfULY) / dfTileHeightUnits + 1e-10) * |
2261 | 260 | dfTileHeightUnits; |
2262 | | |
2263 | | // Clip TMS extent to the one of this TM |
2264 | 260 | if (!bExtendBeyondDateLine) |
2265 | 260 | dfLRX = std::min(dfLRX, sTMExtent.MaxX); |
2266 | 260 | dfLRY = std::max(dfLRY, sTMExtent.MinY); |
2267 | | |
2268 | 260 | const double dfSizeX = 0.5 + (dfLRX - dfULX) / oTM.dfPixelSize; |
2269 | 260 | const double dfSizeY = 0.5 + (dfULY - dfLRY) / oTM.dfPixelSize; |
2270 | 260 | if (dfSizeX > INT_MAX || dfSizeY > INT_MAX) |
2271 | 0 | { |
2272 | 0 | continue; |
2273 | 0 | } |
2274 | 260 | if (poDS->apoDatasets.empty()) |
2275 | 45 | { |
2276 | 45 | CPLDebug("WMTS", "Using tilematrix=%s (zoom level %d)", |
2277 | 45 | oTMS.aoTM[i].osIdentifier.c_str(), i); |
2278 | 45 | oTMS.aoTM.resize(1 + i); |
2279 | 45 | poDS->oTMS = oTMS; |
2280 | 45 | } |
2281 | | |
2282 | 260 | const int nSizeX = static_cast<int>(dfSizeX); |
2283 | 260 | const int nSizeY = static_cast<int>(dfSizeY); |
2284 | | |
2285 | 260 | const double dfDateLineX = |
2286 | 260 | oTM.dfTLX + oTM.nMatrixWidth * dfTileWidthUnits; |
2287 | 260 | const int nSizeX1 = |
2288 | 260 | int(0.5 + (dfDateLineX - dfULX) / oTM.dfPixelSize); |
2289 | 260 | const int nSizeX2 = |
2290 | 260 | int(0.5 + (dfLRX - dfDateLineX) / oTM.dfPixelSize); |
2291 | 260 | if (bExtendBeyondDateLine && dfDateLineX > dfLRX) |
2292 | 0 | { |
2293 | 0 | CPLDebug("WMTS", "ExtendBeyondDateLine ignored in that case"); |
2294 | 0 | bExtendBeyondDateLine = FALSE; |
2295 | 0 | } |
2296 | | |
2297 | 260 | #define WMS_TMS_TEMPLATE \ |
2298 | 260 | "<GDAL_WMS>" \ |
2299 | 260 | "<Service name=\"TMS\">" \ |
2300 | 260 | " <ServerUrl>%s</ServerUrl>" \ |
2301 | 260 | "</Service>" \ |
2302 | 260 | "<DataWindow>" \ |
2303 | 260 | " <UpperLeftX>%.16g</UpperLeftX>" \ |
2304 | 260 | " <UpperLeftY>%.16g</UpperLeftY>" \ |
2305 | 260 | " <LowerRightX>%.16g</LowerRightX>" \ |
2306 | 260 | " <LowerRightY>%.16g</LowerRightY>" \ |
2307 | 260 | " <TileLevel>0</TileLevel>" \ |
2308 | 260 | " <TileX>%d</TileX>" \ |
2309 | 260 | " <TileY>%d</TileY>" \ |
2310 | 260 | " <SizeX>%d</SizeX>" \ |
2311 | 260 | " <SizeY>%d</SizeY>" \ |
2312 | 260 | " <YOrigin>top</YOrigin>" \ |
2313 | 260 | "</DataWindow>" \ |
2314 | 260 | "<BlockSizeX>%d</BlockSizeX>" \ |
2315 | 260 | "<BlockSizeY>%d</BlockSizeY>" \ |
2316 | 260 | "<BandsCount>%d</BandsCount>" \ |
2317 | 260 | "<DataType>%s</DataType>" \ |
2318 | 260 | "%s" \ |
2319 | 260 | "</GDAL_WMS>" |
2320 | | |
2321 | 260 | CPLString osStr(CPLSPrintf( |
2322 | 260 | WMS_TMS_TEMPLATE, WMTSEscapeXML(osURL).c_str(), dfULX, dfULY, |
2323 | 260 | (bExtendBeyondDateLine) ? dfDateLineX : dfLRX, dfLRY, nTileX, |
2324 | 260 | nTileY, (bExtendBeyondDateLine) ? nSizeX1 : nSizeX, nSizeY, |
2325 | 260 | oTM.nTileWidth, oTM.nTileHeight, nBands, |
2326 | 260 | GDALGetDataTypeName(eDataType), osOtherXML.c_str())); |
2327 | 260 | const auto eLastErrorType = CPLGetLastErrorType(); |
2328 | 260 | const auto eLastErrorNum = CPLGetLastErrorNo(); |
2329 | 260 | const std::string osLastErrorMsg = CPLGetLastErrorMsg(); |
2330 | 260 | GDALDataset *poWMSDS = GDALDataset::Open( |
2331 | 260 | osStr, GDAL_OF_RASTER | GDAL_OF_SHARED | GDAL_OF_VERBOSE_ERROR, |
2332 | 260 | nullptr, nullptr, nullptr); |
2333 | 260 | if (poWMSDS == nullptr) |
2334 | 8 | { |
2335 | 8 | CPLDestroyXMLNode(psXML); |
2336 | 8 | delete poDS; |
2337 | 8 | return nullptr; |
2338 | 8 | } |
2339 | | // Restore error state to what it was prior to WMS dataset opening |
2340 | | // if WMS dataset opening did not cause any new error to be emitted |
2341 | 252 | if (CPLGetLastErrorType() == CE_None) |
2342 | 252 | CPLErrorSetState(eLastErrorType, eLastErrorNum, |
2343 | 252 | osLastErrorMsg.c_str()); |
2344 | | |
2345 | 252 | VRTDatasetH hVRTDS = VRTCreate(nRasterXSize, nRasterYSize); |
2346 | 1.26k | for (int iBand = 1; iBand <= nBands; iBand++) |
2347 | 1.00k | { |
2348 | 1.00k | VRTAddBand(hVRTDS, eDataType, nullptr); |
2349 | 1.00k | } |
2350 | | |
2351 | 252 | int nSrcXOff, nSrcYOff, nDstXOff, nDstYOff; |
2352 | | |
2353 | 252 | nSrcXOff = 0; |
2354 | 252 | nDstXOff = static_cast<int>( |
2355 | 252 | std::round((dfULX - poDS->m_gt.xorig) / oTM.dfPixelSize)); |
2356 | | |
2357 | 252 | nSrcYOff = 0; |
2358 | 252 | nDstYOff = static_cast<int>( |
2359 | 252 | std::round((poDS->m_gt.yorig - dfULY) / oTM.dfPixelSize)); |
2360 | | |
2361 | 252 | if (bExtendBeyondDateLine) |
2362 | 0 | { |
2363 | 0 | int nSrcXOff2, nDstXOff2; |
2364 | |
|
2365 | 0 | nSrcXOff2 = 0; |
2366 | 0 | nDstXOff2 = static_cast<int>(std::round( |
2367 | 0 | (dfDateLineX - poDS->m_gt.xorig) / oTM.dfPixelSize)); |
2368 | |
|
2369 | 0 | osStr = CPLSPrintf( |
2370 | 0 | WMS_TMS_TEMPLATE, WMTSEscapeXML(osURL).c_str(), |
2371 | 0 | -dfDateLineX, dfULY, dfLRX - 2 * dfDateLineX, dfLRY, 0, |
2372 | 0 | nTileY, nSizeX2, nSizeY, oTM.nTileWidth, oTM.nTileHeight, |
2373 | 0 | nBands, GDALGetDataTypeName(eDataType), osOtherXML.c_str()); |
2374 | |
|
2375 | 0 | GDALDataset *poWMSDS2 = |
2376 | 0 | GDALDataset::Open(osStr, GDAL_OF_RASTER | GDAL_OF_SHARED, |
2377 | 0 | nullptr, nullptr, nullptr); |
2378 | 0 | CPLAssert(poWMSDS2); |
2379 | |
|
2380 | 0 | for (int iBand = 1; iBand <= nBands; iBand++) |
2381 | 0 | { |
2382 | 0 | VRTSourcedRasterBandH hVRTBand = |
2383 | 0 | reinterpret_cast<VRTSourcedRasterBandH>( |
2384 | 0 | GDALGetRasterBand(hVRTDS, iBand)); |
2385 | 0 | VRTAddSimpleSource( |
2386 | 0 | hVRTBand, GDALGetRasterBand(poWMSDS, iBand), nSrcXOff, |
2387 | 0 | nSrcYOff, nSizeX1, nSizeY, nDstXOff, nDstYOff, nSizeX1, |
2388 | 0 | nSizeY, "NEAR", VRT_NODATA_UNSET); |
2389 | 0 | VRTAddSimpleSource( |
2390 | 0 | hVRTBand, GDALGetRasterBand(poWMSDS2, iBand), nSrcXOff2, |
2391 | 0 | nSrcYOff, nSizeX2, nSizeY, nDstXOff2, nDstYOff, nSizeX2, |
2392 | 0 | nSizeY, "NEAR", VRT_NODATA_UNSET); |
2393 | 0 | } |
2394 | |
|
2395 | 0 | poWMSDS2->Dereference(); |
2396 | 0 | } |
2397 | 252 | else |
2398 | 252 | { |
2399 | 1.26k | for (int iBand = 1; iBand <= nBands; iBand++) |
2400 | 1.00k | { |
2401 | 1.00k | VRTSourcedRasterBandH hVRTBand = |
2402 | 1.00k | reinterpret_cast<VRTSourcedRasterBandH>( |
2403 | 1.00k | GDALGetRasterBand(hVRTDS, iBand)); |
2404 | 1.00k | VRTAddSimpleSource( |
2405 | 1.00k | hVRTBand, GDALGetRasterBand(poWMSDS, iBand), nSrcXOff, |
2406 | 1.00k | nSrcYOff, nSizeX, nSizeY, nDstXOff, nDstYOff, nSizeX, |
2407 | 1.00k | nSizeY, "NEAR", VRT_NODATA_UNSET); |
2408 | 1.00k | } |
2409 | 252 | } |
2410 | | |
2411 | 252 | poWMSDS->Dereference(); |
2412 | | |
2413 | 252 | poDS->apoDatasets.push_back(GDALDataset::FromHandle(hVRTDS)); |
2414 | 252 | } |
2415 | | |
2416 | 37 | if (poDS->apoDatasets.empty()) |
2417 | 0 | { |
2418 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "No zoom level found"); |
2419 | 0 | CPLDestroyXMLNode(psXML); |
2420 | 0 | delete poDS; |
2421 | 0 | return nullptr; |
2422 | 0 | } |
2423 | | |
2424 | 37 | poDS->SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL", |
2425 | 37 | GDAL_MDD_IMAGE_STRUCTURE); |
2426 | 185 | for (int i = 0; i < nBands; i++) |
2427 | 148 | poDS->SetBand(i + 1, new WMTSBand(poDS, i + 1, eDataType)); |
2428 | | |
2429 | 37 | poDS->osXML = "<GDAL_WMTS>\n"; |
2430 | 37 | poDS->osXML += " <GetCapabilitiesUrl>" + |
2431 | 37 | WMTSEscapeXML(osGetCapabilitiesURL) + |
2432 | 37 | "</GetCapabilitiesUrl>\n"; |
2433 | 37 | if (!osSelectLayer.empty()) |
2434 | 37 | poDS->osXML += |
2435 | 37 | " <Layer>" + WMTSEscapeXML(osSelectLayer) + "</Layer>\n"; |
2436 | 37 | if (!osSelectStyle.empty()) |
2437 | 37 | poDS->osXML += |
2438 | 37 | " <Style>" + WMTSEscapeXML(osSelectStyle) + "</Style>\n"; |
2439 | 37 | if (!osSelectTMS.empty()) |
2440 | 37 | poDS->osXML += " <TileMatrixSet>" + WMTSEscapeXML(osSelectTMS) + |
2441 | 37 | "</TileMatrixSet>\n"; |
2442 | 37 | if (!osMaxTileMatrixIdentifier.empty()) |
2443 | 0 | poDS->osXML += " <TileMatrix>" + |
2444 | 0 | WMTSEscapeXML(osMaxTileMatrixIdentifier) + |
2445 | 0 | "</TileMatrix>\n"; |
2446 | 37 | if (nUserMaxZoomLevel >= 0) |
2447 | 0 | poDS->osXML += " <ZoomLevel>" + |
2448 | 0 | CPLString().Printf("%d", nUserMaxZoomLevel) + |
2449 | 0 | "</ZoomLevel>\n"; |
2450 | 37 | if (nCountTileFormat > 1 && !osSelectTileFormat.empty()) |
2451 | 0 | poDS->osXML += " <Format>" + WMTSEscapeXML(osSelectTileFormat) + |
2452 | 0 | "</Format>\n"; |
2453 | 37 | if (nCountInfoFormat > 1 && !osSelectInfoFormat.empty()) |
2454 | 0 | poDS->osXML += " <InfoFormat>" + |
2455 | 0 | WMTSEscapeXML(osSelectInfoFormat) + |
2456 | 0 | "</InfoFormat>\n"; |
2457 | 37 | poDS->osXML += " <DataWindow>\n"; |
2458 | 37 | poDS->osXML += CPLSPrintf(" <UpperLeftX>%.16g</UpperLeftX>\n", |
2459 | 37 | poDS->m_gt.xorig); |
2460 | 37 | poDS->osXML += CPLSPrintf(" <UpperLeftY>%.16g</UpperLeftY>\n", |
2461 | 37 | poDS->m_gt.yorig); |
2462 | 37 | poDS->osXML += CPLSPrintf(" <LowerRightX>%.16g</LowerRightX>\n", |
2463 | 37 | poDS->m_gt.xorig + |
2464 | 37 | poDS->m_gt.xscale * poDS->nRasterXSize); |
2465 | 37 | poDS->osXML += CPLSPrintf(" <LowerRightY>%.16g</LowerRightY>\n", |
2466 | 37 | poDS->m_gt.yorig + |
2467 | 37 | poDS->m_gt.yscale * poDS->nRasterYSize); |
2468 | 37 | poDS->osXML += " </DataWindow>\n"; |
2469 | 37 | if (bExtendBeyondDateLine) |
2470 | 0 | poDS->osXML += |
2471 | 0 | " <ExtendBeyondDateLine>true</ExtendBeyondDateLine>\n"; |
2472 | 37 | poDS->osXML += CPLSPrintf(" <BandsCount>%d</BandsCount>\n", nBands); |
2473 | 37 | poDS->osXML += CPLSPrintf(" <DataType>%s</DataType>\n", |
2474 | 37 | GDALGetDataTypeName(eDataType)); |
2475 | 37 | poDS->osXML += " <Cache />\n"; |
2476 | 37 | poDS->osXML += " <UnsafeSSL>true</UnsafeSSL>\n"; |
2477 | 37 | poDS->osXML += " <ZeroBlockHttpCodes>204,404</ZeroBlockHttpCodes>\n"; |
2478 | 37 | poDS->osXML += |
2479 | 37 | " <ZeroBlockOnServerException>true</ZeroBlockOnServerException>\n"; |
2480 | 37 | poDS->osXML += "</GDAL_WMTS>\n"; |
2481 | 37 | } |
2482 | | |
2483 | 37 | CPLDestroyXMLNode(psXML); |
2484 | | |
2485 | 37 | poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY); |
2486 | 37 | return poDS; |
2487 | 57 | } |
2488 | | |
2489 | | /************************************************************************/ |
2490 | | /* CreateCopy() */ |
2491 | | /************************************************************************/ |
2492 | | |
2493 | | GDALDataset *WMTSDataset::CreateCopy(const char *pszFilename, |
2494 | | GDALDataset *poSrcDS, |
2495 | | CPL_UNUSED int bStrict, |
2496 | | CPL_UNUSED CSLConstList papszOptions, |
2497 | | CPL_UNUSED GDALProgressFunc pfnProgress, |
2498 | | CPL_UNUSED void *pProgressData) |
2499 | 0 | { |
2500 | 0 | if (poSrcDS->GetDriver() == nullptr || |
2501 | 0 | poSrcDS->GetDriver() != GDALGetDriverByName("WMTS")) |
2502 | 0 | { |
2503 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
2504 | 0 | "Source dataset must be a WMTS dataset"); |
2505 | 0 | return nullptr; |
2506 | 0 | } |
2507 | | |
2508 | 0 | const char *pszXML = poSrcDS->GetMetadataItem("XML", "WMTS"); |
2509 | 0 | if (pszXML == nullptr) |
2510 | 0 | { |
2511 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2512 | 0 | "Cannot get XML definition of source WMTS dataset"); |
2513 | 0 | return nullptr; |
2514 | 0 | } |
2515 | | |
2516 | 0 | VSILFILE *fp = VSIFOpenL(pszFilename, "wb"); |
2517 | 0 | if (fp == nullptr) |
2518 | 0 | return nullptr; |
2519 | | |
2520 | 0 | VSIFWriteL(pszXML, 1, strlen(pszXML), fp); |
2521 | 0 | VSIFCloseL(fp); |
2522 | |
|
2523 | 0 | GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly); |
2524 | 0 | return Open(&oOpenInfo); |
2525 | 0 | } |
2526 | | |
2527 | | /************************************************************************/ |
2528 | | /* GDALRegister_WMTS() */ |
2529 | | /************************************************************************/ |
2530 | | |
2531 | | void GDALRegister_WMTS() |
2532 | | |
2533 | 24 | { |
2534 | 24 | if (!GDAL_CHECK_VERSION("WMTS driver")) |
2535 | 0 | return; |
2536 | | |
2537 | 24 | if (GDALGetDriverByName(DRIVER_NAME) != nullptr) |
2538 | 0 | return; |
2539 | | |
2540 | 24 | GDALDriver *poDriver = new GDALDriver(); |
2541 | 24 | WMTSDriverSetCommonMetadata(poDriver); |
2542 | | |
2543 | 24 | poDriver->pfnOpen = WMTSDataset::Open; |
2544 | 24 | poDriver->pfnCreateCopy = WMTSDataset::CreateCopy; |
2545 | | |
2546 | 24 | GetGDALDriverManager()->RegisterDriver(poDriver); |
2547 | 24 | } |