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 : std::vector<std::string>
34 793 : TileMatrixSet::listPredefinedTileMatrixSets(bool includeHidden)
35 : {
36 : std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
37 : "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
38 6344 : "PseudoTMS_GlobalMercator"};
39 793 : if (includeHidden)
40 326 : l.push_back("GlobalGeodeticOriginLat270");
41 793 : const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
42 793 : if (pszSomeFile)
43 : {
44 1586 : std::set<std::string> set;
45 : CPLStringList aosList(
46 1586 : VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
47 130052 : for (int i = 0; i < aosList.size(); i++)
48 : {
49 129259 : const size_t nLen = strlen(aosList[i]);
50 255346 : if (nLen > strlen("tms_") + strlen(".json") &&
51 132431 : STARTS_WITH(aosList[i], "tms_") &&
52 3172 : EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
53 : {
54 6344 : std::string id(aosList[i] + strlen("tms_"),
55 6344 : nLen - (strlen("tms_") + strlen(".json")));
56 3172 : set.insert(std::move(id));
57 : }
58 : }
59 3965 : for (const std::string &id : set)
60 3172 : l.push_back(id);
61 : }
62 793 : return l;
63 : }
64 :
65 : /************************************************************************/
66 : /* parse() */
67 : /************************************************************************/
68 :
69 7806 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
70 : {
71 15612 : CPLJSONDocument oDoc;
72 15612 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
73 :
74 7806 : constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
75 :
76 7806 : if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
77 6763 : EQUAL(fileOrDef, "WebMercatorQuad") ||
78 6763 : EQUAL(
79 : fileOrDef,
80 : "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
81 : {
82 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326
83 : * (WMTS 1.0), Annex E.4 */
84 : // or https://docs.ogc.org/is/17-083r4/17-083r4.html#toc49
85 1043 : poTMS->mTitle = "GoogleMapsCompatible";
86 1043 : poTMS->mIdentifier = "GoogleMapsCompatible";
87 1043 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
88 1043 : poTMS->mBbox.mCrs = poTMS->mCrs;
89 1043 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
90 1043 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
91 1043 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
92 1043 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
93 1043 : poTMS->mWellKnownScaleSet =
94 1043 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
95 33376 : for (int i = 0; i <= 30; i++)
96 : {
97 64666 : TileMatrix tm;
98 32333 : tm.mId = CPLSPrintf("%d", i);
99 32333 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
100 32333 : tm.mResY = tm.mResX;
101 32333 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
102 32333 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
103 32333 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
104 32333 : tm.mTileWidth = 256;
105 32333 : tm.mTileHeight = 256;
106 32333 : tm.mMatrixWidth = 1 << i;
107 32333 : tm.mMatrixHeight = 1 << i;
108 32333 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
109 : }
110 1043 : return poTMS;
111 : }
112 :
113 6763 : if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
114 5971 : EQUAL(fileOrDef, "http://www.opengis.net/def/tilematrixset/OGC/1.0/"
115 : "WorldMercatorWGS84Quad"))
116 : {
117 : // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51
118 792 : poTMS->mTitle = "WorldMercatorWGS84Quad";
119 792 : poTMS->mIdentifier = "WorldMercatorWGS84Quad";
120 792 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
121 792 : poTMS->mBbox.mCrs = poTMS->mCrs;
122 792 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
123 792 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
124 792 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
125 792 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
126 792 : poTMS->mWellKnownScaleSet =
127 792 : "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
128 25344 : for (int i = 0; i <= 30; i++)
129 : {
130 49104 : TileMatrix tm;
131 24552 : tm.mId = CPLSPrintf("%d", i);
132 24552 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
133 24552 : tm.mResY = tm.mResX;
134 24552 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
135 24552 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
136 24552 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
137 24552 : tm.mTileWidth = 256;
138 24552 : tm.mTileHeight = 256;
139 24552 : tm.mMatrixWidth = 1 << i;
140 24552 : tm.mMatrixHeight = 1 << i;
141 24552 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
142 : }
143 792 : return poTMS;
144 : }
145 :
146 5971 : if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
147 : {
148 : /* See global-mercator at
149 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
150 792 : poTMS->mTitle = "PseudoTMS_GlobalMercator";
151 792 : poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
152 792 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
153 792 : poTMS->mBbox.mCrs = poTMS->mCrs;
154 792 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
155 792 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
156 792 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
157 792 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
158 24552 : for (int i = 0; i <= 29; i++)
159 : {
160 47520 : TileMatrix tm;
161 23760 : tm.mId = CPLSPrintf("%d", i);
162 23760 : tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
163 23760 : tm.mResY = tm.mResX;
164 23760 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
165 23760 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
166 23760 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
167 23760 : tm.mTileWidth = 256;
168 23760 : tm.mTileHeight = 256;
169 23760 : tm.mMatrixWidth = 2 * (1 << i);
170 23760 : tm.mMatrixHeight = 2 * (1 << i);
171 23760 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
172 : }
173 792 : return poTMS;
174 : }
175 :
176 5179 : if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
177 5173 : EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
178 5173 : EQUAL(fileOrDef, "WorldCRS84Quad") ||
179 4378 : EQUAL(
180 : fileOrDef,
181 : "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
182 : {
183 : /* See InspireCRS84Quad at
184 : * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
185 : */
186 : /* This is exactly the same as PseudoTMS_GlobalGeodetic */
187 : /* See global-geodetic at
188 : * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
189 : // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
190 801 : poTMS->mTitle = "WorldCRS84Quad";
191 801 : poTMS->mIdentifier = "WorldCRS84Quad";
192 801 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
193 801 : poTMS->mBbox.mCrs = poTMS->mCrs;
194 801 : poTMS->mBbox.mLowerCornerX = -180;
195 801 : poTMS->mBbox.mLowerCornerY = -90;
196 801 : poTMS->mBbox.mUpperCornerX = 180;
197 801 : poTMS->mBbox.mUpperCornerY = 90;
198 801 : poTMS->mWellKnownScaleSet =
199 801 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
200 : // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
201 : // and at 30 it would overflow int32.
202 24831 : for (int i = 0; i <= 29; i++)
203 : {
204 48060 : TileMatrix tm;
205 24030 : tm.mId = CPLSPrintf("%d", i);
206 24030 : tm.mResX = 180. / 256 / (1 << i);
207 24030 : tm.mResY = tm.mResX;
208 24030 : tm.mScaleDenominator =
209 24030 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
210 24030 : tm.mTopLeftX = -180;
211 24030 : tm.mTopLeftY = 90;
212 24030 : tm.mTileWidth = 256;
213 24030 : tm.mTileHeight = 256;
214 24030 : tm.mMatrixWidth = 2 * (1 << i);
215 24030 : tm.mMatrixHeight = 1 << i;
216 24030 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
217 : }
218 801 : return poTMS;
219 : }
220 :
221 4378 : if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
222 3568 : EQUAL(fileOrDef,
223 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad"))
224 : {
225 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
226 : Annex E.3 */
227 810 : poTMS->mTitle = "GoogleCRS84Quad";
228 810 : poTMS->mIdentifier = "GoogleCRS84Quad";
229 810 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
230 810 : poTMS->mBbox.mCrs = poTMS->mCrs;
231 810 : poTMS->mBbox.mLowerCornerX = -180;
232 810 : poTMS->mBbox.mLowerCornerY = -90;
233 810 : poTMS->mBbox.mUpperCornerX = 180;
234 810 : poTMS->mBbox.mUpperCornerY = 90;
235 810 : poTMS->mWellKnownScaleSet =
236 810 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
237 25920 : for (int i = 0; i <= 30; i++)
238 : {
239 50220 : TileMatrix tm;
240 25110 : tm.mId = CPLSPrintf("%d", i);
241 25110 : tm.mResX = 360. / 256 / (1 << i);
242 25110 : tm.mResY = tm.mResX;
243 25110 : tm.mScaleDenominator =
244 25110 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
245 25110 : tm.mTopLeftX = -180;
246 25110 : tm.mTopLeftY = 180;
247 25110 : tm.mTileWidth = 256;
248 25110 : tm.mTileHeight = 256;
249 25110 : tm.mMatrixWidth = 1 << i;
250 25110 : tm.mMatrixHeight = 1 << i;
251 25110 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
252 : }
253 810 : return poTMS;
254 : }
255 :
256 3568 : if (EQUAL(fileOrDef, "GlobalGeodeticOriginLat270"))
257 : {
258 : // gdal2tiles --profile=geodetic *without* --tmscompatible
259 326 : poTMS->mTitle = "GlobalGeodeticOriginLat270";
260 326 : poTMS->mIdentifier = "GlobalGeodeticOriginLat270";
261 326 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
262 326 : poTMS->mBbox.mCrs = poTMS->mCrs;
263 326 : poTMS->mBbox.mLowerCornerX = -180;
264 326 : poTMS->mBbox.mLowerCornerY = -90;
265 326 : poTMS->mBbox.mUpperCornerX = 180;
266 326 : poTMS->mBbox.mUpperCornerY = 270;
267 10432 : for (int i = 0; i <= 30; i++)
268 : {
269 20212 : TileMatrix tm;
270 10106 : tm.mId = CPLSPrintf("%d", i);
271 10106 : tm.mResX = 360. / 256 / (1 << i);
272 10106 : tm.mResY = tm.mResX;
273 10106 : tm.mScaleDenominator =
274 10106 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
275 10106 : tm.mTopLeftX = -180;
276 10106 : tm.mTopLeftY = 270;
277 10106 : tm.mTileWidth = 256;
278 10106 : tm.mTileHeight = 256;
279 10106 : tm.mMatrixWidth = 1 << i;
280 10106 : tm.mMatrixHeight = 1 << i;
281 10106 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
282 : }
283 326 : return poTMS;
284 : }
285 :
286 3242 : bool loadOk = false;
287 3242 : if ( // TMS 2.0 spec
288 3242 : (strstr(fileOrDef, "\"crs\"") &&
289 47 : strstr(fileOrDef, "\"tileMatrices\"")) ||
290 : // TMS 1.0 spec
291 3220 : (strstr(fileOrDef, "\"type\"") &&
292 33 : strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
293 3187 : (strstr(fileOrDef, "\"identifier\"") &&
294 1 : strstr(fileOrDef, "\"boundingBox\"") &&
295 1 : strstr(fileOrDef, "\"tileMatrix\"")))
296 : {
297 56 : loadOk = oDoc.LoadMemory(fileOrDef);
298 : }
299 3186 : else if (STARTS_WITH_CI(fileOrDef, "http://") ||
300 3185 : STARTS_WITH_CI(fileOrDef, "https://"))
301 : {
302 1 : const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
303 1 : loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
304 : }
305 : else
306 : {
307 : VSIStatBufL sStat;
308 3185 : if (VSIStatL(fileOrDef, &sStat) == 0)
309 : {
310 1 : loadOk = oDoc.Load(fileOrDef);
311 : }
312 : else
313 : {
314 3184 : const char *pszFilename = CPLFindFile(
315 6368 : "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
316 3184 : if (pszFilename)
317 : {
318 3182 : loadOk = oDoc.Load(pszFilename);
319 : }
320 : else
321 : {
322 2 : CPLError(CE_Failure, CPLE_AppDefined,
323 : "Invalid tiling matrix set name");
324 : }
325 : }
326 : }
327 3242 : if (!loadOk)
328 : {
329 4 : return nullptr;
330 : }
331 :
332 6476 : auto oRoot = oDoc.GetRoot();
333 : const bool bIsTMSv2 =
334 3238 : oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
335 :
336 6477 : if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
337 3239 : !oRoot.GetObj("tileMatrix").IsValid())
338 : {
339 0 : CPLError(CE_Failure, CPLE_AppDefined,
340 : "Expected type = TileMatrixSetType");
341 0 : return nullptr;
342 : }
343 :
344 4062 : const auto GetCRS = [](const CPLJSONObject &j)
345 : {
346 4062 : if (j.IsValid())
347 : {
348 4061 : if (j.GetType() == CPLJSONObject::Type::String)
349 4058 : return j.ToString();
350 :
351 3 : else if (j.GetType() == CPLJSONObject::Type::Object)
352 : {
353 6 : std::string osURI = j.GetString("uri");
354 3 : if (!osURI.empty())
355 1 : return osURI;
356 :
357 : // Quite a bit of confusion around wkt.
358 : // See https://github.com/opengeospatial/ogcapi-tiles/issues/170
359 4 : const auto jWKT = j.GetObj("wkt");
360 2 : if (jWKT.GetType() == CPLJSONObject::Type::String)
361 : {
362 2 : std::string osWKT = jWKT.ToString();
363 1 : if (!osWKT.empty())
364 1 : return osWKT;
365 : }
366 1 : else if (jWKT.GetType() == CPLJSONObject::Type::Object)
367 : {
368 2 : std::string osWKT = jWKT.ToString();
369 1 : if (!osWKT.empty())
370 1 : return osWKT;
371 : }
372 : }
373 : }
374 1 : return std::string();
375 : };
376 :
377 3238 : poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
378 3238 : poTMS->mTitle = oRoot.GetString("title");
379 3238 : poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
380 9714 : const auto oBbox = oRoot.GetObj("boundingBox");
381 3238 : if (oBbox.IsValid())
382 : {
383 824 : poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
384 2472 : const auto oLowerCorner = oBbox.GetArray("lowerCorner");
385 824 : if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
386 : {
387 824 : poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
388 824 : poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
389 : }
390 2472 : const auto oUpperCorner = oBbox.GetArray("upperCorner");
391 824 : if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
392 : {
393 824 : poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
394 824 : poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
395 : }
396 : }
397 3238 : poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
398 3238 : poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
399 :
400 6476 : OGRSpatialReference oCrs;
401 3238 : if (oCrs.SetFromUserInput(
402 3238 : poTMS->mCrs.c_str(),
403 3238 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
404 : OGRERR_NONE)
405 : {
406 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
407 1 : poTMS->mCrs.c_str());
408 1 : return nullptr;
409 : }
410 3237 : double dfMetersPerUnit = 1.0;
411 3237 : if (oCrs.IsProjected())
412 : {
413 3191 : dfMetersPerUnit = oCrs.GetLinearUnits();
414 : }
415 46 : else if (oCrs.IsGeographic())
416 : {
417 46 : dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
418 : }
419 :
420 : const auto oTileMatrices =
421 9711 : oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
422 3237 : if (oTileMatrices.IsValid())
423 : {
424 3237 : double dfLastScaleDenominator = std::numeric_limits<double>::max();
425 65057 : for (const auto &oTM : oTileMatrices)
426 : {
427 61826 : TileMatrix tm;
428 61826 : tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
429 61826 : tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
430 61826 : if (tm.mScaleDenominator >= dfLastScaleDenominator ||
431 61826 : tm.mScaleDenominator <= 0)
432 : {
433 1 : CPLError(CE_Failure, CPLE_AppDefined,
434 : "Invalid scale denominator or non-decreasing series "
435 : "of scale denominators");
436 1 : return nullptr;
437 : }
438 61825 : dfLastScaleDenominator = tm.mScaleDenominator;
439 : // See note g of Table 2 of
440 : // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
441 61825 : tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
442 61825 : tm.mResY = tm.mResX;
443 61825 : if (bIsTMSv2)
444 : {
445 1635 : const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
446 545 : if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
447 : {
448 0 : CPLError(CE_Warning, CPLE_AppDefined,
449 : "cornerOfOrigin = %s not supported",
450 : osCornerOfOrigin.c_str());
451 : }
452 : }
453 : const auto oTopLeftCorner =
454 123650 : oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
455 61825 : if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
456 : {
457 61825 : tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
458 61825 : tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
459 : }
460 61825 : tm.mTileWidth = oTM.GetInteger("tileWidth");
461 61825 : if (tm.mTileWidth <= 0)
462 : {
463 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
464 : tm.mTileWidth);
465 1 : return nullptr;
466 : }
467 61824 : tm.mTileHeight = oTM.GetInteger("tileHeight");
468 61824 : if (tm.mTileHeight <= 0)
469 : {
470 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
471 : tm.mTileHeight);
472 1 : return nullptr;
473 : }
474 61823 : if (tm.mTileWidth > INT_MAX / tm.mTileHeight)
475 : {
476 1 : CPLError(CE_Failure, CPLE_AppDefined,
477 : "tileWidth(%d) x tileHeight(%d) larger than "
478 : "INT_MAX",
479 : tm.mTileWidth, tm.mTileHeight);
480 1 : return nullptr;
481 : }
482 61822 : tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
483 61822 : if (tm.mMatrixWidth <= 0)
484 : {
485 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
486 : tm.mMatrixWidth);
487 1 : return nullptr;
488 : }
489 61821 : tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
490 61821 : if (tm.mMatrixHeight <= 0)
491 : {
492 1 : CPLError(CE_Failure, CPLE_AppDefined,
493 : "Invalid matrixHeight: %d", tm.mMatrixHeight);
494 1 : return nullptr;
495 : }
496 :
497 : const auto oVariableMatrixWidths = oTM.GetArray(
498 185460 : bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
499 61820 : if (oVariableMatrixWidths.IsValid())
500 : {
501 8 : for (const auto &oVMW : oVariableMatrixWidths)
502 : {
503 5 : TileMatrix::VariableMatrixWidth vmw;
504 5 : vmw.mCoalesce = oVMW.GetInteger("coalesce");
505 5 : vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
506 5 : vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
507 5 : tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
508 : }
509 : }
510 :
511 61820 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
512 : }
513 : }
514 3231 : if (poTMS->mTileMatrixList.empty())
515 : {
516 0 : CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
517 0 : return nullptr;
518 : }
519 :
520 3231 : return poTMS;
521 : }
522 :
523 : /************************************************************************/
524 : /* haveAllLevelsSameTopLeft() */
525 : /************************************************************************/
526 :
527 4298 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
528 : {
529 114344 : for (const auto &oTM : mTileMatrixList)
530 : {
531 220093 : if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
532 110046 : oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
533 : {
534 1 : return false;
535 : }
536 : }
537 4297 : return true;
538 : }
539 :
540 : /************************************************************************/
541 : /* haveAllLevelsSameTileSize() */
542 : /************************************************************************/
543 :
544 4298 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
545 : {
546 114344 : for (const auto &oTM : mTileMatrixList)
547 : {
548 220093 : if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
549 110046 : oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
550 : {
551 1 : return false;
552 : }
553 : }
554 4297 : return true;
555 : }
556 :
557 : /************************************************************************/
558 : /* hasOnlyPowerOfTwoVaryingScales() */
559 : /************************************************************************/
560 :
561 2357 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
562 : {
563 51637 : for (size_t i = 1; i < mTileMatrixList.size(); i++)
564 : {
565 99582 : if (mTileMatrixList[i].mScaleDenominator == 0 ||
566 49791 : std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
567 49791 : mTileMatrixList[i].mScaleDenominator -
568 : 2) > 1e-10)
569 : {
570 511 : return false;
571 : }
572 : }
573 1846 : return true;
574 : }
575 :
576 : /************************************************************************/
577 : /* hasVariableMatrixWidth() */
578 : /************************************************************************/
579 :
580 7247 : bool TileMatrixSet::hasVariableMatrixWidth() const
581 : {
582 197418 : for (const auto &oTM : mTileMatrixList)
583 : {
584 190173 : if (!oTM.mVariableMatrixWidthList.empty())
585 : {
586 2 : return true;
587 : }
588 : }
589 7245 : return false;
590 : }
591 :
592 : /************************************************************************/
593 : /* createRaster() */
594 : /************************************************************************/
595 :
596 : /* static */
597 : std::unique_ptr<TileMatrixSet>
598 6 : TileMatrixSet::createRaster(int width, int height, int tileSize,
599 : int zoomLevelCount, double dfTopLeftX,
600 : double dfTopLeftY, double dfResXFull,
601 : double dfResYFull, const std::string &crs)
602 : {
603 6 : CPLAssert(width > 0);
604 6 : CPLAssert(height > 0);
605 6 : CPLAssert(tileSize > 0);
606 6 : CPLAssert(zoomLevelCount > 0);
607 6 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
608 6 : poTMS->mTitle = "raster";
609 6 : poTMS->mIdentifier = "raster";
610 6 : poTMS->mCrs = crs;
611 6 : poTMS->mBbox.mCrs = poTMS->mCrs;
612 6 : poTMS->mBbox.mLowerCornerX = dfTopLeftX;
613 6 : poTMS->mBbox.mLowerCornerY = dfTopLeftY - height * dfResYFull;
614 6 : poTMS->mBbox.mUpperCornerX = dfTopLeftX + width * dfResYFull;
615 6 : poTMS->mBbox.mUpperCornerY = dfTopLeftY;
616 16 : for (int i = 0; i < zoomLevelCount; i++)
617 : {
618 20 : TileMatrix tm;
619 10 : tm.mId = CPLSPrintf("%d", i);
620 10 : const int iRev = zoomLevelCount - 1 - i;
621 10 : tm.mResX = dfResXFull * (1 << iRev);
622 10 : tm.mResY = dfResYFull * (1 << iRev);
623 10 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
624 10 : tm.mTopLeftX = poTMS->mBbox.mLowerCornerX;
625 10 : tm.mTopLeftY = poTMS->mBbox.mUpperCornerY;
626 10 : tm.mTileWidth = tileSize;
627 10 : tm.mTileHeight = tileSize;
628 10 : tm.mMatrixWidth = std::max(1, DIV_ROUND_UP(width >> iRev, tileSize));
629 10 : tm.mMatrixHeight = std::max(1, DIV_ROUND_UP(height >> iRev, tileSize));
630 10 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
631 : }
632 6 : return poTMS;
633 : }
634 :
635 : /************************************************************************/
636 : /* exportToTMSJsonV1() */
637 : /************************************************************************/
638 :
639 61 : std::string TileMatrixSet::exportToTMSJsonV1() const
640 : {
641 122 : CPLJSONObject oRoot;
642 61 : oRoot["type"] = "TileMatrixSetType";
643 61 : oRoot["title"] = mTitle;
644 61 : oRoot["identifier"] = mIdentifier;
645 61 : if (!mAbstract.empty())
646 0 : oRoot["abstract"] = mAbstract;
647 61 : if (!std::isnan(mBbox.mLowerCornerX))
648 : {
649 61 : CPLJSONObject oBbox;
650 61 : oBbox["type"] = "BoundingBoxType";
651 61 : oBbox["crs"] = mBbox.mCrs;
652 61 : oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
653 61 : oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
654 61 : oRoot["boundingBox"] = std::move(oBbox);
655 : }
656 61 : oRoot["supportedCRS"] = mCrs;
657 61 : if (!mWellKnownScaleSet.empty())
658 56 : oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
659 :
660 61 : CPLJSONArray oTileMatrices;
661 169 : for (const auto &tm : mTileMatrixList)
662 : {
663 216 : CPLJSONObject oTM;
664 108 : oTM["type"] = "TileMatrixType";
665 108 : oTM["identifier"] = tm.mId;
666 108 : oTM["scaleDenominator"] = tm.mScaleDenominator;
667 108 : oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
668 108 : oTM["tileWidth"] = tm.mTileWidth;
669 108 : oTM["tileHeight"] = tm.mTileHeight;
670 108 : oTM["matrixWidth"] = tm.mMatrixWidth;
671 108 : oTM["matrixHeight"] = tm.mMatrixHeight;
672 :
673 108 : if (!tm.mVariableMatrixWidthList.empty())
674 : {
675 1 : CPLJSONArray oVariableMatrixWidths;
676 2 : for (const auto &vmw : tm.mVariableMatrixWidthList)
677 : {
678 2 : CPLJSONObject oVMW;
679 1 : oVMW["coalesce"] = vmw.mCoalesce;
680 1 : oVMW["minTileRow"] = vmw.mMinTileRow;
681 1 : oVMW["maxTileRow"] = vmw.mMaxTileRow;
682 1 : oVariableMatrixWidths.Add(oVMW);
683 : }
684 1 : oTM["variableMatrixWidth"] = oVariableMatrixWidths;
685 : }
686 :
687 108 : oTileMatrices.Add(oTM);
688 : }
689 61 : oRoot["tileMatrix"] = oTileMatrices;
690 122 : return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
691 122 : .replaceAll("\\/", '/');
692 : }
693 :
694 : } // namespace gdal
695 :
696 : //! @endcond
|