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
35 #include "tilematrixset.hpp"
36
37 //! @cond Doxygen_Suppress
38
39 namespace gdal
40 {
41
42 /************************************************************************/
43 /* listPredefinedTileMatrixSets() */
44 /************************************************************************/
45
listPredefinedTileMatrixSets()46 std::set<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
47 {
48 std::set<std::string> l{ "GoogleMapsCompatible",
49 "InspireCRS84Quad" };
50 const char* pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
51 if( pszSomeFile )
52 {
53 CPLStringList aosList(VSIReadDir(CPLGetDirname(pszSomeFile)));
54 for( int i = 0; i < aosList.size(); i++ )
55 {
56 const size_t nLen = strlen(aosList[i]);
57 if( nLen > strlen("tms_") + strlen(".json") &&
58 STARTS_WITH(aosList[i], "tms_") &&
59 EQUAL(aosList[i] + nLen - strlen(".json"), ".json") )
60 {
61 std::string id(aosList[i] + strlen("tms_"),
62 nLen - (strlen("tms_") + strlen(".json")));
63 l.insert(id);
64 }
65 }
66 }
67 return l;
68 }
69
70 /************************************************************************/
71 /* parse() */
72 /************************************************************************/
73
parse(const char * fileOrDef)74 std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char* fileOrDef)
75 {
76 CPLJSONDocument oDoc;
77 std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
78
79 constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
80 if( EQUAL(fileOrDef, "GoogleMapsCompatible") )
81 {
82 /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0), Annex E.4 */
83 poTMS->mTitle = "GoogleMapsCompatible";
84 poTMS->mIdentifier = "GoogleMapsCompatible";
85 poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
86 poTMS->mBbox.mCrs = poTMS->mCrs;
87 poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
88 poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
89 poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
90 poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
91 poTMS->mWellKnownScaleSet = "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
92 for( int i = 0; i < 25; i++ )
93 {
94 TileMatrix tm;
95 tm.mId = CPLSPrintf("%d", i);
96 tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
97 tm.mResY = tm.mResX;
98 tm.mScaleDenominator = tm.mResX / 0.28e-3;
99 tm.mTopLeftX = -HALF_CIRCUMFERENCE;
100 tm.mTopLeftY = HALF_CIRCUMFERENCE;
101 tm.mTileWidth = 256;
102 tm.mTileHeight = 256;
103 tm.mMatrixWidth = 1 << i;
104 tm.mMatrixHeight = 1 << i;
105 poTMS->mTileMatrixList.emplace_back(std::move(tm));
106 }
107 return poTMS;
108 }
109
110 if( EQUAL(fileOrDef, "InspireCRS84Quad") )
111 {
112 /* See InspireCRS84Quad at http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf */
113 /* This is exactly the same as PseudoTMS_GlobalGeodetic */
114 /* See global-geodetic at http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
115 // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
116 poTMS->mTitle = "InspireCRS84Quad";
117 poTMS->mIdentifier = "InspireCRS84Quad";
118 poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
119 poTMS->mBbox.mCrs = poTMS->mCrs;
120 poTMS->mBbox.mLowerCornerX = -180;
121 poTMS->mBbox.mLowerCornerY = -90;
122 poTMS->mBbox.mUpperCornerX = 180;
123 poTMS->mBbox.mUpperCornerY = 90;
124 poTMS->mWellKnownScaleSet = "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
125 for( int i = 0; i < 18; i++ )
126 {
127 TileMatrix tm;
128 tm.mId = CPLSPrintf("%d", i);
129 tm.mResX = 180. / 256 / (1 << i);
130 tm.mResY = tm.mResX;
131 tm.mScaleDenominator = tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
132 tm.mTopLeftX = -180;
133 tm.mTopLeftY = 90;
134 tm.mTileWidth = 256;
135 tm.mTileHeight = 256;
136 tm.mMatrixWidth = 2 * (1 << i);
137 tm.mMatrixHeight = 1 << i;
138 poTMS->mTileMatrixList.emplace_back(std::move(tm));
139 }
140 return poTMS;
141 }
142
143 bool loadOk = false;
144 if( (strstr(fileOrDef, "\"type\"") != nullptr &&
145 strstr(fileOrDef, "\"TileMatrixSetType\"") != nullptr) ||
146 (strstr(fileOrDef, "\"identifier\"") != nullptr &&
147 strstr(fileOrDef, "\"boundingBox\"") != nullptr &&
148 (strstr(fileOrDef, "\"tileMatrix\"") != nullptr ||
149 strstr(fileOrDef, "\"tileMatrices\"") != nullptr)) )
150 {
151 loadOk = oDoc.LoadMemory(fileOrDef);
152 }
153 else if( STARTS_WITH_CI(fileOrDef, "http://") ||
154 STARTS_WITH_CI(fileOrDef, "https://") )
155 {
156 const char* const apszOptions[] = { "MAX_FILE_SIZE=1000000", nullptr };
157 loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
158 }
159 else
160 {
161 VSIStatBufL sStat;
162 if( VSIStatL( fileOrDef, &sStat ) == 0 )
163 {
164 loadOk = oDoc.Load(fileOrDef);
165 }
166 else
167 {
168 const char* pszFilename = CPLFindFile( "gdal",
169 (std::string("tms_") + fileOrDef + ".json").c_str() );
170 if( pszFilename )
171 {
172 loadOk = oDoc.Load(pszFilename);
173 }
174 else
175 {
176 CPLError(CE_Failure, CPLE_AppDefined,
177 "Invalid tiling matrix set name");
178 }
179 }
180 }
181 if( !loadOk )
182 {
183 return nullptr;
184 }
185
186 auto oRoot = oDoc.GetRoot();
187 if( oRoot.GetString("type") != "TileMatrixSetType" &&
188 !oRoot.GetObj("tileMatrix").IsValid() &&
189 !oRoot.GetObj("tileMatrices").IsValid() )
190 {
191 CPLError(CE_Failure, CPLE_AppDefined, "Expected type = TileMatrixSetType");
192 return nullptr;
193 }
194
195 poTMS->mIdentifier = oRoot.GetString("identifier");
196 poTMS->mTitle = oRoot.GetString("title");
197 poTMS->mAbstract = oRoot.GetString("abstract");
198 const auto oBbox = oRoot.GetObj("boundingBox");
199 if( oBbox.IsValid() )
200 {
201 poTMS->mBbox.mCrs = oBbox.GetString("crs");
202 const auto oLowerCorner = oBbox.GetArray("lowerCorner");
203 if( oLowerCorner.IsValid() && oLowerCorner.Size() == 2 )
204 {
205 poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
206 poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
207 }
208 const auto oUpperCorner = oBbox.GetArray("upperCorner");
209 if( oUpperCorner.IsValid() && oUpperCorner.Size() == 2 )
210 {
211 poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
212 poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
213 }
214 }
215 poTMS->mCrs = oRoot.GetString("supportedCRS");
216 poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
217
218 OGRSpatialReference oCrs;
219 if( oCrs.SetFromUserInput(poTMS->mCrs.c_str()) != OGRERR_NONE )
220 {
221 CPLError(CE_Failure, CPLE_AppDefined,
222 "Cannot parse CRS %s", poTMS->mCrs.c_str());
223 return nullptr;
224 }
225 double dfMetersPerUnit = 1.0;
226 if( oCrs.IsProjected() )
227 {
228 dfMetersPerUnit = oCrs.GetLinearUnits();
229 }
230 else if( oCrs.IsGeographic() )
231 {
232 dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
233 }
234
235 const auto oTileMatrix = oRoot.GetObj("tileMatrix").IsValid() ?
236 oRoot.GetArray("tileMatrix") :
237 oRoot.GetArray("tileMatrices");
238 if( oTileMatrix.IsValid() )
239 {
240 double dfLastScaleDenominator = std::numeric_limits<double>::max();
241 for( const auto& oTM: oTileMatrix )
242 {
243 TileMatrix tm;
244 tm.mId = oTM.GetString("identifier");
245 tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
246 if( tm.mScaleDenominator >= dfLastScaleDenominator ||
247 tm.mScaleDenominator <= 0 )
248 {
249 CPLError(CE_Failure, CPLE_AppDefined,
250 "Invalid scale denominator or non-decreasing series "
251 "of scale denominators");
252 return nullptr;
253 }
254 dfLastScaleDenominator = tm.mScaleDenominator;
255 // See note g of Table 2 of http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
256 tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
257 tm.mResY = tm.mResX;
258 const auto oTopLeftCorner = oTM.GetArray("topLeftCorner");
259 if( oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2 )
260 {
261 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
262 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
263 }
264 tm.mTileWidth = oTM.GetInteger("tileWidth");
265 tm.mTileHeight = oTM.GetInteger("tileHeight");
266 tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
267 tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
268
269 const auto oVariableMatrixWidth = oTM.GetObj("variableMatrixWidth").IsValid() ?
270 oTM.GetArray("variableMatrixWidth") :
271 oTM.GetArray("variableMatrixWidths");
272 if( oVariableMatrixWidth.IsValid() )
273 {
274 for( const auto& oVMW: oVariableMatrixWidth )
275 {
276 TileMatrix::VariableMatrixWidth vmw;
277 vmw.mCoalesce = oVMW.GetInteger("coalesce");
278 vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
279 vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
280 tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
281 }
282 }
283
284 poTMS->mTileMatrixList.emplace_back(std::move(tm));
285 }
286 }
287 if( poTMS->mTileMatrixList.empty() )
288 {
289 CPLError(CE_Failure, CPLE_AppDefined,
290 "No tileMatrix defined");
291 return nullptr;
292 }
293
294 return poTMS;
295 }
296
297 /************************************************************************/
298 /* haveAllLevelsSameTopLeft() */
299 /************************************************************************/
300
haveAllLevelsSameTopLeft() const301 bool TileMatrixSet::haveAllLevelsSameTopLeft() const
302 {
303 for( const auto& oTM: mTileMatrixList )
304 {
305 if( oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
306 oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY )
307 {
308 return false;
309 }
310 }
311 return true;
312 }
313
314 /************************************************************************/
315 /* haveAllLevelsSameTileSize() */
316 /************************************************************************/
317
haveAllLevelsSameTileSize() const318 bool TileMatrixSet::haveAllLevelsSameTileSize() const
319 {
320 for( const auto& oTM: mTileMatrixList )
321 {
322 if( oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
323 oTM.mTileHeight != mTileMatrixList[0].mTileHeight )
324 {
325 return false;
326 }
327 }
328 return true;
329 }
330
331 /************************************************************************/
332 /* hasOnlyPowerOfTwoVaryingScales() */
333 /************************************************************************/
334
hasOnlyPowerOfTwoVaryingScales() const335 bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
336 {
337 for( size_t i = 1; i < mTileMatrixList.size(); i++ )
338 {
339 if( mTileMatrixList[i].mScaleDenominator == 0 ||
340 std::fabs(mTileMatrixList[i-1].mScaleDenominator /
341 mTileMatrixList[i].mScaleDenominator - 2) > 1e-10 )
342 {
343 return false;
344 }
345 }
346 return true;
347 }
348
349 /************************************************************************/
350 /* hasVariableMatrixWidth() */
351 /************************************************************************/
352
hasVariableMatrixWidth() const353 bool TileMatrixSet::hasVariableMatrixWidth() const
354 {
355 for( const auto& oTM: mTileMatrixList )
356 {
357 if( !oTM.mVariableMatrixWidthList.empty() )
358 {
359 return true;
360 }
361 }
362 return false;
363 }
364
365 } // namespace gdal
366
367 //! @endcond
368