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