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