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 791 : TileMatrixSet::listPredefinedTileMatrixSets(bool includeHidden)
35 : {
36 : std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
37 : "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
38 6328 : "PseudoTMS_GlobalMercator"};
39 791 : if (includeHidden)
40 324 : l.push_back("GlobalGeodeticOriginLat270");
41 791 : const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
42 791 : if (pszSomeFile)
43 : {
44 1582 : std::set<std::string> set;
45 : CPLStringList aosList(
46 1582 : VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
47 129724 : for (int i = 0; i < aosList.size(); i++)
48 : {
49 128933 : const size_t nLen = strlen(aosList[i]);
50 254702 : if (nLen > strlen("tms_") + strlen(".json") &&
51 132097 : STARTS_WITH(aosList[i], "tms_") &&
52 3164 : EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
53 : {
54 6328 : std::string id(aosList[i] + strlen("tms_"),
55 6328 : nLen - (strlen("tms_") + strlen(".json")));
56 3164 : set.insert(std::move(id));
57 : }
58 : }
59 3955 : for (const std::string &id : set)
60 3164 : l.push_back(id);
61 : }
62 791 : return l;
63 : }
64 :
65 : /************************************************************************/
66 : /* parse() */
67 : /************************************************************************/
68 :
69 7785 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
70 : {
71 15570 : CPLJSONDocument oDoc;
72 15570 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
73 :
74 7785 : constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
75 :
76 7785 : if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
77 6745 : EQUAL(fileOrDef, "WebMercatorQuad") ||
78 6745 : 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 1040 : poTMS->mTitle = "GoogleMapsCompatible";
86 1040 : poTMS->mIdentifier = "GoogleMapsCompatible";
87 1040 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
88 1040 : poTMS->mBbox.mCrs = poTMS->mCrs;
89 1040 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
90 1040 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
91 1040 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
92 1040 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
93 1040 : poTMS->mWellKnownScaleSet =
94 1040 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
95 33280 : for (int i = 0; i <= 30; i++)
96 : {
97 64480 : TileMatrix tm;
98 32240 : tm.mId = CPLSPrintf("%d", i);
99 32240 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
100 32240 : tm.mResY = tm.mResX;
101 32240 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
102 32240 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
103 32240 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
104 32240 : tm.mTileWidth = 256;
105 32240 : tm.mTileHeight = 256;
106 32240 : tm.mMatrixWidth = 1 << i;
107 32240 : tm.mMatrixHeight = 1 << i;
108 32240 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
109 : }
110 1040 : return poTMS;
111 : }
112 :
113 6745 : if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
114 5955 : 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 790 : poTMS->mTitle = "WorldMercatorWGS84Quad";
119 790 : poTMS->mIdentifier = "WorldMercatorWGS84Quad";
120 790 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
121 790 : poTMS->mBbox.mCrs = poTMS->mCrs;
122 790 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
123 790 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
124 790 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
125 790 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
126 790 : poTMS->mWellKnownScaleSet =
127 790 : "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
128 25280 : for (int i = 0; i <= 30; i++)
129 : {
130 48980 : TileMatrix tm;
131 24490 : tm.mId = CPLSPrintf("%d", i);
132 24490 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
133 24490 : tm.mResY = tm.mResX;
134 24490 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
135 24490 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
136 24490 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
137 24490 : tm.mTileWidth = 256;
138 24490 : tm.mTileHeight = 256;
139 24490 : tm.mMatrixWidth = 1 << i;
140 24490 : tm.mMatrixHeight = 1 << i;
141 24490 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
142 : }
143 790 : return poTMS;
144 : }
145 :
146 5955 : if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
147 : {
148 : /* See global-mercator at
149 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
150 790 : poTMS->mTitle = "PseudoTMS_GlobalMercator";
151 790 : poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
152 790 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
153 790 : poTMS->mBbox.mCrs = poTMS->mCrs;
154 790 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
155 790 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
156 790 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
157 790 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
158 24490 : for (int i = 0; i <= 29; i++)
159 : {
160 47400 : TileMatrix tm;
161 23700 : tm.mId = CPLSPrintf("%d", i);
162 23700 : tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
163 23700 : tm.mResY = tm.mResX;
164 23700 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
165 23700 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
166 23700 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
167 23700 : tm.mTileWidth = 256;
168 23700 : tm.mTileHeight = 256;
169 23700 : tm.mMatrixWidth = 2 * (1 << i);
170 23700 : tm.mMatrixHeight = 2 * (1 << i);
171 23700 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
172 : }
173 790 : return poTMS;
174 : }
175 :
176 5165 : if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
177 5159 : EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
178 5159 : EQUAL(fileOrDef, "WorldCRS84Quad") ||
179 4366 : 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 799 : poTMS->mTitle = "WorldCRS84Quad";
191 799 : poTMS->mIdentifier = "WorldCRS84Quad";
192 799 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
193 799 : poTMS->mBbox.mCrs = poTMS->mCrs;
194 799 : poTMS->mBbox.mLowerCornerX = -180;
195 799 : poTMS->mBbox.mLowerCornerY = -90;
196 799 : poTMS->mBbox.mUpperCornerX = 180;
197 799 : poTMS->mBbox.mUpperCornerY = 90;
198 799 : poTMS->mWellKnownScaleSet =
199 799 : "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 24769 : for (int i = 0; i <= 29; i++)
203 : {
204 47940 : TileMatrix tm;
205 23970 : tm.mId = CPLSPrintf("%d", i);
206 23970 : tm.mResX = 180. / 256 / (1 << i);
207 23970 : tm.mResY = tm.mResX;
208 23970 : tm.mScaleDenominator =
209 23970 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
210 23970 : tm.mTopLeftX = -180;
211 23970 : tm.mTopLeftY = 90;
212 23970 : tm.mTileWidth = 256;
213 23970 : tm.mTileHeight = 256;
214 23970 : tm.mMatrixWidth = 2 * (1 << i);
215 23970 : tm.mMatrixHeight = 1 << i;
216 23970 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
217 : }
218 799 : return poTMS;
219 : }
220 :
221 4366 : if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
222 3558 : 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 808 : poTMS->mTitle = "GoogleCRS84Quad";
228 808 : poTMS->mIdentifier = "GoogleCRS84Quad";
229 808 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
230 808 : poTMS->mBbox.mCrs = poTMS->mCrs;
231 808 : poTMS->mBbox.mLowerCornerX = -180;
232 808 : poTMS->mBbox.mLowerCornerY = -90;
233 808 : poTMS->mBbox.mUpperCornerX = 180;
234 808 : poTMS->mBbox.mUpperCornerY = 90;
235 808 : poTMS->mWellKnownScaleSet =
236 808 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
237 25856 : for (int i = 0; i <= 30; i++)
238 : {
239 50096 : TileMatrix tm;
240 25048 : tm.mId = CPLSPrintf("%d", i);
241 25048 : tm.mResX = 360. / 256 / (1 << i);
242 25048 : tm.mResY = tm.mResX;
243 25048 : tm.mScaleDenominator =
244 25048 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
245 25048 : tm.mTopLeftX = -180;
246 25048 : tm.mTopLeftY = 180;
247 25048 : tm.mTileWidth = 256;
248 25048 : tm.mTileHeight = 256;
249 25048 : tm.mMatrixWidth = 1 << i;
250 25048 : tm.mMatrixHeight = 1 << i;
251 25048 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
252 : }
253 808 : return poTMS;
254 : }
255 :
256 3558 : if (EQUAL(fileOrDef, "GlobalGeodeticOriginLat270"))
257 : {
258 : // gdal2tiles --profile=geodetic *without* --tmscompatible
259 324 : poTMS->mTitle = "GlobalGeodeticOriginLat270";
260 324 : poTMS->mIdentifier = "GlobalGeodeticOriginLat270";
261 324 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
262 324 : poTMS->mBbox.mCrs = poTMS->mCrs;
263 324 : poTMS->mBbox.mLowerCornerX = -180;
264 324 : poTMS->mBbox.mLowerCornerY = -90;
265 324 : poTMS->mBbox.mUpperCornerX = 180;
266 324 : poTMS->mBbox.mUpperCornerY = 270;
267 10368 : for (int i = 0; i <= 30; i++)
268 : {
269 20088 : TileMatrix tm;
270 10044 : tm.mId = CPLSPrintf("%d", i);
271 10044 : tm.mResX = 360. / 256 / (1 << i);
272 10044 : tm.mResY = tm.mResX;
273 10044 : tm.mScaleDenominator =
274 10044 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
275 10044 : tm.mTopLeftX = -180;
276 10044 : tm.mTopLeftY = 270;
277 10044 : tm.mTileWidth = 256;
278 10044 : tm.mTileHeight = 256;
279 10044 : tm.mMatrixWidth = 1 << i;
280 10044 : tm.mMatrixHeight = 1 << i;
281 10044 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
282 : }
283 324 : return poTMS;
284 : }
285 :
286 3234 : bool loadOk = false;
287 3234 : if ( // TMS 2.0 spec
288 3234 : (strstr(fileOrDef, "\"crs\"") &&
289 47 : strstr(fileOrDef, "\"tileMatrices\"")) ||
290 : // TMS 1.0 spec
291 3212 : (strstr(fileOrDef, "\"type\"") &&
292 33 : strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
293 3179 : (strstr(fileOrDef, "\"identifier\"") &&
294 1 : strstr(fileOrDef, "\"boundingBox\"") &&
295 1 : strstr(fileOrDef, "\"tileMatrix\"")))
296 : {
297 56 : loadOk = oDoc.LoadMemory(fileOrDef);
298 : }
299 3178 : else if (STARTS_WITH_CI(fileOrDef, "http://") ||
300 3177 : 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 3177 : if (VSIStatL(fileOrDef, &sStat) == 0)
309 : {
310 1 : loadOk = oDoc.Load(fileOrDef);
311 : }
312 : else
313 : {
314 3176 : const char *pszFilename = CPLFindFile(
315 6352 : "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
316 3176 : if (pszFilename)
317 : {
318 3174 : 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 3234 : if (!loadOk)
328 : {
329 4 : return nullptr;
330 : }
331 :
332 6460 : auto oRoot = oDoc.GetRoot();
333 : const bool bIsTMSv2 =
334 3230 : oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
335 :
336 6461 : if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
337 3231 : !oRoot.GetObj("tileMatrix").IsValid())
338 : {
339 0 : CPLError(CE_Failure, CPLE_AppDefined,
340 : "Expected type = TileMatrixSetType");
341 0 : return nullptr;
342 : }
343 :
344 4052 : const auto GetCRS = [](const CPLJSONObject &j)
345 : {
346 4052 : if (j.IsValid())
347 : {
348 4051 : if (j.GetType() == CPLJSONObject::Type::String)
349 4048 : 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 3230 : poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
378 3230 : poTMS->mTitle = oRoot.GetString("title");
379 3230 : poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
380 9690 : const auto oBbox = oRoot.GetObj("boundingBox");
381 3230 : if (oBbox.IsValid())
382 : {
383 822 : poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
384 2466 : const auto oLowerCorner = oBbox.GetArray("lowerCorner");
385 822 : if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
386 : {
387 822 : poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
388 822 : poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
389 : }
390 2466 : const auto oUpperCorner = oBbox.GetArray("upperCorner");
391 822 : if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
392 : {
393 822 : poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
394 822 : poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
395 : }
396 : }
397 3230 : poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
398 3230 : poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
399 :
400 6460 : OGRSpatialReference oCrs;
401 3230 : if (oCrs.SetFromUserInput(
402 3230 : poTMS->mCrs.c_str(),
403 3230 : 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 3229 : double dfMetersPerUnit = 1.0;
411 3229 : if (oCrs.IsProjected())
412 : {
413 3183 : 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 9687 : oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
422 3229 : if (oTileMatrices.IsValid())
423 : {
424 3229 : double dfLastScaleDenominator = std::numeric_limits<double>::max();
425 64895 : for (const auto &oTM : oTileMatrices)
426 : {
427 61672 : TileMatrix tm;
428 61672 : tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
429 61672 : tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
430 61672 : if (tm.mScaleDenominator >= dfLastScaleDenominator ||
431 61672 : 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 61671 : dfLastScaleDenominator = tm.mScaleDenominator;
439 : // See note g of Table 2 of
440 : // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
441 61671 : tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
442 61671 : tm.mResY = tm.mResX;
443 61671 : 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 123342 : oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
455 61671 : if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
456 : {
457 61671 : tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
458 61671 : tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
459 : }
460 61671 : tm.mTileWidth = oTM.GetInteger("tileWidth");
461 61671 : if (tm.mTileWidth <= 0)
462 : {
463 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
464 : tm.mTileWidth);
465 1 : return nullptr;
466 : }
467 61670 : tm.mTileHeight = oTM.GetInteger("tileHeight");
468 61670 : if (tm.mTileHeight <= 0)
469 : {
470 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
471 : tm.mTileHeight);
472 1 : return nullptr;
473 : }
474 61669 : 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 61668 : tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
483 61668 : if (tm.mMatrixWidth <= 0)
484 : {
485 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
486 : tm.mMatrixWidth);
487 1 : return nullptr;
488 : }
489 61667 : tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
490 61667 : 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 184998 : bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
499 61666 : 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 61666 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
512 : }
513 : }
514 3223 : if (poTMS->mTileMatrixList.empty())
515 : {
516 0 : CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
517 0 : return nullptr;
518 : }
519 :
520 3223 : 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 7226 : bool TileMatrixSet::hasVariableMatrixWidth() const
581 : {
582 196844 : for (const auto &oTM : mTileMatrixList)
583 : {
584 189620 : if (!oTM.mVariableMatrixWidthList.empty())
585 : {
586 2 : return true;
587 : }
588 : }
589 7224 : 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 60 : std::string TileMatrixSet::exportToTMSJsonV1() const
640 : {
641 120 : CPLJSONObject oRoot;
642 60 : oRoot["type"] = "TileMatrixSetType";
643 60 : oRoot["title"] = mTitle;
644 60 : oRoot["identifier"] = mIdentifier;
645 60 : if (!mAbstract.empty())
646 0 : oRoot["abstract"] = mAbstract;
647 60 : if (!std::isnan(mBbox.mLowerCornerX))
648 : {
649 60 : CPLJSONObject oBbox;
650 60 : oBbox["type"] = "BoundingBoxType";
651 60 : oBbox["crs"] = mBbox.mCrs;
652 60 : oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
653 60 : oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
654 60 : oRoot["boundingBox"] = std::move(oBbox);
655 : }
656 60 : oRoot["supportedCRS"] = mCrs;
657 60 : if (!mWellKnownScaleSet.empty())
658 55 : oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
659 :
660 60 : CPLJSONArray oTileMatrices;
661 167 : for (const auto &tm : mTileMatrixList)
662 : {
663 214 : CPLJSONObject oTM;
664 107 : oTM["type"] = "TileMatrixType";
665 107 : oTM["identifier"] = tm.mId;
666 107 : oTM["scaleDenominator"] = tm.mScaleDenominator;
667 107 : oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
668 107 : oTM["tileWidth"] = tm.mTileWidth;
669 107 : oTM["tileHeight"] = tm.mTileHeight;
670 107 : oTM["matrixWidth"] = tm.mMatrixWidth;
671 107 : oTM["matrixHeight"] = tm.mMatrixHeight;
672 :
673 107 : 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 107 : oTileMatrices.Add(oTM);
688 : }
689 60 : oRoot["tileMatrix"] = oTileMatrices;
690 120 : return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
691 120 : .replaceAll("\\/", '/');
692 : }
693 :
694 : } // namespace gdal
695 :
696 : //! @endcond
|