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