Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Virtual file system /vsipmtiles/ PMTiles
5 : * Author: Even Rouault <even.rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2023, Planet Labs
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_vsi_virtual.h"
14 :
15 : #include "vsipmtiles.h"
16 : #include "ogr_pmtiles.h"
17 :
18 : #include "cpl_json.h"
19 :
20 : #include <set>
21 :
22 : #define ENDS_WITH_CI(a, b) \
23 : (strlen(a) >= strlen(b) && EQUAL(a + strlen(a) - strlen(b), b))
24 :
25 : constexpr const char *PMTILES_HEADER_JSON = "pmtiles_header.json";
26 : constexpr const char *METADATA_JSON = "metadata.json";
27 :
28 : /************************************************************************/
29 : /* VSIPMTilesFilesystemHandler */
30 : /************************************************************************/
31 :
32 : class VSIPMTilesFilesystemHandler final : public VSIFilesystemHandler
33 : {
34 : public:
35 1741 : VSIPMTilesFilesystemHandler() = default;
36 :
37 : VSIVirtualHandleUniquePtr Open(const char *pszFilename,
38 : const char *pszAccess, bool bSetError,
39 : CSLConstList papszOptions) override;
40 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
41 : int nFlags) override;
42 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
43 : };
44 :
45 : /************************************************************************/
46 : /* VSIPMTilesGetTileExtension() */
47 : /************************************************************************/
48 :
49 36 : static const char *VSIPMTilesGetTileExtension(OGRPMTilesDataset *poDS)
50 : {
51 36 : const auto &sHeader = poDS->GetHeader();
52 36 : switch (sHeader.tile_type)
53 : {
54 0 : case pmtiles::TILETYPE_PNG:
55 0 : return ".png";
56 0 : case pmtiles::TILETYPE_JPEG:
57 0 : return ".jpg";
58 0 : case pmtiles::TILETYPE_WEBP:
59 0 : return ".webp";
60 36 : case pmtiles::TILETYPE_MVT:
61 36 : return ".mvt";
62 : }
63 0 : if (sHeader.tile_compression == pmtiles::COMPRESSION_GZIP)
64 0 : return ".bin.gz";
65 0 : if (sHeader.tile_compression == pmtiles::COMPRESSION_ZSTD)
66 0 : return ".bin.zstd";
67 0 : return ".bin";
68 : }
69 :
70 : /************************************************************************/
71 : /* VSIPMTilesGetPMTilesHeaderJson() */
72 : /************************************************************************/
73 :
74 4 : static std::string VSIPMTilesGetPMTilesHeaderJson(OGRPMTilesDataset *poDS)
75 : {
76 4 : const auto &sHeader = poDS->GetHeader();
77 8 : CPLJSONDocument oDoc;
78 8 : CPLJSONObject oRoot;
79 4 : oRoot.Set("root_dir_offset", sHeader.root_dir_offset);
80 4 : oRoot.Set("json_metadata_offset", sHeader.json_metadata_offset);
81 4 : oRoot.Set("json_metadata_bytes", sHeader.json_metadata_bytes);
82 4 : oRoot.Set("leaf_dirs_offset", sHeader.leaf_dirs_offset);
83 4 : oRoot.Set("leaf_dirs_bytes", sHeader.leaf_dirs_bytes);
84 4 : oRoot.Set("tile_data_offset", sHeader.tile_data_offset);
85 4 : oRoot.Set("tile_data_bytes", sHeader.tile_data_bytes);
86 4 : oRoot.Set("addressed_tiles_count", sHeader.addressed_tiles_count);
87 4 : oRoot.Set("tile_entries_count", sHeader.tile_entries_count);
88 4 : oRoot.Set("tile_contents_count", sHeader.tile_contents_count);
89 4 : oRoot.Set("clustered", sHeader.clustered);
90 4 : oRoot.Set("internal_compression", sHeader.internal_compression);
91 4 : oRoot.Set("internal_compression_str",
92 4 : OGRPMTilesDataset::GetCompression(sHeader.internal_compression));
93 4 : oRoot.Set("tile_compression", sHeader.tile_compression);
94 4 : oRoot.Set("tile_compression_str",
95 4 : OGRPMTilesDataset::GetCompression(sHeader.tile_compression));
96 4 : oRoot.Set("tile_type", sHeader.tile_type);
97 4 : oRoot.Set("tile_type_str", OGRPMTilesDataset::GetTileType(sHeader));
98 4 : oRoot.Set("min_zoom", sHeader.min_zoom);
99 4 : oRoot.Set("max_zoom", sHeader.max_zoom);
100 4 : oRoot.Set("min_lon_e7", sHeader.min_lon_e7);
101 4 : oRoot.Set("min_lon_e7_float", sHeader.min_lon_e7 / 10e6);
102 4 : oRoot.Set("min_lat_e7", sHeader.min_lat_e7);
103 4 : oRoot.Set("min_lat_e7_float", sHeader.min_lat_e7 / 10e6);
104 4 : oRoot.Set("max_lon_e7", sHeader.max_lon_e7);
105 4 : oRoot.Set("max_lon_e7_float", sHeader.max_lon_e7 / 10e6);
106 4 : oRoot.Set("max_lat_e7", sHeader.max_lat_e7);
107 4 : oRoot.Set("max_lat_e7_float", sHeader.max_lat_e7 / 10e6);
108 4 : oRoot.Set("center_zoom", sHeader.center_zoom);
109 4 : oRoot.Set("center_lon_e7", sHeader.center_lon_e7);
110 4 : oRoot.Set("center_lat_e7", sHeader.center_lat_e7);
111 4 : oDoc.SetRoot(oRoot);
112 8 : return oDoc.SaveAsString();
113 : }
114 :
115 : /************************************************************************/
116 : /* VSIPMTilesOpen() */
117 : /************************************************************************/
118 :
119 : static std::unique_ptr<OGRPMTilesDataset>
120 58 : VSIPMTilesOpen(const char *pszFilename, std::string &osSubfilename,
121 : int &nComponents, int &nZ, int &nX, int &nY)
122 : {
123 58 : if (!STARTS_WITH(pszFilename, "/vsipmtiles/"))
124 0 : return nullptr;
125 58 : pszFilename += strlen("/vsipmtiles/");
126 :
127 116 : std::string osFilename(pszFilename);
128 58 : if (!osFilename.empty() && osFilename.back() == '/')
129 0 : osFilename.pop_back();
130 58 : pszFilename = osFilename.c_str();
131 :
132 58 : nZ = nX = nY = -1;
133 58 : nComponents = 0;
134 116 : std::string osPmtilesFilename;
135 :
136 58 : const char *pszPmtilesExt = strstr(pszFilename, ".pmtiles");
137 58 : if (!pszPmtilesExt)
138 1 : return nullptr;
139 :
140 114 : CPLStringList aosTokens;
141 : do
142 : {
143 57 : if (pszPmtilesExt[strlen(".pmtiles")] == '/')
144 : {
145 54 : const char *pszSubFile = pszPmtilesExt + strlen(".pmtiles/");
146 54 : osPmtilesFilename.assign(pszFilename, pszSubFile - pszFilename - 1);
147 54 : osSubfilename = pszPmtilesExt + strlen(".pmtiles/");
148 101 : if (osSubfilename == METADATA_JSON ||
149 47 : osSubfilename == PMTILES_HEADER_JSON)
150 : {
151 11 : break;
152 : }
153 : }
154 : else
155 : {
156 3 : osPmtilesFilename = pszFilename;
157 3 : osSubfilename.clear();
158 3 : break;
159 : }
160 :
161 43 : aosTokens = CSLTokenizeString2(osSubfilename.c_str(), "/", 0);
162 43 : nComponents = aosTokens.size();
163 43 : if (nComponents >= 4)
164 0 : return nullptr;
165 :
166 43 : if (CPLGetValueType(aosTokens[0]) != CPL_VALUE_INTEGER)
167 2 : return nullptr;
168 41 : nZ = atoi(aosTokens[0]);
169 41 : if (nComponents == 1)
170 3 : break;
171 :
172 38 : if (CPLGetValueType(aosTokens[1]) != CPL_VALUE_INTEGER)
173 1 : return nullptr;
174 37 : nX = atoi(aosTokens[1]);
175 37 : if (nComponents == 2)
176 4 : break;
177 :
178 33 : break;
179 : } while (false);
180 :
181 108 : GDALOpenInfo oOpenInfo(osPmtilesFilename.c_str(), GA_ReadOnly);
182 108 : CPLStringList aosOptions;
183 54 : aosOptions.SetNameValue("DECOMPRESS_TILES", "NO");
184 54 : aosOptions.SetNameValue("ACCEPT_ANY_TILE_TYPE", "YES");
185 54 : oOpenInfo.papszOpenOptions = aosOptions.List();
186 108 : auto poDS = std::make_unique<OGRPMTilesDataset>();
187 : {
188 54 : CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler);
189 54 : if (!poDS->Open(&oOpenInfo))
190 4 : return nullptr;
191 : }
192 :
193 50 : if (nComponents == 3)
194 : {
195 33 : const char *pszTileExt = VSIPMTilesGetTileExtension(poDS.get());
196 33 : if (!ENDS_WITH_CI(aosTokens[2], pszTileExt))
197 2 : return nullptr;
198 31 : aosTokens[2][strlen(aosTokens[2]) - strlen(pszTileExt)] = 0;
199 31 : if (CPLGetValueType(aosTokens[2]) != CPL_VALUE_INTEGER)
200 0 : return nullptr;
201 31 : nY = atoi(aosTokens[2]);
202 : }
203 48 : return poDS;
204 : }
205 :
206 : /************************************************************************/
207 : /* Open() */
208 : /************************************************************************/
209 :
210 : VSIVirtualHandleUniquePtr
211 23 : VSIPMTilesFilesystemHandler::Open(const char *pszFilename,
212 : const char *pszAccess, bool /*bSetError*/,
213 : CSLConstList /*papszOptions*/)
214 : {
215 23 : if (strchr(pszAccess, '+') || strchr(pszAccess, 'w') ||
216 22 : strchr(pszAccess, 'a'))
217 1 : return nullptr;
218 44 : std::string osSubfilename;
219 : int nComponents;
220 : int nZ;
221 : int nX;
222 : int nY;
223 : auto poDS =
224 44 : VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
225 22 : if (!poDS)
226 7 : return nullptr;
227 :
228 15 : if (osSubfilename == METADATA_JSON)
229 : {
230 : return VSIVirtualHandleUniquePtr(
231 : VSIFileFromMemBuffer(nullptr,
232 2 : reinterpret_cast<GByte *>(CPLStrdup(
233 2 : poDS->GetMetadataContent().c_str())),
234 4 : poDS->GetMetadataContent().size(), true));
235 : }
236 :
237 13 : if (osSubfilename == PMTILES_HEADER_JSON)
238 : {
239 6 : const auto osStr = VSIPMTilesGetPMTilesHeaderJson(poDS.get());
240 : return VSIVirtualHandleUniquePtr(VSIFileFromMemBuffer(
241 3 : nullptr, reinterpret_cast<GByte *>(CPLStrdup(osStr.c_str())),
242 6 : osStr.size(), true));
243 : }
244 :
245 10 : if (nComponents != 3)
246 3 : return nullptr;
247 :
248 14 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
249 :
250 14 : OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, nY, nX, nY);
251 7 : auto sTile = oIter.GetNextTile();
252 7 : if (sTile.offset == 0)
253 1 : return nullptr;
254 :
255 6 : const auto *posStr = poDS->ReadTileData(sTile.offset, sTile.length);
256 6 : if (!posStr)
257 : {
258 0 : return nullptr;
259 : }
260 :
261 6 : GByte *pabyData = static_cast<GByte *>(CPLMalloc(posStr->size()));
262 6 : memcpy(pabyData, posStr->data(), posStr->size());
263 : return VSIVirtualHandleUniquePtr(
264 6 : VSIFileFromMemBuffer(nullptr, pabyData, posStr->size(), true));
265 : }
266 :
267 : /************************************************************************/
268 : /* Stat() */
269 : /************************************************************************/
270 :
271 26 : int VSIPMTilesFilesystemHandler::Stat(const char *pszFilename,
272 : VSIStatBufL *pStatBuf, int /*nFlags*/)
273 : {
274 26 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
275 :
276 52 : std::string osSubfilename;
277 : int nComponents;
278 : int nZ;
279 : int nX;
280 : int nY;
281 : auto poDS =
282 52 : VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
283 26 : if (!poDS)
284 0 : return -1;
285 :
286 26 : if (osSubfilename.empty())
287 0 : return -1;
288 :
289 : VSIStatBufL sStatPmtiles;
290 26 : if (VSIStatL(poDS->GetDescription(), &sStatPmtiles) == 0)
291 : {
292 26 : pStatBuf->st_mtime = sStatPmtiles.st_mtime;
293 : }
294 :
295 26 : if (osSubfilename == METADATA_JSON)
296 : {
297 2 : pStatBuf->st_mode = S_IFREG;
298 2 : pStatBuf->st_size = poDS->GetMetadataContent().size();
299 2 : return 0;
300 : }
301 :
302 24 : if (osSubfilename == PMTILES_HEADER_JSON)
303 : {
304 1 : pStatBuf->st_mode = S_IFREG;
305 1 : pStatBuf->st_size = VSIPMTilesGetPMTilesHeaderJson(poDS.get()).size();
306 1 : return 0;
307 : }
308 :
309 46 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
310 :
311 46 : OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, nY, nX, nY);
312 23 : auto sTile = oIter.GetNextTile();
313 23 : if (sTile.offset == 0)
314 1 : return -1;
315 :
316 22 : if (nComponents <= 2)
317 : {
318 0 : pStatBuf->st_mode = S_IFDIR;
319 0 : return 0;
320 : }
321 :
322 22 : pStatBuf->st_mode = S_IFREG;
323 22 : pStatBuf->st_size = sTile.length;
324 22 : return 0;
325 : }
326 :
327 : /************************************************************************/
328 : /* ReadDirEx() */
329 : /************************************************************************/
330 :
331 10 : char **VSIPMTilesFilesystemHandler::ReadDirEx(const char *pszFilename,
332 : int nMaxFiles)
333 : {
334 20 : std::string osSubfilename;
335 : int nComponents;
336 : int nZ;
337 : int nX;
338 : int nY;
339 : auto poDS =
340 20 : VSIPMTilesOpen(pszFilename, osSubfilename, nComponents, nZ, nX, nY);
341 10 : if (!poDS)
342 3 : return nullptr;
343 :
344 7 : if (osSubfilename.empty())
345 : {
346 2 : CPLStringList aosFiles;
347 1 : aosFiles.AddString(PMTILES_HEADER_JSON);
348 1 : aosFiles.AddString(METADATA_JSON);
349 4 : for (int i = poDS->GetMinZoomLevel(); i <= poDS->GetMaxZoomLevel(); ++i)
350 : {
351 3 : OGRPMTilesTileIterator oIter(poDS.get(), i);
352 3 : auto sTile = oIter.GetNextTile();
353 3 : if (sTile.offset != 0)
354 : {
355 3 : if (nMaxFiles > 0 && aosFiles.size() >= nMaxFiles)
356 0 : break;
357 3 : aosFiles.AddString(CPLSPrintf("%d", i));
358 : }
359 : }
360 1 : return aosFiles.StealList();
361 : }
362 :
363 6 : if (nComponents == 1)
364 : {
365 4 : std::set<int> oSetX;
366 4 : OGRPMTilesTileIterator oIter(poDS.get(), nZ);
367 : while (true)
368 : {
369 4 : auto sTile = oIter.GetNextTile();
370 4 : if (sTile.offset == 0)
371 2 : break;
372 2 : oSetX.insert(sTile.x);
373 2 : if (nMaxFiles > 0 && static_cast<int>(oSetX.size()) >= nMaxFiles)
374 0 : break;
375 2 : if (oSetX.size() == 1024 * 1024)
376 : {
377 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too many tiles");
378 0 : return nullptr;
379 : }
380 2 : }
381 4 : CPLStringList aosFiles;
382 4 : for (int x : oSetX)
383 : {
384 2 : aosFiles.AddString(CPLSPrintf("%d", x));
385 : }
386 2 : return aosFiles.StealList();
387 : }
388 :
389 4 : if (nComponents == 2)
390 : {
391 6 : std::set<int> oSetY;
392 6 : OGRPMTilesTileIterator oIter(poDS.get(), nZ, nX, -1, nX, -1);
393 : while (true)
394 : {
395 4 : auto sTile = oIter.GetNextTile();
396 4 : if (sTile.offset == 0)
397 3 : break;
398 1 : oSetY.insert(sTile.y);
399 1 : if (nMaxFiles > 0 && static_cast<int>(oSetY.size()) >= nMaxFiles)
400 0 : break;
401 1 : if (oSetY.size() == 1024 * 1024)
402 : {
403 0 : CPLError(CE_Failure, CPLE_AppDefined, "Too many tiles");
404 0 : return nullptr;
405 : }
406 1 : }
407 6 : CPLStringList aosFiles;
408 3 : const char *pszTileExt = VSIPMTilesGetTileExtension(poDS.get());
409 4 : for (int y : oSetY)
410 : {
411 1 : aosFiles.AddString(CPLSPrintf("%d%s", y, pszTileExt));
412 : }
413 3 : return aosFiles.StealList();
414 : }
415 :
416 1 : return nullptr;
417 : }
418 :
419 : /************************************************************************/
420 : /* VSIPMTilesRegister() */
421 : /************************************************************************/
422 :
423 1741 : void VSIPMTilesRegister()
424 : {
425 1741 : if (VSIFileManager::GetHandler("/vsipmtiles/") ==
426 1741 : VSIFileManager::GetHandler("/"))
427 : {
428 1741 : VSIFileManager::InstallHandler("/vsipmtiles/",
429 1741 : new VSIPMTilesFilesystemHandler());
430 : }
431 1741 : }
|