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