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