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 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_json.h"
30 : #include "ogr_spatialref.h"
31 :
32 : #include <cmath>
33 : #include <cfloat>
34 : #include <limits>
35 :
36 : #include "tilematrixset.hpp"
37 :
38 : //! @cond Doxygen_Suppress
39 :
40 : namespace gdal
41 : {
42 :
43 : /************************************************************************/
44 : /* listPredefinedTileMatrixSets() */
45 : /************************************************************************/
46 :
47 47 : std::set<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
48 : {
49 235 : std::set<std::string> l{"GoogleMapsCompatible", "InspireCRS84Quad"};
50 47 : const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
51 47 : if (pszSomeFile)
52 : {
53 94 : CPLStringList aosList(VSIReadDir(CPLGetDirname(pszSomeFile)));
54 7379 : for (int i = 0; i < aosList.size(); i++)
55 : {
56 7332 : const size_t nLen = strlen(aosList[i]);
57 14476 : if (nLen > strlen("tms_") + strlen(".json") &&
58 7520 : STARTS_WITH(aosList[i], "tms_") &&
59 188 : EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
60 : {
61 376 : std::string id(aosList[i] + strlen("tms_"),
62 376 : nLen - (strlen("tms_") + strlen(".json")));
63 188 : l.insert(id);
64 : }
65 : }
66 : }
67 47 : return l;
68 : }
69 :
70 : /************************************************************************/
71 : /* parse() */
72 : /************************************************************************/
73 :
74 415 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
75 : {
76 830 : CPLJSONDocument oDoc;
77 830 : std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
78 :
79 415 : constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
80 415 : if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
81 295 : EQUAL(
82 : fileOrDef,
83 : "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
84 : {
85 : /* See http://portal.opengeospatial.org/files/?artifact_id=35326
86 : * (WMTS 1.0), Annex E.4 */
87 120 : poTMS->mTitle = "GoogleMapsCompatible";
88 120 : poTMS->mIdentifier = "GoogleMapsCompatible";
89 120 : poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
90 120 : poTMS->mBbox.mCrs = poTMS->mCrs;
91 120 : poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
92 120 : poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
93 120 : poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
94 120 : poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
95 120 : poTMS->mWellKnownScaleSet =
96 120 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
97 3840 : for (int i = 0; i <= 30; i++)
98 : {
99 7440 : TileMatrix tm;
100 3720 : tm.mId = CPLSPrintf("%d", i);
101 3720 : tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
102 3720 : tm.mResY = tm.mResX;
103 3720 : tm.mScaleDenominator = tm.mResX / 0.28e-3;
104 3720 : tm.mTopLeftX = -HALF_CIRCUMFERENCE;
105 3720 : tm.mTopLeftY = HALF_CIRCUMFERENCE;
106 3720 : tm.mTileWidth = 256;
107 3720 : tm.mTileHeight = 256;
108 3720 : tm.mMatrixWidth = 1 << i;
109 3720 : tm.mMatrixHeight = 1 << i;
110 3720 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
111 : }
112 120 : return poTMS;
113 : }
114 :
115 295 : if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
116 243 : EQUAL(
117 : fileOrDef,
118 : "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
119 : {
120 : /* See InspireCRS84Quad at
121 : * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
122 : */
123 : /* This is exactly the same as PseudoTMS_GlobalGeodetic */
124 : /* See global-geodetic at
125 : * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
126 : // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
127 52 : poTMS->mTitle = "InspireCRS84Quad";
128 52 : poTMS->mIdentifier = "InspireCRS84Quad";
129 52 : poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
130 52 : poTMS->mBbox.mCrs = poTMS->mCrs;
131 52 : poTMS->mBbox.mLowerCornerX = -180;
132 52 : poTMS->mBbox.mLowerCornerY = -90;
133 52 : poTMS->mBbox.mUpperCornerX = 180;
134 52 : poTMS->mBbox.mUpperCornerY = 90;
135 52 : poTMS->mWellKnownScaleSet =
136 52 : "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
137 : // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
138 : // and at 30 it would overflow int32.
139 1612 : for (int i = 0; i <= 29; i++)
140 : {
141 3120 : TileMatrix tm;
142 1560 : tm.mId = CPLSPrintf("%d", i);
143 1560 : tm.mResX = 180. / 256 / (1 << i);
144 1560 : tm.mResY = tm.mResX;
145 1560 : tm.mScaleDenominator =
146 1560 : tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
147 1560 : tm.mTopLeftX = -180;
148 1560 : tm.mTopLeftY = 90;
149 1560 : tm.mTileWidth = 256;
150 1560 : tm.mTileHeight = 256;
151 1560 : tm.mMatrixWidth = 2 * (1 << i);
152 1560 : tm.mMatrixHeight = 1 << i;
153 1560 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
154 : }
155 52 : return poTMS;
156 : }
157 :
158 243 : bool loadOk = false;
159 243 : if ( // TMS 2.0 spec
160 243 : (strstr(fileOrDef, "\"crs\"") &&
161 40 : strstr(fileOrDef, "\"tileMatrices\"")) ||
162 : // TMS 1.0 spec
163 224 : (strstr(fileOrDef, "\"type\"") &&
164 24 : strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
165 200 : (strstr(fileOrDef, "\"identifier\"") &&
166 1 : strstr(fileOrDef, "\"boundingBox\"") &&
167 1 : strstr(fileOrDef, "\"tileMatrix\"")))
168 : {
169 44 : loadOk = oDoc.LoadMemory(fileOrDef);
170 : }
171 199 : else if (STARTS_WITH_CI(fileOrDef, "http://") ||
172 198 : STARTS_WITH_CI(fileOrDef, "https://"))
173 : {
174 1 : const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
175 1 : loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
176 : }
177 : else
178 : {
179 : VSIStatBufL sStat;
180 198 : if (VSIStatL(fileOrDef, &sStat) == 0)
181 : {
182 1 : loadOk = oDoc.Load(fileOrDef);
183 : }
184 : else
185 : {
186 197 : const char *pszFilename = CPLFindFile(
187 394 : "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
188 197 : if (pszFilename)
189 : {
190 195 : loadOk = oDoc.Load(pszFilename);
191 : }
192 : else
193 : {
194 2 : CPLError(CE_Failure, CPLE_AppDefined,
195 : "Invalid tiling matrix set name");
196 : }
197 : }
198 : }
199 243 : if (!loadOk)
200 : {
201 4 : return nullptr;
202 : }
203 :
204 478 : auto oRoot = oDoc.GetRoot();
205 : const bool bIsTMSv2 =
206 239 : oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
207 :
208 479 : if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
209 240 : !oRoot.GetObj("tileMatrix").IsValid())
210 : {
211 0 : CPLError(CE_Failure, CPLE_AppDefined,
212 : "Expected type = TileMatrixSetType");
213 0 : return nullptr;
214 : }
215 :
216 239 : poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
217 239 : poTMS->mTitle = oRoot.GetString("title");
218 239 : poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
219 717 : const auto oBbox = oRoot.GetObj("boundingBox");
220 239 : if (oBbox.IsValid())
221 : {
222 72 : poTMS->mBbox.mCrs = oBbox.GetString("crs");
223 216 : const auto oLowerCorner = oBbox.GetArray("lowerCorner");
224 72 : if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
225 : {
226 72 : poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
227 72 : poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
228 : }
229 216 : const auto oUpperCorner = oBbox.GetArray("upperCorner");
230 72 : if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
231 : {
232 72 : poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
233 72 : poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
234 : }
235 : }
236 239 : poTMS->mCrs = oRoot.GetString(bIsTMSv2 ? "crs" : "supportedCRS");
237 239 : poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
238 :
239 478 : OGRSpatialReference oCrs;
240 239 : if (oCrs.SetFromUserInput(
241 239 : poTMS->mCrs.c_str(),
242 239 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
243 : OGRERR_NONE)
244 : {
245 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
246 1 : poTMS->mCrs.c_str());
247 1 : return nullptr;
248 : }
249 238 : double dfMetersPerUnit = 1.0;
250 238 : if (oCrs.IsProjected())
251 : {
252 202 : dfMetersPerUnit = oCrs.GetLinearUnits();
253 : }
254 36 : else if (oCrs.IsGeographic())
255 : {
256 36 : dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
257 : }
258 :
259 : const auto oTileMatrices =
260 714 : oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
261 238 : if (oTileMatrices.IsValid())
262 : {
263 238 : double dfLastScaleDenominator = std::numeric_limits<double>::max();
264 4551 : for (const auto &oTM : oTileMatrices)
265 : {
266 4314 : TileMatrix tm;
267 4314 : tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
268 4314 : tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
269 4314 : if (tm.mScaleDenominator >= dfLastScaleDenominator ||
270 4314 : tm.mScaleDenominator <= 0)
271 : {
272 1 : CPLError(CE_Failure, CPLE_AppDefined,
273 : "Invalid scale denominator or non-decreasing series "
274 : "of scale denominators");
275 1 : return nullptr;
276 : }
277 4313 : dfLastScaleDenominator = tm.mScaleDenominator;
278 : // See note g of Table 2 of
279 : // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
280 4313 : tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
281 4313 : tm.mResY = tm.mResX;
282 4313 : if (bIsTMSv2)
283 : {
284 1626 : const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
285 542 : if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
286 : {
287 0 : CPLError(CE_Warning, CPLE_AppDefined,
288 : "cornerOfOrigin = %s not supported",
289 : osCornerOfOrigin.c_str());
290 : }
291 : }
292 : const auto oTopLeftCorner =
293 12939 : oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
294 4313 : if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
295 : {
296 4313 : tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
297 4313 : tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
298 : }
299 4313 : tm.mTileWidth = oTM.GetInteger("tileWidth");
300 4313 : tm.mTileHeight = oTM.GetInteger("tileHeight");
301 4313 : tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
302 4313 : tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
303 :
304 : const auto oVariableMatrixWidths = oTM.GetArray(
305 12939 : bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
306 4313 : if (oVariableMatrixWidths.IsValid())
307 : {
308 8 : for (const auto &oVMW : oVariableMatrixWidths)
309 : {
310 5 : TileMatrix::VariableMatrixWidth vmw;
311 5 : vmw.mCoalesce = oVMW.GetInteger("coalesce");
312 5 : vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
313 5 : vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
314 5 : tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
315 : }
316 : }
317 :
318 4313 : poTMS->mTileMatrixList.emplace_back(std::move(tm));
319 : }
320 : }
321 237 : if (poTMS->mTileMatrixList.empty())
322 : {
323 0 : CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
324 0 : return nullptr;
325 : }
326 :
327 237 : return poTMS;
328 : }
329 :
330 : /************************************************************************/
331 : /* haveAllLevelsSameTopLeft() */
332 : /************************************************************************/
333 :
334 371 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
335 : {
336 9365 : for (const auto &oTM : mTileMatrixList)
337 : {
338 17989 : if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
339 8994 : oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
340 : {
341 1 : return false;
342 : }
343 : }
344 370 : return true;
345 : }
346 :
347 : /************************************************************************/
348 : /* haveAllLevelsSameTileSize() */
349 : /************************************************************************/
350 :
351 371 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
352 : {
353 9365 : for (const auto &oTM : mTileMatrixList)
354 : {
355 17989 : if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
356 8994 : oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
357 : {
358 1 : return false;
359 : }
360 : }
361 370 : return true;
362 : }
363 :
364 : /************************************************************************/
365 : /* hasOnlyPowerOfTwoVaryingScales() */
366 : /************************************************************************/
367 :
368 300 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
369 : {
370 5813 : for (size_t i = 1; i < mTileMatrixList.size(); i++)
371 : {
372 11188 : if (mTileMatrixList[i].mScaleDenominator == 0 ||
373 5594 : std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
374 5594 : mTileMatrixList[i].mScaleDenominator -
375 : 2) > 1e-10)
376 : {
377 81 : return false;
378 : }
379 : }
380 219 : return true;
381 : }
382 :
383 : /************************************************************************/
384 : /* hasVariableMatrixWidth() */
385 : /************************************************************************/
386 :
387 292 : bool TileMatrixSet::hasVariableMatrixWidth() const
388 : {
389 7591 : for (const auto &oTM : mTileMatrixList)
390 : {
391 7301 : if (!oTM.mVariableMatrixWidthList.empty())
392 : {
393 2 : return true;
394 : }
395 : }
396 290 : return false;
397 : }
398 :
399 : } // namespace gdal
400 :
401 : //! @endcond
|