Skip to content

Commit e9f5ab8

Browse files
committed
PMTiles CreateCopy(): use 'gdal raster tile' when possible
1 parent e235cf3 commit e9f5ab8

10 files changed

Lines changed: 797 additions & 86 deletions

File tree

apps/gdalalg_raster_tile.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ GDALRasterTileAlgorithm::GDALRasterTileAlgorithm(bool standaloneStep)
213213

214214
AddArg("min-zoom", 0, _("Minimum zoom level"), &m_minZoomLevel)
215215
.SetMinValueIncluded(0);
216+
217+
// Only used by PMTiles driver for now
218+
AddArg("min-zoom-single-tile", 0,
219+
_("Determine minimum zoom level to produce a single tile"),
220+
&m_minZoomLevelSingleTile)
221+
.SetHidden();
222+
216223
AddArg("max-zoom", 0, _("Maximum zoom level"), &m_maxZoomLevel)
217224
.SetMinValueIncluded(0);
218225

@@ -4824,8 +4831,6 @@ bool GDALRasterTileAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
48244831
m_maxZoomLevel--;
48254832
}
48264833
}
4827-
if (m_minZoomLevel < 0)
4828-
m_minZoomLevel = m_maxZoomLevel;
48294834

48304835
auto tileMatrix = tileMatrixList[m_maxZoomLevel];
48314836
int nMinTileX = 0;
@@ -4927,6 +4932,17 @@ bool GDALRasterTileAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
49274932
dstGT[4] = 0;
49284933
dstGT[5] = -tileMatrix.mResY;
49294934

4935+
if (m_minZoomLevelSingleTile)
4936+
{
4937+
const int nMaxDim = std::max(nXSize, nYSize);
4938+
const int nOvrCount = static_cast<int>(
4939+
std::ceil(std::max(0.0, std::log2(static_cast<double>(nMaxDim) /
4940+
tileMatrix.mTileWidth))));
4941+
m_minZoomLevel = std::max(0, m_maxZoomLevel - nOvrCount);
4942+
}
4943+
else if (m_minZoomLevel < 0)
4944+
m_minZoomLevel = m_maxZoomLevel;
4945+
49304946
/* -------------------------------------------------------------------- */
49314947
/* Setup warp options. */
49324948
/* -------------------------------------------------------------------- */

apps/gdalalg_raster_tile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class GDALRasterTileAlgorithm /* non final */
6767
std::string m_overviewResampling{};
6868
int m_minZoomLevel = -1;
6969
int m_maxZoomLevel = -1;
70+
bool m_minZoomLevelSingleTile = false;
7071
bool m_noIntersectionIsOK = false;
7172
int m_minTileX = -1;
7273
int m_minTileY = -1;

autotest/gdrivers/pmtiles.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,10 @@ def test_pmtiles_convert_from_pmtiles(tmp_path, tile_format):
206206

207207
@pytest.mark.require_driver("MBTILES")
208208
@pytest.mark.parametrize("tile_format", ["PNG", "JPEG", "WEBP"])
209-
def test_pmtiles_convert_from_non_pmtiles(tmp_vsimem, tile_format):
209+
@pytest.mark.parametrize("use_gdal_raster_tile", [True, False])
210+
def test_pmtiles_convert_from_non_pmtiles(
211+
tmp_vsimem, tile_format, use_gdal_raster_tile
212+
):
210213

211214
if gdal.GetDriverByName(tile_format) is None:
212215
pytest.skip(f"Driver {tile_format} is not available")
@@ -228,10 +231,13 @@ def test_pmtiles_convert_from_non_pmtiles(tmp_vsimem, tile_format):
228231
src_ds.GetRasterBand(1).Fill(255)
229232
src_ds.GetRasterBand(2).Fill(255)
230233
src_ds.GetRasterBand(3).Fill(255)
234+
options = {"TILE_FORMAT": tile_format}
235+
if not use_gdal_raster_tile:
236+
options["ZOOM_LEVEL_STRATEGY"] = "LOWER"
231237
gdal.alg.raster.convert(
232238
input=src_ds,
233239
output=tmp_vsimem / "out.pmtiles",
234-
creation_option={"TILE_FORMAT": tile_format},
240+
creation_option=options,
235241
)
236242

237243
ds = gdal.Open(tmp_vsimem / "out.pmtiles")
@@ -253,11 +259,12 @@ def test_pmtiles_convert_from_non_pmtiles(tmp_vsimem, tile_format):
253259

254260
@pytest.mark.require_driver("MBTILES")
255261
@pytest.mark.require_driver("PNG")
262+
@pytest.mark.parametrize("format", ["PNG", "PNG8"])
256263
@pytest.mark.parametrize(
257264
"size,expected_ovr_count", [(256, 0), (257, 1), (512, 1), (513, 2)]
258265
)
259266
def test_pmtiles_convert_from_non_pmtiles_auto_add_overviews(
260-
tmp_vsimem, size, expected_ovr_count
267+
tmp_path, format, size, expected_ovr_count
261268
):
262269

