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