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 754 : TileMatrixSet::listPredefinedTileMatrixSets(bool includeHidden)
35 : {
36 : std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
37 : "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
38 6032 : "PseudoTMS_GlobalMercator"};
39 754 : if (includeHidden)
40 292 : l.push_back("GlobalGeodeticOriginLat270");
41 754 : const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
42 754 : if (pszSomeFile)
43 : {
44 1508 : std::set<std::string> set;
45 : CPLStringList aosList(
46 1508 : VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
47 123656 : for (int i = 0; i < aosList.size(); i++)
48 : {
49 122902 : const size_t nLen = strlen(aosList[i]);
50 242788 : if (nLen > strlen("tms_") + strlen(".json") &&
51 125918 : STARTS_WITH(aosList[i], "tms_") &&
52 3016 : EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
53 : {
54 6032 : std::string id(aosList[i] + strlen("tms_"),
55 6032 : nLen - (strlen("tms_") + strlen(".json")));
56 3016 : set.insert(std::move(id));
57 : }
58 : }
59 3770 : for (const std::string &id : set)
60 3016 : l.push_back(id);
61 : }
62 754 : return l;
63 : }
64 :
65 : /************************************************************************/
66 : /* parse() */
67 : /************************************************************************/
68 :
69 7404 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
70 : {
71 14808 : CPLJSONDocument oDoc;
72 14808 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
73 :
74 7404 : constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
75 :
76 7404 : if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
77 6417 : EQUAL(fileOrDef, "WebMercatorQuad") ||
78 6417 : 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 987 : poTMS->mTitle = "GoogleMapsCompatible";
86 987 : poTMS->mIdentifier = "GoogleMapsCompatible";
87 987 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
88 987 : poTMS->mBbox.mCrs = poTMS->mCrs;
89 987 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
90 987 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
91 987 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
92 987 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
93 987 : poTMS->mWellKnownScaleSet =
94 987 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
95 31584 : for (int i = 0; i <= 30; i++)
96 : {
97 61194 : TileMatrix tm;
98 30597 : tm.mId = CPLSPrintf("%d", i);
99 30597 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
100 30597 : tm.mResY = tm.mResX;
101 30597 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
102 30597 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
103 30597 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
104 30597 : tm.mTileWidth = 256;
105 30597 : tm.mTileHeight = 256;
106 30597 : tm.mMatrixWidth = 1 << i;
107 30597 : tm.mMatrixHeight = 1 << i;
108 30597 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
109 : }
110 987 : return poTMS;
111 : }
112 :
113 6417 : if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
114 5664 : 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 753 : poTMS->mTitle = "WorldMercatorWGS84Quad";
119 753 : poTMS->mIdentifier = "WorldMercatorWGS84Quad";
120 753 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
121 753 : poTMS->mBbox.mCrs = poTMS->mCrs;
122 753 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
123 753 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
124 753 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
125 753 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
126 753 : poTMS->mWellKnownScaleSet =
127 753 : "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
128 24096 : for (int i = 0; i <= 30; i++)
129 : {
130 46686 : TileMatrix tm;
131 23343 : tm.mId = CPLSPrintf("%d", i);
132 23343 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
133 23343 : tm.mResY = tm.mResX;
134 23343 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
135 23343 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
136 23343 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
137 23343 : tm.mTileWidth = 256;
138 23343 : tm.mTileHeight = 256;
139 23343 : tm.mMatrixWidth = 1 << i;
140 23343 : tm.mMatrixHeight = 1 << i;
141 23343 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
142 : }
143 753 : return poTMS;
144 : }
145 :
146 5664 : if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
147 : {
148 : /* See global-mercator at
149 : http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
150 753 : poTMS->mTitle = "PseudoTMS_GlobalMercator";
151 753 : poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
152 753 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
153 753 : poTMS->mBbox.mCrs = poTMS->mCrs;
154 753 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
155 753 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
156 753 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
157 753 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
158 23343 : for (int i = 0; i <= 29; i++)
159 : {
160 45180 : TileMatrix tm;
161 22590 : tm.mId = CPLSPrintf("%d", i);
162 22590 : tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
163 22590 : tm.mResY = tm.mResX;
164 22590 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
165 22590 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
166 22590 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
167 22590 : tm.mTileWidth = 256;
168 22590 : tm.mTileHeight = 256;
169 22590 : tm.mMatrixWidth = 2 * (1 << i);
170 22590 : tm.mMatrixHeight = 2 * (1 << i);
171 22590 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
172 : }
173 753 : return poTMS;
174 : }
175 :
176 4911 : if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
177 4905 : EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
178 4905 : EQUAL(fileOrDef, "WorldCRS84Quad") ||
179 4149 : 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 762 : poTMS->mTitle = "WorldCRS84Quad";
191 762 : poTMS->mIdentifier = "WorldCRS84Quad";
192 762 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
193 762 : poTMS->mBbox.mCrs = poTMS->mCrs;
194 762 : poTMS->mBbox.mLowerCornerX = -180;
195 762 : poTMS->mBbox.mLowerCornerY = -90;
196 762 : poTMS->mBbox.mUpperCornerX = 180;
197 762 : poTMS->mBbox.mUpperCornerY = 90;
198 762 : poTMS->mWellKnownScaleSet =
199 762 : "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 23622 : for (int i = 0; i <= 29; i++)
203 : {
204 45720 : TileMatrix tm;
205 22860 : tm.mId = CPLSPrintf("%d", i);
206 22860 : tm.mResX = 180. / 256 / (1 << i);
207 22860 : tm.mResY = tm.mResX;
208 22860 : tm.mScaleDenominator =
209 22860 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
210 22860 : tm.mTopLeftX = -180;
211 22860 : tm.mTopLeftY = 90;
212 22860 : tm.mTileWidth = 256;
213 22860 : tm.mTileHeight = 256;
214 22860 : tm.mMatrixWidth = 2 * (1 << i);
215 22860 : tm.mMatrixHeight = 1 << i;
216 22860 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
217 : }
218 762 : return poTMS;
219 : }
220 :
221 4149 : if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
222 3378 : 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 771 : poTMS->mTitle = "GoogleCRS84Quad";
228 771 : poTMS->mIdentifier = "GoogleCRS84Quad";
229 771 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
230 771 : poTMS->mBbox.mCrs = poTMS->mCrs;
231 771 : poTMS->mBbox.mLowerCornerX = -180;
232 771 : poTMS->mBbox.mLowerCornerY = -90;
233 771 : poTMS->mBbox.mUpperCornerX = 180;
234 771 : poTMS->mBbox.mUpperCornerY = 90;
235 771 : poTMS->mWellKnownScaleSet =
236 771 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
237 24672 : for (int i = 0; i <= 30; i++)
238 : {
239 47802 : TileMatrix tm;
240 23901 : tm.mId = CPLSPrintf("%d", i);
241 23901 : tm.mResX = 360. / 256 / (1 << i);
242 23901 : tm.mResY = tm.mResX;
243 23901 : tm.mScaleDenominator =
244 23901 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
245 23901 : tm.mTopLeftX = -180;
246 23901 : tm.mTopLeftY = 180;
247 23901 : tm.mTileWidth = 256;
248 23901 : tm.mTileHeight = 256;
249 23901 : tm.mMatrixWidth = 1 << i;
250 23901 : tm.mMatrixHeight = 1 << i;
251 23901 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
252 : }
253 771 : return poTMS;
254 : }
255 :
256 3378 : if (EQUAL(fileOrDef, "GlobalGeodeticOriginLat270"))
257 : {
258 : // gdal2tiles --profile=geodetic *without* --tmscompatible
259 292 : poTMS->mTitle = "GlobalGeodeticOriginLat270";
260 292 : poTMS->mIdentifier = "GlobalGeodeticOriginLat270";
261 292 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
262 292 : poTMS->mBbox.mCrs = poTMS->mCrs;
263 292 : poTMS->mBbox.mLowerCornerX = -180;
264 292 : poTMS->mBbox.mLowerCornerY = -90;
265 292 : poTMS->mBbox.mUpperCornerX = 180;
266 292 : poTMS->mBbox.mUpperCornerY = 270;
267 9344 : for (int i = 0; i <= 30; i++)
268 : {
269 18104 : TileMatrix tm;
270 9052 : tm.mId = CPLSPrintf("%d", i);
271 9052 : tm.mResX = 360. / 256 / (1 << i);
272 9052 : tm.mResY = tm.mResX;
273 9052 : tm.mScaleDenominator =
274 9052 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
275 9052 : tm.mTopLeftX = -180;
276 9052 : tm.mTopLeftY = 270;
277 9052 : tm.mTileWidth = 256;
278 9052 : tm.mTileHeight = 256;
279 9052 : tm.mMatrixWidth = 1 << i;
280 9052 : tm.mMatrixHeight = 1 << i;
281 9052 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
282 : }
283 292 : return poTMS;
284 : }
285 :
286 3086 : bool loadOk = false;
287 3086 : if ( // TMS 2.0 spec
288 3086 : (strstr(fileOrDef, "\"crs\"") &&
289 47 : strstr(fileOrDef, "\"tileMatrices\"")) ||
290 : // TMS 1.0 spec
291 3064 : (strstr(fileOrDef, "\"type\"") &&
292 33 : strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
293 3031 : (strstr(fileOrDef, "\"identifier\"") &&
294 1 : strstr(fileOrDef, "\"boundingBox\"") &&
295 1 : strstr(fileOrDef, "\"tileMatrix\"")))
296 : {
297 56 : loadOk = oDoc.LoadMemory(fileOrDef);
298 : }
299 3030 : else if (STARTS_WITH_CI(fileOrDef, "http://") ||
300 3029 : 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 3029 : if (VSIStatL(fileOrDef, &sStat) == 0)
309 : {
310 1 : loadOk = oDoc.Load(fileOrDef);
311 : }
312 : else
313 : {
314 3028 : const char *pszFilename = CPLFindFile(
315 6056 : "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
316 3028 : if (pszFilename)
317 : {
318 3026 : 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 3086 : if (!loadOk)
328 : {
329 4 : return nullptr;
330 : }
331 :
332 6164 : auto oRoot = oDoc.GetRoot();
333 : const bool bIsTMSv2 =
334 3082 : oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
335 :
336 6165 : if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
337 3083 : !oRoot.GetObj("tileMatrix").IsValid())
338 : {
339 0 : CPLError(CE_Failure, CPLE_AppDefined,
340 : "Expected type = TileMatrixSetType");
341 0 : return nullptr;
342 : }
343 :
344 3867 : const auto GetCRS = [](const CPLJSONObject &j)
345 : {
346 3867 : if (j.IsValid())
347 : {
348 3866 : if (j.GetType() == CPLJSONObject::Type::String)
349 3863 : 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 3082 : poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
378 3082 : poTMS->mTitle = oRoot.GetString("title");
379 3082 : poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
380 9246 : const auto oBbox = oRoot.GetObj("boundingBox");
381 3082 : if (oBbox.IsValid())
382 : {
383 785 : poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
384 2355 : const auto oLowerCorner = oBbox.GetArray("lowerCorner");
385 785 : if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
386 : {
387 785 : poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
388 785 : poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
389 : }
390 2355 : const auto oUpperCorner = oBbox.GetArray("upperCorner");
391 785 : if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
392 : {
393 785 : poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
394 785 : poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
395 : }
396 : }
397 3082 : poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
398 3082 : poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
399 :
400 6164 : OGRSpatialReference oCrs;
401 3082 : if (oCrs.SetFromUserInput(
402 3082 : poTMS->mCrs.c_str(),
403 3082 : 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 3081 : double dfMetersPerUnit = 1.0;
411 3081 : if (oCrs.IsProjected())
412 : {
413 3035 : 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 9243 : oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
422 3081 : if (oTileMatrices.IsValid())
423 : {
424 3081 : double dfLastScaleDenominator = std::numeric_limits<double>::max();
425 61898 : for (const auto &oTM : oTileMatrices)
426 : {
427 58823 : TileMatrix tm;
428 58823 : tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
429 58823 : tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
430 58823 : if (tm.mScaleDenominator >= dfLastScaleDenominator ||
431 58823 : 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 58822 : dfLastScaleDenominator = tm.mScaleDenominator;
439 : // See note g of Table 2 of
440 : // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
441 58822 : tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
442 58822 : tm.mResY = tm.mResX;
443 58822 : 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 117644 : oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
455 58822 : if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
456 : {
457 58822 : tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
458 58822 : tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
459 : }
460 58822 : tm.mTileWidth = oTM.GetInteger("tileWidth");
461 58822 : if (tm.mTileWidth <= 0)
462 : {
463 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
464 : tm.mTileWidth);
465 1 : return nullptr;
466 : }
467 58821 : tm.mTileHeight = oTM.GetInteger("tileHeight");
468 58821 : if (tm.mTileHeight <= 0)
469 : {
470 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
471 : tm.mTileHeight);
472 1 : return nullptr;
473 : }
474 58820 : 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 58819 : tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
483 58819 : if (tm.mMatrixWidth <= 0)
484 : {
485 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
486 : tm.mMatrixWidth);
487 1 : return nullptr;
488 : }
489 58818 : tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
490 58818 : 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 176451 : bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
499 58817 : 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 58817 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
512 : }
513 : }
514 3075 : if (poTMS->mTileMatrixList.empty())
515 : {
516 0 : CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
517 0 : return nullptr;
518 : }
519 :
520 3075 : return poTMS;
521 : }
522 :
523 : /************************************************************************/
524 : /* haveAllLevelsSameTopLeft() */
525 : /************************************************************************/
526 :
527 4253 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
528 : {
529 113149 : for (const auto &oTM : mTileMatrixList)
530 : {
531 217793 : if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
532 108896 : oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
533 : {
534 1 : return false;
535 : }
536 : }
537 4252 : return true;
538 : }
539 :
540 : /************************************************************************/
541 : /* haveAllLevelsSameTileSize() */
542 : /************************************************************************/
543 :
544 4253 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
545 : {
546 113149 : for (const auto &oTM : mTileMatrixList)
547 : {
548 217793 : if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
549 108896 : oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
550 : {
551 1 : return false;
552 : }
553 : }
554 4252 : return true;
555 : }
556 :
557 : /************************************************************************/
558 : /* hasOnlyPowerOfTwoVaryingScales() */
559 : /************************************************************************/
560 :
561 2312 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
562 : {
563 50657 : for (size_t i = 1; i < mTileMatrixList.size(); i++)
564 : {
565 97692 : if (mTileMatrixList[i].mScaleDenominator == 0 ||
566 48846 : std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
567 48846 : mTileMatrixList[i].mScaleDenominator -
568 : 2) > 1e-10)
569 : {
570 501 : return false;
571 : }
572 : }
573 1811 : return true;
574 : }
575 :
576 : /************************************************************************/
577 : /* hasVariableMatrixWidth() */
578 : /************************************************************************/
579 :
580 6855 : bool TileMatrixSet::hasVariableMatrixWidth() const
581 : {
582 186690 : for (const auto &oTM : mTileMatrixList)
583 : {
584 179837 : if (!oTM.mVariableMatrixWidthList.empty())
585 : {
586 2 : return true;
587 : }
588 : }
589 6853 : 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 45 : std::string TileMatrixSet::exportToTMSJsonV1() const
640 : {
641 90 : CPLJSONObject oRoot;
642 45 : oRoot["type"] = "TileMatrixSetType";
643 45 : oRoot["title"] = mTitle;
644 45 : oRoot["identifier"] = mIdentifier;
645 45 : if (!mAbstract.empty())
646 0 : oRoot["abstract"] = mAbstract;
647 45 : if (!std::isnan(mBbox.mLowerCornerX))
648 : {
649 45 : CPLJSONObject oBbox;
650 45 : oBbox["type"] = "BoundingBoxType";
651 45 : oBbox["crs"] = mBbox.mCrs;
652 45 : oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
653 45 : oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
654 45 : oRoot["boundingBox"] = std::move(oBbox);
655 : }
656 45 : oRoot["supportedCRS"] = mCrs;
657 45 : if (!mWellKnownScaleSet.empty())
658 40 : oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
659 :
660 45 : CPLJSONArray oTileMatrices;
661 133 : for (const auto &tm : mTileMatrixList)
662 : {
663 176 : CPLJSONObject oTM;
664 88 : oTM["type"] = "TileMatrixType";
665 88 : oTM["identifier"] = tm.mId;
666 88 : oTM["scaleDenominator"] = tm.mScaleDenominator;
667 88 : oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
668 88 : oTM["tileWidth"] = tm.mTileWidth;
669 88 : oTM["tileHeight"] = tm.mTileHeight;
670 88 : oTM["matrixWidth"] = tm.mMatrixWidth;
671 88 : oTM["matrixHeight"] = tm.mMatrixHeight;
672 :
673 88 : 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 88 : oTileMatrices.Add(oTM);
688 : }
689 45 : oRoot["tileMatrix"] = oTileMatrices;
690 90 : return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
691 90 : .replaceAll("\\/", '/');
692 : }
693 :
694 : } // namespace gdal
695 :
696 : //! @endcond
|