263270
src_ds = gdal.GetDriverByName("MEM").Create("", size, size, 3)
@@ -279,9 +286,9 @@ def test_pmtiles_convert_from_non_pmtiles_auto_add_overviews(
279286
src_ds.GetRasterBand(3).Fill(255)
280287
gdal.alg.raster.convert(
281288
input=src_ds,
282-
output=tmp_vsimem / "out.pmtiles",
283-
creation_option={"TILE_FORMAT": "PNG"},
289+
output=tmp_path / "out.pmtiles",
290+
creation_option={"TILE_FORMAT": format},
284291
)
285292

286-
ds = gdal.Open(tmp_vsimem / "out.pmtiles")
293+
ds = gdal.Open(tmp_path / "out.pmtiles")
287294
assert ds.GetRasterBand(1).GetOverviewCount() == expected_ovr_count

gcore/gdalalgorithm.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ static bool CheckCanSetDatasetObject(const GDALAlgorithmArg *arg)
464464

465465
bool GDALAlgorithmArg::Set(GDALDataset *ds)
466466
{
467-
if (m_decl.GetType() != GAAT_DATASET)
467+
if (m_decl.GetType() != GAAT_DATASET &&
468+
m_decl.GetType() != GAAT_DATASET_LIST)
468469
{
469470
CPLError(CE_Failure, CPLE_AppDefined,
470471
"Calling Set(GDALDataset*, bool) on argument '%s' of type %s "
@@ -475,8 +476,18 @@ bool GDALAlgorithmArg::Set(GDALDataset *ds)
475476
if (!CheckCanSetDatasetObject(this))
476477
return false;
477478
m_explicitlySet = true;
478-
auto &val = *std::get<GDALArgDatasetValue *>(m_value);
479-
val.Set(ds);
479+
if (m_decl.GetType() == GAAT_DATASET)
480+
{
481+
auto &val = *std::get<GDALArgDatasetValue *>(m_value);
482+
val.Set(ds);
483+
}
484+
else
485+
{
486+
CPLAssert(m_decl.GetType() == GAAT_DATASET_LIST);
487+
auto &val = *std::get<std::vector<GDALArgDatasetValue> *>(m_value);
488+
val.resize(1);
489+
val[0].Set(ds);
490+
}
480491
return RunAllActions();
481492
}
482493

ogr/ogrsf_frmts/pmtiles/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ add_gdal_driver(TARGET ogr_PMTiles
1414
gdal_standard_includes(ogr_PMTiles)
1515
target_include_directories(ogr_PMTiles PRIVATE ${GDAL_VECTOR_FORMAT_SOURCE_DIR}/mvt ${GDAL_RASTER_FORMAT_SOURCE_DIR}/mbtiles)
1616

17+
if (GDAL_ENABLE_ALGORITHMS)
18+
target_compile_definitions(ogr_PMTiles PRIVATE -DGDAL_ENABLE_ALGORITHMS)
19+
target_sources(ogr_PMTiles PRIVATE ogrpmtilesfromtileset.cpp)
20+
endif()
21+
1722
# Needed for Create() mode
1823
if (GDAL_USE_GEOS)
1924
target_compile_definitions(ogr_PMTiles PRIVATE -DHAVE_GEOS=1)

ogr/ogrsf_frmts/pmtiles/ogr_pmtiles.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "include_pmtiles.h"
2323

24+
#include <array>
2425
#include <limits>
2526
#include <set>
2627
#include <stack>
@@ -437,4 +438,27 @@ class OGRPMTilesWriterDataset final : public GDALDataset
437438

438439
#endif // HAVE_MVT_WRITE_SUPPORT
439440

441+
/************************************************************************/
442+
/* HashArray() */
443+
/************************************************************************/
444+
445+
// From https://codereview.stackexchange.com/questions/171999/specializing-stdhash-for-stdarray
446+
// We do not use std::hash<std::array<T, N>> as the name of the struct
447+
// because with gcc 5.4 we get the following error:
448+
// https://stackoverflow.com/questions/25594644/warning-specialization-of-template-in-different-namespace
449+
template <class T, size_t N> struct HashArray
450+
{
451+
CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
452+
size_t operator()(const std::array<T, N> &key) const
453+
{
454+
std::hash<T> hasher;
455+
size_t result = 0;
456+
for (size_t i = 0; i < N; ++i)
457+
{
458+
result = result * 31 + hasher(key[i]);
459+
}
460+
return result;
461+
}
462+
};
463+
440464
#endif // OGR_PMTILES_H_INCLUDED

0 commit comments

Comments
 (0)