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