Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implementation of 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 "ogr_pmtiles.h"
14 :
15 : #include "vsipmtiles.h"
16 :
17 : #include "ogrpmtilesfrommbtiles.h"
18 :
19 : #include "mbtiles.h"
20 :
21 : #ifdef GDAL_ENABLE_ALGORITHMS
22 : #include "gdalalg_raster_tile.h"
23 : #include "ogrpmtilesfromtileset.h"
24 : #endif
25 :
26 : #ifdef HAVE_MVT_WRITE_SUPPORT
27 : #include "mvtutils.h"
28 : #endif
29 :
30 : #include <algorithm>
31 : #include <cmath>
32 :
33 : /************************************************************************/
34 : /* OGRPMTilesDriverIdentify() */
35 : /************************************************************************/
36 :
37 60198 : static int OGRPMTilesDriverIdentify(GDALOpenInfo *poOpenInfo)
38 : {
39 62642 : if (poOpenInfo->nHeaderBytes < PMTILES_HEADER_LENGTH || !poOpenInfo->fpL ||
40 2444 : !poOpenInfo->IsExtensionEqualToCI("pmtiles"))
41 : {
42 60059 : return FALSE;
43 : }
44 139 : return memcmp(poOpenInfo->pabyHeader, "PMTiles\x03", 8) == 0;
45 : }
46 :
47 : /************************************************************************/
48 : /* OGRPMTilesDriverOpen() */
49 : /************************************************************************/
50 :
51 84 : static GDALDataset *OGRPMTilesDriverOpen(GDALOpenInfo *poOpenInfo)
52 : {
53 84 : if (!OGRPMTilesDriverIdentify(poOpenInfo))
54 5 : return nullptr;
55 158 : auto poDS = std::make_unique<OGRPMTilesDataset>();
56 79 : if (!poDS->Open(poOpenInfo))
57 8 : return nullptr;
58 71 : return poDS.release();
59 : }
60 :
61 : /************************************************************************/
62 : /* OGRPMTilesDriverCanVectorTranslateFrom() */
63 : /************************************************************************/
64 :
65 5 : static bool OGRPMTilesDriverCanVectorTranslateFrom(
66 : const char * /*pszDestName*/, GDALDataset *poSourceDS,
67 : CSLConstList papszVectorTranslateArguments, char ***ppapszFailureReasons)
68 : {
69 5 : auto poSrcDriver = poSourceDS->GetDriver();
70 5 : if (!(poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MBTiles")))
71 : {
72 1 : if (ppapszFailureReasons)
73 1 : *ppapszFailureReasons = CSLAddString(
74 : *ppapszFailureReasons, "Source driver is not MBTiles");
75 1 : return false;
76 : }
77 :
78 4 : if (papszVectorTranslateArguments)
79 : {
80 2 : const int nArgs = CSLCount(papszVectorTranslateArguments);
81 4 : for (int i = 0; i < nArgs; ++i)
82 : {
83 2 : if (i + 1 < nArgs &&
84 2 : (strcmp(papszVectorTranslateArguments[i], "-f") == 0 ||
85 0 : strcmp(papszVectorTranslateArguments[i], "-of") == 0))
86 : {
87 2 : ++i;
88 : }
89 : else
90 : {
91 0 : if (ppapszFailureReasons)
92 0 : *ppapszFailureReasons =
93 0 : CSLAddString(*ppapszFailureReasons,
94 : "Direct copy from MBTiles does not "
95 : "support GDALVectorTranslate() options");
96 0 : return false;
97 : }
98 : }
99 : }
100 :
101 4 : return true;
102 : }
103 :
104 : /************************************************************************/
105 : /* OGRPMTilesDriverVectorTranslateFrom() */
106 : /************************************************************************/
107 :
108 2 : static GDALDataset *OGRPMTilesDriverVectorTranslateFrom(
109 : const char *pszDestName, GDALDataset *poSourceDS,
110 : CSLConstList papszVectorTranslateArguments,
111 : GDALProgressFunc /* pfnProgress */, void * /* pProgressData */)
112 : {
113 2 : if (!OGRPMTilesDriverCanVectorTranslateFrom(
114 : pszDestName, poSourceDS, papszVectorTranslateArguments, nullptr))
115 : {
116 0 : return nullptr;
117 : }
118 :
119 2 : if (!OGRPMTilesConvertFromMBTiles(pszDestName,
120 2 : poSourceDS->GetDescription()))
121 : {
122 0 : return nullptr;
123 : }
124 :
125 4 : GDALOpenInfo oOpenInfo(pszDestName, GA_ReadOnly);
126 2 : oOpenInfo.nOpenFlags = GDAL_OF_VECTOR;
127 2 : return OGRPMTilesDriverOpen(&oOpenInfo);
128 : }
129 :
130 : #ifdef HAVE_MVT_WRITE_SUPPORT
131 : /************************************************************************/
132 : /* OGRPMTilesDriverCreate() */
133 : /************************************************************************/
134 :
135 66 : static GDALDataset *OGRPMTilesDriverCreate(const char *pszFilename, int nXSize,
136 : int nYSize, int nBandsIn,
137 : GDALDataType eDT,
138 : CSLConstList papszOptions)
139 : {
140 66 : if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eDT == GDT_Unknown)
141 : {
142 68 : auto poDS = std::make_unique<OGRPMTilesWriterDataset>();
143 34 : if (!poDS->Create(pszFilename, papszOptions))
144 1 : return nullptr;
145 33 : return poDS.release();
146 : }
147 32 : CPLError(CE_Failure, CPLE_AppDefined, "Create() not supported for raster");
148 32 : return nullptr;
149 : }
150 : #endif
151 :
152 : /************************************************************************/
153 : /* OGRPMTilesDriverCreateCopy() */
154 : /************************************************************************/
155 :
156 35 : static GDALDataset *OGRPMTilesDriverCreateCopy(const char *pszFilename,
157 : GDALDataset *poSrcDS, int,
158 : CSLConstList papszOptions,
159 : GDALProgressFunc pfnProgress,
160 : void *pProgressData)
161 : {
162 35 : auto poSrcDriver = poSrcDS->GetDriver();
163 35 : if (poSrcDriver && EQUAL(poSrcDriver->GetDescription(), "MBTiles"))
164 : {
165 3 : if (papszOptions && papszOptions[0])
166 : {
167 0 : CPLError(CE_Failure, CPLE_AppDefined,
168 : "No creation option is supported for conversion of "
169 : "MBTiles to PMTiles");
170 0 : return nullptr;
171 : }
172 3 : if (!OGRPMTilesConvertFromMBTiles(pszFilename,
173 3 : poSrcDS->GetDescription()))
174 0 : return nullptr;
175 : }
176 : else
177 : {
178 : const int nBlockSize = std::clamp(
179 0 : atoi(CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256")), 64,
180 32 : 8192);
181 : const char *pszResampling =
182 32 : CSLFetchNameValueDef(papszOptions, "RESAMPLING", "BILINEAR");
183 32 : CPLStringList aosOptions(papszOptions);
184 32 : if (!aosOptions.FetchNameValue("NAME"))
185 : aosOptions.SetNameValue("NAME",
186 32 : CPLGetBasenameSafe(pszFilename).c_str());
187 32 : if (!aosOptions.FetchNameValue("DESCRIPTION"))
188 : aosOptions.SetNameValue("DESCRIPTION",
189 32 : CPLGetBasenameSafe(pszFilename).c_str());
190 :
191 : // There are two code paths possible:
192 : // - the preferred one, using "gdal raster tile" when possible, since
193 : // this is multi-threaded
194 : // - the fallback one, using a temporary MBTiles dataset
195 :
196 : #if GDAL_ENABLE_ALGORITHMS
197 0 : std::unique_ptr<GDALDataset> poTmpSrcDS;
198 32 : std::string osTmpSrcFilename;
199 :
200 : // Check that the dataset can be used in a thread-safe way to be able
201 : // to use gdal raster tile
202 : bool bCanCreateThreadSafeDataset;
203 : {
204 64 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
205 : bCanCreateThreadSafeDataset =
206 64 : std::unique_ptr<GDALDataset>(GDALGetThreadSafeDataset(
207 32 : poSrcDS, GDAL_OF_RASTER)) != nullptr;
208 :
209 : // Case of anonymous VRT, e.g. when doing
210 : // gdal_translate in.tif out.pmtiles -outsize ...
211 1 : if (!bCanCreateThreadSafeDataset && poSrcDriver &&
212 33 : EQUAL(poSrcDriver->GetDescription(), "VRT") &&
213 0 : EQUAL(poSrcDS->GetDescription(), ""))
214 : {
215 : osTmpSrcFilename =
216 0 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename));
217 0 : osTmpSrcFilename += ".vrt";
218 0 : poTmpSrcDS.reset(
219 : poSrcDriver->CreateCopy(osTmpSrcFilename.c_str(), poSrcDS,
220 : false, nullptr, nullptr, nullptr));
221 0 : if (poTmpSrcDS)
222 : {
223 0 : poTmpSrcDS->MarkSuppressOnClose();
224 0 : bCanCreateThreadSafeDataset = true;
225 0 : poSrcDS = poTmpSrcDS.get();
226 : }
227 : else
228 : {
229 0 : VSIUnlink(osTmpSrcFilename.c_str());
230 : }
231 : }
232 : }
233 :
234 : const char *pszTileFormat =
235 32 : CSLFetchNameValueDef(papszOptions, "TILE_FORMAT", "PNG");
236 : // gdal raster tile doesn't support the PNG8 tile format
237 : // or the ZOOM_LEVEL_STRATEGY option
238 59 : if (bCanCreateThreadSafeDataset && !EQUAL(pszTileFormat, "PNG8") &&
239 27 : !CSLFetchNameValue(papszOptions, "ZOOM_LEVEL_STRATEGY"))
240 : {
241 : std::string osTmpTilesDirectory =
242 48 : std::string(pszFilename).append(".tmp");
243 24 : if (!VSIIsLocal(pszFilename))
244 : {
245 : osTmpTilesDirectory =
246 0 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename));
247 : }
248 :
249 24 : GDALRasterTileAlgorithm alg;
250 : try
251 : {
252 24 : alg["input"] = poSrcDS;
253 24 : alg["output"] = osTmpTilesDirectory;
254 24 : alg["format"] = pszTileFormat;
255 24 : alg["tile-size"] = nBlockSize;
256 24 : alg["min-zoom-single-tile"] = true;
257 24 : alg["resampling"] = pszResampling;
258 24 : alg["overview-resampling"] = pszResampling;
259 24 : CPLStringList aosCO;
260 24 : if (const char *pszQuality =
261 24 : CSLFetchNameValue(papszOptions, "QUALITY"))
262 : {
263 0 : if (EQUAL(pszTileFormat, "JPEG") ||
264 0 : EQUAL(pszTileFormat, "WEBP"))
265 0 : aosCO.SetNameValue("QUALITY", pszQuality);
266 : }
267 24 : if (const char *pszZLevel =
268 24 : CSLFetchNameValue(papszOptions, "ZLEVEL"))
269 : {
270 0 : if (EQUAL(pszTileFormat, "PNG"))
271 0 : aosCO.SetNameValue("ZLEVEL", pszZLevel);
272 : }
273 48 : alg["creation-option"] =
274 72 : static_cast<std::vector<std::string>>(aosCO);
275 : }
276 0 : catch (const std::exception &e)
277 : {
278 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
279 0 : return nullptr;
280 : }
281 :
282 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
283 : pScaledProgress(GDALCreateScaledProgress(0.0, 0.9, pfnProgress,
284 : pProgressData),
285 24 : GDALDestroyScaledProgress);
286 : const bool bRet =
287 24 : alg.Run(pScaledProgress ? GDALScaledProgress : nullptr,
288 12 : pScaledProgress.get()) &&
289 36 : alg.Finalize() &&
290 12 : OGRPMTilesConvertFromTileset(pszFilename,
291 : osTmpTilesDirectory.c_str(),
292 12 : poSrcDS, aosOptions.List());
293 24 : VSIRmdirRecursive(osTmpTilesDirectory.c_str());
294 24 : if (!bRet)
295 12 : return nullptr;
296 12 : if (pfnProgress)
297 12 : pfnProgress(1.0, "", pProgressData);
298 : }
299 : else
300 : #endif
301 : {
302 : auto poMBTilesDrv =
303 8 : GetGDALDriverManager()->GetDriverByName("MBTiles");
304 8 : if (!poMBTilesDrv)
305 : {
306 0 : CPLError(CE_Failure, CPLE_AppDefined,
307 : "Conversion to PMTiles requires prior conversion to "
308 : "MBTiles but driver is not available");
309 1 : return nullptr;
310 : }
311 : std::string osTmpMBTilesFilename =
312 8 : CPLResetExtensionSafe(pszFilename, "mbtiles");
313 8 : if (!VSIIsLocal(pszFilename))
314 : {
315 : osTmpMBTilesFilename =
316 0 : CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename));
317 : }
318 :
319 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
320 : pScaledProgress(GDALCreateScaledProgress(0.0, 2.0 / 3 * 0.9,
321 : pfnProgress,
322 : pProgressData),
323 8 : GDALDestroyScaledProgress);
324 : std::unique_ptr<GDALDataset> poMBTilesDS(poMBTilesDrv->CreateCopy(
325 8 : osTmpMBTilesFilename.c_str(), poSrcDS, false, aosOptions.List(),
326 8 : pScaledProgress ? GDALScaledProgress : nullptr,
327 24 : pScaledProgress.get()));
328 8 : if (!poMBTilesDS)
329 : {
330 : // Error message emitted by CreateCopy()
331 : VSIStatBufL sStat;
332 1 : if (VSIStatL(osTmpMBTilesFilename.c_str(), &sStat) == 0 &&
333 0 : VSIUnlink(osTmpMBTilesFilename.c_str()) != 0)
334 : {
335 0 : CPLError(CE_Warning, CPLE_FileIO, "Cannot delete %s",
336 : osTmpMBTilesFilename.c_str());
337 : }
338 1 : return nullptr;
339 : }
340 7 : const int nMaxDim = std::max(poMBTilesDS->GetRasterXSize(),
341 14 : poMBTilesDS->GetRasterYSize());
342 7 : const int nOvrCount = static_cast<int>(std::ceil(
343 7 : std::log2(static_cast<double>(nMaxDim) / nBlockSize)));
344 7 : if (nOvrCount > 0)
345 : {
346 3 : std::vector<int> anLevels;
347 7 : for (int i = 0; i < nOvrCount; ++i)
348 4 : anLevels.push_back(1 << (i + 1));
349 3 : pScaledProgress.reset(GDALCreateScaledProgress(
350 : 2.0 / 3 * 0.9, 0.9, pfnProgress, pProgressData));
351 : const bool bOK =
352 9 : (poMBTilesDS->BuildOverviews(
353 3 : pszResampling, nOvrCount, anLevels.data(), 0, nullptr,
354 3 : pScaledProgress ? GDALScaledProgress : nullptr,
355 3 : pScaledProgress.get(), nullptr) == CE_None);
356 3 : if (!bOK)
357 : {
358 0 : poMBTilesDS.reset();
359 : // Error message emitted by BuildOverviewss()
360 0 : if (VSIUnlink(osTmpMBTilesFilename.c_str()) != 0)
361 : {
362 0 : CPLError(CE_Warning, CPLE_FileIO, "Cannot delete %s",
363 : osTmpMBTilesFilename.c_str());
364 : }
365 0 : return nullptr;
366 : }
367 : }
368 7 : poMBTilesDS.reset();
369 7 : if (!OGRPMTilesConvertFromMBTiles(pszFilename,
370 : osTmpMBTilesFilename.c_str()))
371 : {
372 0 : if (VSIUnlink(osTmpMBTilesFilename.c_str()) != 0)
373 : {
374 0 : CPLError(CE_Warning, CPLE_FileIO, "Cannot delete %s",
375 : osTmpMBTilesFilename.c_str());
376 : }
377 0 : return nullptr;
378 : }
379 7 : if (pfnProgress)
380 7 : pfnProgress(1.0, "", pProgressData);
381 7 : VSIUnlink(osTmpMBTilesFilename.c_str());
382 : }
383 : }
384 44 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
385 22 : oOpenInfo.nOpenFlags = GDAL_OF_RASTER | GDAL_OF_VECTOR;
386 22 : return OGRPMTilesDriverOpen(&oOpenInfo);
387 : }
388 :
389 : /************************************************************************/
390 : /* RegisterOGRPMTiles() */
391 : /************************************************************************/
392 :
393 2126 : void RegisterOGRPMTiles()
394 : {
395 2126 : if (GDALGetDriverByName("PMTiles") != nullptr)
396 263 : return;
397 :
398 1863 : VSIPMTilesRegister();
399 :
400 1863 : GDALDriver *poDriver = new GDALDriver();
401 :
402 1863 : poDriver->SetDescription("PMTiles");
403 1863 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
404 1863 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
405 1863 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "ProtoMap Tiles");
406 1863 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "pmtiles");
407 1863 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
408 1863 : "drivers/vector/pmtiles.html");
409 :
410 1863 : poDriver->SetMetadataItem(
411 : GDAL_DMD_OPENOPTIONLIST,
412 : "<OpenOptionList>"
413 : " <Option name='ZOOM_LEVEL' type='integer' "
414 : "description='Zoom level of full resolution. If not specified, maximum "
415 : "non-empty zoom level'/>"
416 : " <Option name='CLIP' type='boolean' "
417 : "description='Whether to clip geometries to tile extent' "
418 : "default='YES'/>"
419 : " <Option name='ZOOM_LEVEL_AUTO' type='boolean' "
420 : "description='Whether to auto-select the zoom level for vector layers "
421 : "according to spatial filter extent. Only for display purpose' "
422 : "default='NO'/>"
423 : " <Option name='JSON_FIELD' type='boolean' "
424 : "description='For vector layers, "
425 : "whether to put all attributes as a serialized JSon dictionary'/>"
426 1863 : "</OpenOptionList>");
427 :
428 1863 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
429 :
430 1863 : poDriver->pfnOpen = OGRPMTilesDriverOpen;
431 1863 : poDriver->pfnIdentify = OGRPMTilesDriverIdentify;
432 1863 : poDriver->pfnCanVectorTranslateFrom =
433 : OGRPMTilesDriverCanVectorTranslateFrom;
434 1863 : poDriver->pfnVectorTranslateFrom = OGRPMTilesDriverVectorTranslateFrom;
435 :
436 1863 : poDriver->SetMetadataItem(
437 : GDAL_DMD_CREATIONOPTIONLIST,
438 : "<CreationOptionList>" MBTILES_RASTER_CREATION_OPTIONS
439 : #ifdef HAVE_MVT_WRITE_SUPPORT
440 : MVT_MBTILES_COMMON_DSCO
441 : #endif
442 1863 : "</CreationOptionList>");
443 :
444 : #ifdef HAVE_MVT_WRITE_SUPPORT
445 1863 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
446 1863 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
447 1863 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
448 1863 : "Integer Integer64 Real String");
449 1863 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
450 1863 : "Boolean Float32");
451 :
452 1863 : poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
453 :
454 1863 : poDriver->pfnCreate = OGRPMTilesDriverCreate;
455 : #endif
456 1863 : poDriver->pfnCreateCopy = OGRPMTilesDriverCreateCopy;
457 :
458 1863 : GetGDALDriverManager()->RegisterDriver(poDriver);
459 : }
|