Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Class to handle TileMatrixSet
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_json.h"
14 : #include "gdal_priv.h"
15 : #include "ogr_spatialref.h"
16 :
17 : #include <algorithm>
18 : #include <cmath>
19 : #include <cfloat>
20 : #include <limits>
21 :
22 : #include "tilematrixset.hpp"
23 :
24 : //! @cond Doxygen_Suppress
25 :
26 : namespace gdal
27 : {
28 :
29 : /************************************************************************/
30 : /* listPredefinedTileMatrixSets() */
31 : /************************************************************************/
32 :
33 580 : std::vector<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
34 : {
35 : std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
36 : "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
37 4640 : "PseudoTMS_GlobalMercator"};
38 580 : const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
39 580 : if (pszSomeFile)
40 : {
41 1160 : std::set<std::string> set;
42 : CPLStringList aosList(
43 1160 : VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
44 95120 : for (int i = 0; i < aosList.size(); i++)
45 : {
46 94540 : const size_t nLen = strlen(aosList[i]);
47 186760 : if (nLen > strlen("tms_") + strlen(".json") &&
48 96860 : STARTS_WITH(aosList[i], "tms_") &&
49 2320 : EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
50 : {
51 4640 : std::string id(aosList[i] + strlen("tms_"),
52 4640 : nLen - (strlen("tms_") + strlen(".json")));
53 2320 : set.insert(std::move(id));
54 : }
55 : }
56 2900 : for (const std::string &id : set)
57 2320 : l.push_back(id);
58 : }
59 580 : return l;
60 : }
61 :
62 : /************************************************************************/
63 : /* parse() */
64 : /************************************************************************/
65 :
66 5451 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
67 : {
68 10902 : CPLJSONDocument oDoc;
69 10902 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
70 :
71 5451 : constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
72 :
73 5451 : if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
74 4729 : EQUAL(fileOrDef, "WebMercatorQuad") ||
75 4729 : EQUAL(
76 : fileOrDef,
77 : "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
78 : {
79 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326
80 : * (WMTS 1.0), Annex E.4 */
81 : // or https://docs.ogc.org/is/17-083r4/17-083r4.html#toc49
82 722 : poTMS->mTitle = "GoogleMapsCompatible";
83 722 : poTMS->mIdentifier = "GoogleMapsCompatible";
84 722 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
85 722 : poTMS->mBbox.mCrs = poTMS->mCrs;
86 722 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
87 722 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
88 722 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
89 722 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
90 722 : poTMS->mWellKnownScaleSet =
91 722 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
92 23104 : for (int i = 0; i <= 30; i++)
93 : {
94 44764 : TileMatrix tm;
95 22382 : tm.mId = CPLSPrintf("%d", i);
96 22382 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
97 22382 : tm.mResY = tm.mResX;
98 22382 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
99 22382 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
100 22382 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
101 22382 : tm.mTileWidth = 256;
102 22382 : tm.mTileHeight = 256;
103 22382 : tm.mMatrixWidth = 1 << i;
104 22382 : tm.mMatrixHeight = 1 << i;
105 22382 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
106 : }
107 722 : return poTMS;
108 : }
109 :
110 4729 : if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
111 4150 : EQUAL(fileOrDef, "http://www.opengis.net/def/tilematrixset/OGC/1.0/"
112 : "WorldMercatorWGS84Quad"))
113 : {
114 : // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51
115 579 : poTMS->mTitle = "WorldMercatorWGS84Quad";
116 579 : poTMS->mIdentifier = "WorldMercatorWGS84Quad";
117 579 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
118 579 : poTMS->mBbox.mCrs = poTMS->mCrs;
119 579 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
120 579 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
121 579 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
122 579 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
123 579 : poTMS->mWellKnownScaleSet =
124 579 : "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
125 18528 : for (int i = 0; i <= 30; i++)
126 : {
127 35898 : TileMatrix tm;
128 17949 : tm.mId = CPLSPrintf("%d", i);
129 17949 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
130 17949 : tm.mResY = tm.mResX;
131 17949 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
132 17949 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
133 17949 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
134 17949 : tm.mTileWidth = 256;
135 17949 : tm.mTileHeight = 256;
136 17949 : tm.mMatrixWidth = 1 << i;
137 17949 : tm.mMatrixHeight = 1 << i;
138 17949 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
139 : }
140 579 : return poTMS;
141 : }
142 :
143 4150 : if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
144 : {
145 : /* See global-mercator at
146 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
147 579 : poTMS->mTitle = "PseudoTMS_GlobalMercator";
148 579 : poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
149 579 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
150 579 : poTMS->mBbox.mCrs = poTMS->mCrs;
151 579 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
152 579 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
153 579 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
154 579 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
155 17949 : for (int i = 0; i <= 29; i++)
156 : {
157 34740 : TileMatrix tm;
158 17370 : tm.mId = CPLSPrintf("%d", i);
159 17370 : tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
160 17370 : tm.mResY = tm.mResX;
161 17370 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
162 17370 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
163 17370 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
164 17370 : tm.mTileWidth = 256;
165 17370 : tm.mTileHeight = 256;
166 17370 : tm.mMatrixWidth = 2 * (1 << i);
167 17370 : tm.mMatrixHeight = 2 * (1 << i);
168 17370 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
169 : }
170 579 : return poTMS;
171 : }
172 :
173 3571 : if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
174 3565 : EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
175 3565 : EQUAL(fileOrDef, "WorldCRS84Quad") ||
176 2984 : EQUAL(
177 : fileOrDef,
178 : "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
179 : {
180 : /* See InspireCRS84Quad at
181 : * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
182 : */
183 : /* This is exactly the same as PseudoTMS_GlobalGeodetic */
184 : /* See global-geodetic at
185 : * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
186 : // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
187 587 : poTMS->mTitle = "WorldCRS84Quad";
188 587 : poTMS->mIdentifier = "WorldCRS84Quad";
189 587 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
190 587 : poTMS->mBbox.mCrs = poTMS->mCrs;
191 587 : poTMS->mBbox.mLowerCornerX = -180;
192 587 : poTMS->mBbox.mLowerCornerY = -90;
193 587 : poTMS->mBbox.mUpperCornerX = 180;
194 587 : poTMS->mBbox.mUpperCornerY = 90;
195 587 : poTMS->mWellKnownScaleSet =
196 587 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
197 : // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
198 : // and at 30 it would overflow int32.
199 18197 : for (int i = 0; i <= 29; i++)
200 : {
201 35220 : TileMatrix tm;
202 17610 : tm.mId = CPLSPrintf("%d", i);
203 17610 : tm.mResX = 180. / 256 / (1 << i);
204 17610 : tm.mResY = tm.mResX;
205 17610 : tm.mScaleDenominator =
206 17610 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
207 17610 : tm.mTopLeftX = -180;
208 17610 : tm.mTopLeftY = 90;
209 17610 : tm.mTileWidth = 256;
210 17610 : tm.mTileHeight = 256;
211 17610 : tm.mMatrixWidth = 2 * (1 << i);
212 17610 : tm.mMatrixHeight = 1 << i;
213 17610 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
214 : }
215 587 : return poTMS;
216 : }
217 :
218 2984 : if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
219 2387 : EQUAL(fileOrDef,
220 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad"))
221 : {
222 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
223 : Annex E.3 */
224 597 : poTMS->mTitle = "GoogleCRS84Quad";
225 597 : poTMS->mIdentifier = "GoogleCRS84Quad";
226 597 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
227 597 : poTMS->mBbox.mCrs = poTMS->mCrs;
228 597 : poTMS->mBbox.mLowerCornerX = -180;
229 597 : poTMS->mBbox.mLowerCornerY = -90;
230 597 : poTMS->mBbox.mUpperCornerX = 180;
231 597 : poTMS->mBbox.mUpperCornerY = 90;
232 597 : poTMS->mWellKnownScaleSet =
233 597 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
234 19104 : for (int i = 0; i <= 30; i++)
235 : {
236 37014 : TileMatrix tm;
237 18507 : tm.mId = CPLSPrintf("%d", i);
238 18507 : tm.mResX = 360. / 256 / (1 << i);
239 18507 : tm.mResY = tm.mResX;
240 18507 : tm.mScaleDenominator =
241 18507 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
242 18507 : tm.mTopLeftX = -180;
243 18507 : tm.mTopLeftY = 180;
244 18507 : tm.mTileWidth = 256;
245 18507 : tm.mTileHeight = 256;
246 18507 : tm.mMatrixWidth = 1 << i;
247 18507 : tm.mMatrixHeight = 1 << i;
248 18507 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
249 : }
250 597 : return poTMS;
251 : }
252 :
253 2387 : bool loadOk = false;
254 2387 : if ( // TMS 2.0 spec
255 2387 : (strstr(fileOrDef, "\"crs\"") &&
256 45 : strstr(fileOrDef, "\"tileMatrices\"")) ||
257 : // TMS 1.0 spec
258 2365 : (strstr(fileOrDef, "\"type\"") &&
259 31 : strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
260 2334 : (strstr(fileOrDef, "\"identifier\"") &&
261 1 : strstr(fileOrDef, "\"boundingBox\"") &&
262 1 : strstr(fileOrDef, "\"tileMatrix\"")))
263 : {
264 54 : loadOk = oDoc.LoadMemory(fileOrDef);
265 : }
266 2333 : else if (STARTS_WITH_CI(fileOrDef, "http://") ||
267 2332 : STARTS_WITH_CI(fileOrDef, "https://"))
268 : {
269 1 : const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
270 1 : loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
271 : }
272 : else
273 : {
274 : VSIStatBufL sStat;
275 2332 : if (VSIStatL(fileOrDef, &sStat) == 0)
276 : {
277 1 : loadOk = oDoc.Load(fileOrDef);
278 : }
279 : else
280 : {
281 2331 : const char *pszFilename = CPLFindFile(
282 4662 : "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
283 2331 : if (pszFilename)
284 : {
285 2329 : loadOk = oDoc.Load(pszFilename);
286 : }
287 : else
288 : {
289 2 : CPLError(CE_Failure, CPLE_AppDefined,
290 : "Invalid tiling matrix set name");
291 : }
292 : }
293 : }
294 2387 : if (!loadOk)
295 : {
296 4 : return nullptr;
297 : }
298 :
299 4766 : auto oRoot = oDoc.GetRoot();
300 : const bool bIsTMSv2 =
301 2383 : oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
302 :
303 4767 : if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
304 2384 : !oRoot.GetObj("tileMatrix").IsValid())
305 : {
306 0 : CPLError(CE_Failure, CPLE_AppDefined,
307 : "Expected type = TileMatrixSetType");
308 0 : return nullptr;
309 : }
310 :
311 2992 : const auto GetCRS = [](const CPLJSONObject &j)
312 : {
313 2992 : if (j.IsValid())
314 : {
315 2991 : if (j.GetType() == CPLJSONObject::Type::String)
316 2988 : return j.ToString();
317 :
318 3 : else if (j.GetType() == CPLJSONObject::Type::Object)
319 : {
320 6 : std::string osURI = j.GetString("uri");
321 3 : if (!osURI.empty())
322 1 : return osURI;
323 :
324 : // Quite a bit of confusion around wkt.
325 : // See https://github.com/opengeospatial/ogcapi-tiles/issues/170
326 4 : const auto jWKT = j.GetObj("wkt");
327 2 : if (jWKT.GetType() == CPLJSONObject::Type::String)
328 : {
329 2 : std::string osWKT = jWKT.ToString();
330 1 : if (!osWKT.empty())
331 1 : return osWKT;
332 : }
333 1 : else if (jWKT.GetType() == CPLJSONObject::Type::Object)
334 : {
335 2 : std::string osWKT = jWKT.ToString();
336 1 : if (!osWKT.empty())
337 1 : return osWKT;
338 : }
339 : }
340 : }
341 1 : return std::string();
342 : };
343 :
344 2383 : poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
345 2383 : poTMS->mTitle = oRoot.GetString("title");
346 2383 : poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
347 7149 : const auto oBbox = oRoot.GetObj("boundingBox");
348 2383 : if (oBbox.IsValid())
349 : {
350 609 : poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
351 1827 : const auto oLowerCorner = oBbox.GetArray("lowerCorner");
352 609 : if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
353 : {
354 609 : poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
355 609 : poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
356 : }
357 1827 : const auto oUpperCorner = oBbox.GetArray("upperCorner");
358 609 : if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
359 : {
360 609 : poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
361 609 : poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
362 : }
363 : }
364 2383 : poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
365 2383 : poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
366 :
367 4766 : OGRSpatialReference oCrs;
368 2383 : if (oCrs.SetFromUserInput(
369 2383 : poTMS->mCrs.c_str(),
370 2383 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
371 : OGRERR_NONE)
372 : {
373 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
374 1 : poTMS->mCrs.c_str());
375 1 : return nullptr;
376 : }
377 2382 : double dfMetersPerUnit = 1.0;
378 2382 : if (oCrs.IsProjected())
379 : {
380 2336 : dfMetersPerUnit = oCrs.GetLinearUnits();
381 : }
382 46 : else if (oCrs.IsGeographic())
383 : {
384 46 : dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
385 : }
386 :
387 : const auto oTileMatrices =
388 7146 : oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
389 2382 : if (oTileMatrices.IsValid())
390 : {
391 2382 : double dfLastScaleDenominator = std::numeric_limits<double>::max();
392 47779 : for (const auto &oTM : oTileMatrices)
393 : {
394 45403 : TileMatrix tm;
395 45403 : tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
396 45403 : tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
397 45403 : if (tm.mScaleDenominator >= dfLastScaleDenominator ||
398 45403 : tm.mScaleDenominator <= 0)
399 : {
400 1 : CPLError(CE_Failure, CPLE_AppDefined,
401 : "Invalid scale denominator or non-decreasing series "
402 : "of scale denominators");
403 1 : return nullptr;
404 : }
405 45402 : dfLastScaleDenominator = tm.mScaleDenominator;
406 : // See note g of Table 2 of
407 : // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
408 45402 : tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
409 45402 : tm.mResY = tm.mResX;
410 45402 : if (bIsTMSv2)
411 : {
412 1635 : const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
413 545 : if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
414 : {
415 0 : CPLError(CE_Warning, CPLE_AppDefined,
416 : "cornerOfOrigin = %s not supported",
417 : osCornerOfOrigin.c_str());
418 : }
419 : }
420 : const auto oTopLeftCorner =
421 90804 : oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
422 45402 : if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
423 : {
424 45402 : tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
425 45402 : tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
426 : }
427 45402 : tm.mTileWidth = oTM.GetInteger("tileWidth");
428 45402 : if (tm.mTileWidth <= 0)
429 : {
430 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
431 : tm.mTileWidth);
432 1 : return nullptr;
433 : }
434 45401 : tm.mTileHeight = oTM.GetInteger("tileHeight");
435 45401 : if (tm.mTileHeight <= 0)
436 : {
437 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
438 : tm.mTileHeight);
439 1 : return nullptr;
440 : }
441 45400 : if (tm.mTileWidth > INT_MAX / tm.mTileHeight)
442 : {
443 1 : CPLError(CE_Failure, CPLE_AppDefined,
444 : "tileWidth(%d) x tileHeight(%d) larger than "
445 : "INT_MAX",
446 : tm.mTileWidth, tm.mTileHeight);
447 1 : return nullptr;
448 : }
449 45399 : tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
450 45399 : if (tm.mMatrixWidth <= 0)
451 : {
452 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
453 : tm.mMatrixWidth);
454 1 : return nullptr;
455 : }
456 45398 : tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
457 45398 : if (tm.mMatrixHeight <= 0)
458 : {
459 1 : CPLError(CE_Failure, CPLE_AppDefined,
460 : "Invalid matrixHeight: %d", tm.mMatrixHeight);
461 1 : return nullptr;
462 : }
463 :
464 : const auto oVariableMatrixWidths = oTM.GetArray(
465 136191 : bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
466 45397 : if (oVariableMatrixWidths.IsValid())
467 : {
468 8 : for (const auto &oVMW : oVariableMatrixWidths)
469 : {
470 5 : TileMatrix::VariableMatrixWidth vmw;
471 5 : vmw.mCoalesce = oVMW.GetInteger("coalesce");
472 5 : vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
473 5 : vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
474 5 : tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
475 : }
476 : }
477 :
478 45397 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
479 : }
480 : }
481 2376 : if (poTMS->mTileMatrixList.empty())
482 : {
483 0 : CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
484 0 : return nullptr;
485 : }
486 :
487 2376 : return poTMS;
488 : }
489 :
490 : /************************************************************************/
491 : /* haveAllLevelsSameTopLeft() */
492 : /************************************************************************/
493 :
494 4249 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
495 : {
496 113080 : for (const auto &oTM : mTileMatrixList)
497 : {
498 217663 : if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
499 108831 : oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
500 : {
501 1 : return false;
502 : }
503 : }
504 4248 : return true;
505 : }
506 :
507 : /************************************************************************/
508 : /* haveAllLevelsSameTileSize() */
509 : /************************************************************************/
510 :
511 4249 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
512 : {
513 113080 : for (const auto &oTM : mTileMatrixList)
514 : {
515 217663 : if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
516 108831 : oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
517 : {
518 1 : return false;
519 : }
520 : }
521 4248 : return true;
522 : }
523 :
524 : /************************************************************************/
525 : /* hasOnlyPowerOfTwoVaryingScales() */
526 : /************************************************************************/
527 :
528 2308 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
529 : {
530 50592 : for (size_t i = 1; i < mTileMatrixList.size(); i++)
531 : {
532 97570 : if (mTileMatrixList[i].mScaleDenominator == 0 ||
533 48785 : std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
534 48785 : mTileMatrixList[i].mScaleDenominator -
535 : 2) > 1e-10)
536 : {
537 501 : return false;
538 : }
539 : }
540 1807 : return true;
541 : }
542 :
543 : /************************************************************************/
544 : /* hasVariableMatrixWidth() */
545 : /************************************************************************/
546 :
547 4902 : bool TileMatrixSet::hasVariableMatrixWidth() const
548 : {
549 132790 : for (const auto &oTM : mTileMatrixList)
550 : {
551 127890 : if (!oTM.mVariableMatrixWidthList.empty())
552 : {
553 2 : return true;
554 : }
555 : }
556 4900 : return false;
557 : }
558 :
559 : /************************************************************************/
560 : /* createRaster() */
561 : /************************************************************************/
562 :
563 : /* static */
564 : std::unique_ptr<TileMatrixSet>
565 4 : TileMatrixSet::createRaster(int width, int height, int tileSize,
566 : int zoomLevelCount, double dfTopLeftX,
567 : double dfTopLeftY, double dfResXFull,
568 : double dfResYFull, const std::string &crs)
569 : {
570 4 : CPLAssert(width > 0);
571 4 : CPLAssert(height > 0);
572 4 : CPLAssert(tileSize > 0);
573 4 : CPLAssert(zoomLevelCount > 0);
574 4 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
575 4 : poTMS->mTitle = "raster";
576 4 : poTMS->mIdentifier = "raster";
577 4 : poTMS->mCrs = crs;
578 4 : poTMS->mBbox.mCrs = poTMS->mCrs;
579 4 : poTMS->mBbox.mLowerCornerX = dfTopLeftX;
580 4 : poTMS->mBbox.mLowerCornerY = dfTopLeftY - height * dfResYFull;
581 4 : poTMS->mBbox.mUpperCornerX = dfTopLeftX + width * dfResYFull;
582 4 : poTMS->mBbox.mUpperCornerY = dfTopLeftY;
583 10 : for (int i = 0; i < zoomLevelCount; i++)
584 : {
585 12 : TileMatrix tm;
586 6 : tm.mId = CPLSPrintf("%d", i);
587 6 : const int iRev = zoomLevelCount - 1 - i;
588 6 : tm.mResX = dfResXFull * (1 << iRev);
589 6 : tm.mResY = dfResYFull * (1 << iRev);
590 6 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
591 6 : tm.mTopLeftX = poTMS->mBbox.mLowerCornerX;
592 6 : tm.mTopLeftY = poTMS->mBbox.mUpperCornerY;
593 6 : tm.mTileWidth = tileSize;
594 6 : tm.mTileHeight = tileSize;
595 6 : tm.mMatrixWidth = std::max(1, DIV_ROUND_UP(width >> iRev, tileSize));
596 6 : tm.mMatrixHeight = std::max(1, DIV_ROUND_UP(height >> iRev, tileSize));
597 6 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
598 : }
599 4 : return poTMS;
600 : }
601 :
602 : } // namespace gdal
603 :
604 : //! @endcond
|