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 "mvtutils.h"
16 :
17 : #include <algorithm>
18 : #include <time.h>
19 :
20 : /************************************************************************/
21 : /* OGRPMTilesVectorLayer() */
22 : /************************************************************************/
23 :
24 41 : OGRPMTilesVectorLayer::OGRPMTilesVectorLayer(
25 : OGRPMTilesDataset *poDS, const char *pszLayerName,
26 : const CPLJSONObject &oFields, const CPLJSONArray &oAttributesFromTileStats,
27 : bool bJsonField, double dfMinX, double dfMinY, double dfMaxX, double dfMaxY,
28 : OGRwkbGeometryType eGeomType, int nZoomLevel,
29 41 : bool bZoomLevelFromSpatialFilter)
30 41 : : m_poDS(poDS), m_poFeatureDefn(new OGRFeatureDefn(pszLayerName)),
31 82 : m_bJsonField(bJsonField)
32 : {
33 41 : SetDescription(pszLayerName);
34 41 : m_poFeatureDefn->SetGeomType(eGeomType);
35 41 : OGRSpatialReference *poSRS = new OGRSpatialReference();
36 41 : poSRS->importFromEPSG(3857);
37 41 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
38 41 : poSRS->Release();
39 41 : m_poFeatureDefn->Reference();
40 :
41 41 : if (m_bJsonField)
42 : {
43 2 : OGRFieldDefn oFieldDefnId("mvt_id", OFTInteger64);
44 1 : m_poFeatureDefn->AddFieldDefn(&oFieldDefnId);
45 : }
46 : else
47 : {
48 40 : OGRMVTInitFields(m_poFeatureDefn, oFields, oAttributesFromTileStats);
49 : }
50 :
51 41 : m_sExtent.MinX = dfMinX;
52 41 : m_sExtent.MinY = dfMinY;
53 41 : m_sExtent.MaxX = dfMaxX;
54 41 : m_sExtent.MaxY = dfMaxY;
55 :
56 41 : m_nZoomLevel = nZoomLevel;
57 41 : m_bZoomLevelAuto = bZoomLevelFromSpatialFilter;
58 41 : OGRPMTilesVectorLayer::SetSpatialFilter(nullptr);
59 :
60 : // If the metadata contains an empty fields object, this may be a sign
61 : // that it doesn't know the schema. In that case check if a tile has
62 : // attributes, and in that case create a json field.
63 41 : if (!m_bJsonField && oFields.IsValid() && oFields.GetChildren().empty())
64 : {
65 1 : m_bJsonField = true;
66 2 : auto poSrcFeature = GetNextSrcFeature();
67 1 : m_bJsonField = false;
68 :
69 1 : if (poSrcFeature)
70 : {
71 : // There is at least the mvt_id field
72 1 : if (poSrcFeature->GetFieldCount() > 1)
73 : {
74 0 : m_bJsonField = true;
75 : }
76 : }
77 1 : OGRPMTilesVectorLayer::ResetReading();
78 : }
79 :
80 41 : if (m_bJsonField)
81 : {
82 2 : OGRFieldDefn oFieldDefn("json", OFTString);
83 1 : m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
84 : }
85 41 : }
86 :
87 : /************************************************************************/
88 : /* ~OGRPMTilesVectorLayer() */
89 : /************************************************************************/
90 :
91 82 : OGRPMTilesVectorLayer::~OGRPMTilesVectorLayer()
92 : {
93 41 : m_poFeatureDefn->Release();
94 82 : }
95 :
96 : /************************************************************************/
97 : /* ResetReading() */
98 : /************************************************************************/
99 :
100 393 : void OGRPMTilesVectorLayer::ResetReading()
101 : {
102 393 : m_poTileDS.reset();
103 393 : m_poTileLayer = nullptr;
104 393 : m_poTileIterator.reset();
105 393 : }
106 :
107 : /************************************************************************/
108 : /* GuessGeometryType() */
109 : /************************************************************************/
110 :
111 : /* static */
112 2 : OGRwkbGeometryType OGRPMTilesVectorLayer::GuessGeometryType(
113 : OGRPMTilesDataset *poDS, const char *pszLayerName, int nZoomLevel)
114 : {
115 4 : OGRPMTilesTileIterator oIterator(poDS, nZoomLevel);
116 :
117 2 : const char *const apszAllowedDrivers[] = {"MVT", nullptr};
118 4 : CPLStringList aosOpenOptions;
119 : aosOpenOptions.SetNameValue("METADATA_FILE",
120 2 : poDS->GetMetadataFilename().c_str());
121 4 : std::string osTileData;
122 2 : bool bFirst = true;
123 2 : OGRwkbGeometryType eGeomType = wkbUnknown;
124 : time_t nStart;
125 2 : time(&nStart);
126 : while (true)
127 : {
128 5 : uint32_t nRunLength = 0;
129 5 : const auto sTile = oIterator.GetNextTile(&nRunLength);
130 5 : if (sTile.offset == 0)
131 : {
132 2 : break;
133 : }
134 :
135 3 : const auto *posStr = poDS->ReadTileData(sTile.offset, sTile.length);
136 3 : if (!posStr)
137 : {
138 0 : continue;
139 : }
140 3 : osTileData = *posStr;
141 :
142 : const std::string osTmpFilename = VSIMemGenerateHiddenFilename(
143 3 : CPLSPrintf("pmtiles_%u_%u.pbf", sTile.x, sTile.y));
144 3 : VSIFCloseL(VSIFileFromMemBuffer(
145 3 : osTmpFilename.c_str(), reinterpret_cast<GByte *>(&osTileData[0]),
146 3 : osTileData.size(), false));
147 :
148 : auto poTileDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
149 3 : ("MVT:" + osTmpFilename).c_str(), GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
150 6 : apszAllowedDrivers, aosOpenOptions.List(), nullptr));
151 3 : if (poTileDS)
152 : {
153 3 : auto poTileLayer = poTileDS->GetLayerByName(pszLayerName);
154 3 : if (poTileLayer)
155 : {
156 3 : if (bFirst)
157 : {
158 2 : eGeomType = poTileLayer->GetGeomType();
159 2 : if (eGeomType != wkbUnknown)
160 2 : bFirst = false;
161 : }
162 1 : else if (eGeomType != poTileLayer->GetGeomType())
163 : {
164 0 : VSIUnlink(osTmpFilename.c_str());
165 0 : return wkbUnknown;
166 : }
167 3 : if (nRunLength > 1)
168 1 : oIterator.SkipRunLength();
169 : }
170 : }
171 3 : VSIUnlink(osTmpFilename.c_str());
172 :
173 : // Browse through tiles no longer than 1 sec
174 : time_t nNow;
175 3 : time(&nNow);
176 3 : if (nNow - nStart > 1)
177 0 : break;
178 3 : }
179 :
180 2 : return eGeomType;
181 : }
182 :
183 : /************************************************************************/
184 : /* GetTotalFeatureCount() */
185 : /************************************************************************/
186 :
187 8 : GIntBig OGRPMTilesVectorLayer::GetTotalFeatureCount() const
188 : {
189 16 : OGRPMTilesTileIterator oIterator(m_poDS, m_nZoomLevel);
190 :
191 8 : GIntBig nFeatureCount = 0;
192 8 : const char *const apszAllowedDrivers[] = {"MVT", nullptr};
193 16 : CPLStringList aosOpenOptions;
194 : aosOpenOptions.SetNameValue("METADATA_FILE",
195 8 : m_poDS->GetMetadataFilename().c_str());
196 8 : std::string osTileData;
197 : while (true)
198 : {
199 23 : uint32_t nRunLength = 0;
200 23 : const auto sTile = oIterator.GetNextTile(&nRunLength);
201 23 : if (sTile.offset == 0)
202 : {
203 8 : break;
204 : }
205 :
206 15 : const auto *posStr = m_poDS->ReadTileData(sTile.offset, sTile.length);
207 15 : if (!posStr)
208 : {
209 0 : continue;
210 : }
211 15 : osTileData = *posStr;
212 :
213 : const std::string osTmpFilename = VSIMemGenerateHiddenFilename(
214 30 : CPLSPrintf("pmtiles_%u_%u_getfeaturecount.pbf", sTile.x, sTile.y));
215 15 : VSIFCloseL(VSIFileFromMemBuffer(
216 15 : osTmpFilename.c_str(), reinterpret_cast<GByte *>(&osTileData[0]),
217 15 : osTileData.size(), false));
218 :
219 : auto poTileDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
220 15 : ("MVT:" + osTmpFilename).c_str(), GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
221 45 : apszAllowedDrivers, aosOpenOptions.List(), nullptr));
222 15 : if (poTileDS)
223 : {
224 15 : auto poTileLayer = poTileDS->GetLayerByName(GetDescription());
225 15 : if (poTileLayer)
226 : {
227 : const GIntBig nTileFeatureCount =
228 15 : poTileLayer->GetFeatureCount();
229 15 : nFeatureCount += nRunLength * nTileFeatureCount;
230 15 : if (nRunLength > 1)
231 1 : oIterator.SkipRunLength();
232 : }
233 : }
234 15 : VSIUnlink(osTmpFilename.c_str());
235 15 : }
236 :
237 16 : return nFeatureCount;
238 : }
239 :
240 : /************************************************************************/
241 : /* GetFeatureCount() */
242 : /************************************************************************/
243 :
244 70 : GIntBig OGRPMTilesVectorLayer::GetFeatureCount(int bForce)
245 : {
246 70 : if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
247 : {
248 32 : if (m_nFeatureCount < 0)
249 : {
250 8 : m_nFeatureCount = GetTotalFeatureCount();
251 : }
252 32 : return m_nFeatureCount;
253 : }
254 38 : return OGRLayer::GetFeatureCount(bForce);
255 : }
256 :
257 : /************************************************************************/
258 : /* GetFeature() */
259 : /************************************************************************/
260 :
261 35 : OGRFeature *OGRPMTilesVectorLayer::GetFeature(GIntBig nFID)
262 : {
263 35 : if (nFID < 0)
264 4 : return nullptr;
265 31 : const int nZ = m_nZoomLevel;
266 31 : const int nX = static_cast<int>(nFID & ((1 << nZ) - 1));
267 31 : const int nY = static_cast<int>((nFID >> nZ) & ((1 << nZ) - 1));
268 31 : const GIntBig nTileFID = nFID >> (2 * nZ);
269 :
270 62 : OGRPMTilesTileIterator oIterator(m_poDS, m_nZoomLevel, nX, nY, nX, nY);
271 31 : const auto sTile = oIterator.GetNextTile();
272 31 : if (sTile.offset == 0)
273 : {
274 8 : return nullptr;
275 : }
276 23 : CPLAssert(sTile.z == m_nZoomLevel);
277 23 : CPLAssert(sTile.x == static_cast<uint32_t>(nX));
278 23 : CPLAssert(sTile.y == static_cast<uint32_t>(nY));
279 :
280 23 : const auto *posStr = m_poDS->ReadTileData(sTile.offset, sTile.length);
281 23 : if (!posStr)
282 : {
283 0 : return nullptr;
284 : }
285 46 : std::string osTileData = *posStr;
286 :
287 : const std::string osTmpFilename = VSIMemGenerateHiddenFilename(
288 46 : CPLSPrintf("pmtiles_getfeature_%u_%u.pbf", sTile.x, sTile.y));
289 23 : VSIFCloseL(VSIFileFromMemBuffer(osTmpFilename.c_str(),
290 23 : reinterpret_cast<GByte *>(&osTileData[0]),
291 23 : osTileData.size(), false));
292 :
293 23 : const char *const apszAllowedDrivers[] = {"MVT", nullptr};
294 46 : CPLStringList aosOpenOptions;
295 23 : aosOpenOptions.SetNameValue("X", CPLSPrintf("%u", sTile.x));
296 23 : aosOpenOptions.SetNameValue("Y", CPLSPrintf("%u", sTile.y));
297 23 : aosOpenOptions.SetNameValue("Z", CPLSPrintf("%d", m_nZoomLevel));
298 : aosOpenOptions.SetNameValue(
299 : "METADATA_FILE",
300 23 : m_bJsonField ? "" : m_poDS->GetMetadataFilename().c_str());
301 23 : if (!m_poDS->GetClipOpenOption().empty())
302 : {
303 : aosOpenOptions.SetNameValue("CLIP",
304 0 : m_poDS->GetClipOpenOption().c_str());
305 : }
306 : auto poTileDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
307 23 : ("MVT:" + osTmpFilename).c_str(), GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
308 69 : apszAllowedDrivers, aosOpenOptions.List(), nullptr));
309 23 : std::unique_ptr<OGRFeature> poFeature;
310 23 : if (poTileDS)
311 : {
312 23 : auto poTileLayer = poTileDS->GetLayerByName(GetDescription());
313 23 : if (poTileLayer)
314 : {
315 : auto poUnderlyingFeature =
316 46 : std::unique_ptr<OGRFeature>(poTileLayer->GetFeature(nTileFID));
317 23 : if (poUnderlyingFeature)
318 : {
319 22 : poFeature = CreateFeatureFrom(poUnderlyingFeature.get());
320 22 : poFeature->SetFID(nFID);
321 : }
322 : }
323 : }
324 23 : VSIUnlink(osTmpFilename.c_str());
325 :
326 23 : return poFeature.release();
327 : }
328 :
329 : /************************************************************************/
330 : /* GetNextSrcFeature() */
331 : /************************************************************************/
332 :
333 1849 : std::unique_ptr<OGRFeature> OGRPMTilesVectorLayer::GetNextSrcFeature()
334 : {
335 1849 : if (!m_poTileIterator)
336 : {
337 : int nMinTileX;
338 : int nMinTileY;
339 : int nMaxTileX;
340 : int nMaxTileY;
341 177 : ExtentToTileExtent(m_sExtent, nMinTileX, nMinTileY, nMaxTileX,
342 : nMaxTileY);
343 :
344 : // Optimization: if the spatial filter is totally out of the extent,
345 : // exit early
346 177 : if (m_nFilterMaxX < nMinTileX || m_nFilterMaxY < nMinTileY ||
347 177 : m_nFilterMinX > nMaxTileX || m_nFilterMinY > nMaxTileY)
348 : {
349 4 : return nullptr;
350 : }
351 :
352 173 : m_poTileIterator = std::make_unique<OGRPMTilesTileIterator>(
353 173 : m_poDS, m_nZoomLevel, m_nFilterMinX, m_nFilterMinY, m_nFilterMaxX,
354 173 : m_nFilterMaxY);
355 : }
356 :
357 1845 : OGRFeature *poTileFeat = nullptr;
358 3517 : if (!m_poTileLayer ||
359 1672 : (poTileFeat = m_poTileLayer->GetNextFeature()) == nullptr)
360 : {
361 758 : const char *const apszAllowedDrivers[] = {"MVT", nullptr};
362 :
363 : while (true)
364 : {
365 758 : const auto sTile = m_poTileIterator->GetNextTile();
366 758 : if (sTile.offset == 0)
367 : {
368 116 : return nullptr;
369 : }
370 :
371 642 : m_nX = sTile.x;
372 642 : m_nY = sTile.y;
373 :
374 642 : if (sTile.offset == m_nLastTileOffset)
375 : {
376 : // In case of run-length encoded tiles, we do not need to
377 : // re-read it from disk
378 : }
379 : else
380 : {
381 202 : m_nLastTileOffset = sTile.offset;
382 202 : CPLDebugOnly("PMTiles", "Opening tile X=%u, Y=%u, Z=%d",
383 : sTile.x, sTile.y, m_nZoomLevel);
384 :
385 : const auto *posStr =
386 202 : m_poDS->ReadTileData(sTile.offset, sTile.length);
387 202 : if (!posStr)
388 : {
389 0 : return nullptr;
390 : }
391 202 : m_osTileData = *posStr;
392 : }
393 :
394 642 : m_poTileDS.reset();
395 : const std::string osTmpFilename = VSIMemGenerateHiddenFilename(
396 642 : CPLSPrintf("pmtiles_%u_%u.pbf", sTile.x, sTile.y));
397 642 : VSIFCloseL(VSIFileFromMemBuffer(
398 : osTmpFilename.c_str(),
399 642 : reinterpret_cast<GByte *>(&m_osTileData[0]),
400 642 : m_osTileData.size(), false));
401 :
402 642 : CPLStringList aosOpenOptions;
403 642 : aosOpenOptions.SetNameValue("X", CPLSPrintf("%u", sTile.x));
404 642 : aosOpenOptions.SetNameValue("Y", CPLSPrintf("%u", sTile.y));
405 642 : aosOpenOptions.SetNameValue("Z", CPLSPrintf("%d", m_nZoomLevel));
406 : aosOpenOptions.SetNameValue(
407 : "METADATA_FILE",
408 642 : m_bJsonField ? "" : m_poDS->GetMetadataFilename().c_str());
409 642 : if (!m_poDS->GetClipOpenOption().empty())
410 : {
411 : aosOpenOptions.SetNameValue(
412 2 : "CLIP", m_poDS->GetClipOpenOption().c_str());
413 : }
414 642 : m_poTileDS.reset(GDALDataset::Open(
415 1284 : ("MVT:" + osTmpFilename).c_str(),
416 : GDAL_OF_VECTOR | GDAL_OF_INTERNAL, apszAllowedDrivers,
417 642 : aosOpenOptions.List(), nullptr));
418 642 : if (m_poTileDS)
419 : {
420 642 : m_poTileDS->SetDescription(osTmpFilename.c_str());
421 642 : m_poTileDS->MarkSuppressOnClose();
422 642 : m_poTileLayer = m_poTileDS->GetLayerByName(GetDescription());
423 642 : if (m_poTileLayer)
424 : {
425 642 : poTileFeat = m_poTileLayer->GetNextFeature();
426 642 : if (poTileFeat)
427 : {
428 642 : break;
429 : }
430 : }
431 0 : m_poTileDS.reset();
432 0 : m_poTileLayer = nullptr;
433 : }
434 : else
435 : {
436 0 : VSIUnlink(osTmpFilename.c_str());
437 : }
438 0 : }
439 : }
440 :
441 1729 : return std::unique_ptr<OGRFeature>(poTileFeat);
442 : }
443 :
444 : /************************************************************************/
445 : /* CreateFeatureFrom() */
446 : /************************************************************************/
447 :
448 : std::unique_ptr<OGRFeature>
449 1750 : OGRPMTilesVectorLayer::CreateFeatureFrom(OGRFeature *poSrcFeature)
450 : {
451 : return std::unique_ptr<OGRFeature>(OGRMVTCreateFeatureFrom(
452 1750 : poSrcFeature, m_poFeatureDefn, m_bJsonField, GetSpatialRef()));
453 : }
454 :
455 : /************************************************************************/
456 : /* GetNextRawFeature() */
457 : /************************************************************************/
458 :
459 1848 : OGRFeature *OGRPMTilesVectorLayer::GetNextRawFeature()
460 : {
461 3696 : auto poSrcFeat = GetNextSrcFeature();
462 1848 : if (poSrcFeat == nullptr)
463 120 : return nullptr;
464 :
465 1728 : const GIntBig nFIDBase =
466 1728 : (static_cast<GIntBig>(m_nY) << m_nZoomLevel) | m_nX;
467 3456 : auto poFeature = CreateFeatureFrom(poSrcFeat.get());
468 1728 : poFeature->SetFID((poSrcFeat->GetFID() << (2 * m_nZoomLevel)) | nFIDBase);
469 :
470 1728 : return poFeature.release();
471 : }
472 :
473 : /************************************************************************/
474 : /* TestCapability() */
475 : /************************************************************************/
476 :
477 140 : int OGRPMTilesVectorLayer::TestCapability(const char *pszCap)
478 : {
479 140 : if (EQUAL(pszCap, OLCStringsAsUTF8) ||
480 92 : EQUAL(pszCap, OLCFastSpatialFilter) || EQUAL(pszCap, OLCFastGetExtent))
481 : {
482 56 : return TRUE;
483 : }
484 :
485 84 : if (EQUAL(pszCap, OLCFastFeatureCount))
486 0 : return m_nFeatureCount >= 0 && !m_poFilterGeom && !m_poAttrQuery;
487 :
488 84 : return FALSE;
489 : }
490 :
491 : /************************************************************************/
492 : /* GetExtent() */
493 : /************************************************************************/
494 :
495 23 : OGRErr OGRPMTilesVectorLayer::GetExtent(OGREnvelope *psExtent, int)
496 : {
497 23 : *psExtent = m_sExtent;
498 23 : return OGRERR_NONE;
499 : }
500 :
501 : /************************************************************************/
502 : /* ExtentToTileExtent() */
503 : /************************************************************************/
504 :
505 211 : void OGRPMTilesVectorLayer::ExtentToTileExtent(const OGREnvelope &sEnvelope,
506 : int &nTileMinX, int &nTileMinY,
507 : int &nTileMaxX,
508 : int &nTileMaxY) const
509 : {
510 211 : const double dfTileDim = 2 * MAX_GM / (1 << m_nZoomLevel);
511 211 : constexpr double EPS = 1e-5;
512 422 : nTileMinX = std::max(0, static_cast<int>(floor(
513 211 : (sEnvelope.MinX + MAX_GM) / dfTileDim + EPS)));
514 : // PMTiles and MVT uses a Y=MAX_GM as the y=0 tile
515 422 : nTileMinY = std::max(0, static_cast<int>(floor(
516 211 : (MAX_GM - sEnvelope.MaxY) / dfTileDim + EPS)));
517 211 : nTileMaxX = std::min(
518 422 : static_cast<int>(floor((sEnvelope.MaxX + MAX_GM) / dfTileDim + EPS)),
519 211 : (1 << m_nZoomLevel) - 1);
520 211 : nTileMaxY = std::min(
521 422 : static_cast<int>(floor((MAX_GM - sEnvelope.MinY) / dfTileDim + EPS)),
522 211 : (1 << m_nZoomLevel) - 1);
523 211 : }
524 :
525 : /************************************************************************/
526 : /* SetSpatialFilter() */
527 : /************************************************************************/
528 :
529 151 : void OGRPMTilesVectorLayer::SetSpatialFilter(OGRGeometry *poGeomIn)
530 : {
531 151 : OGRLayer::SetSpatialFilter(poGeomIn);
532 :
533 151 : if (m_poFilterGeom != nullptr && m_sFilterEnvelope.MinX <= -MAX_GM &&
534 12 : m_sFilterEnvelope.MinY <= -MAX_GM && m_sFilterEnvelope.MaxX >= MAX_GM &&
535 8 : m_sFilterEnvelope.MaxY >= MAX_GM)
536 : {
537 8 : if (m_bZoomLevelAuto)
538 : {
539 0 : m_nZoomLevel = m_poDS->GetMinZoomLevel();
540 : }
541 8 : m_nFilterMinX = 0;
542 8 : m_nFilterMinY = 0;
543 8 : m_nFilterMaxX = (1 << m_nZoomLevel) - 1;
544 8 : m_nFilterMaxY = (1 << m_nZoomLevel) - 1;
545 : }
546 143 : else if (m_poFilterGeom != nullptr &&
547 34 : m_sFilterEnvelope.MinX >= -10 * MAX_GM &&
548 34 : m_sFilterEnvelope.MinY >= -10 * MAX_GM &&
549 34 : m_sFilterEnvelope.MaxX <= 10 * MAX_GM &&
550 34 : m_sFilterEnvelope.MaxY <= 10 * MAX_GM)
551 : {
552 34 : if (m_bZoomLevelAuto)
553 : {
554 : double dfExtent =
555 4 : std::min(m_sFilterEnvelope.MaxX - m_sFilterEnvelope.MinX,
556 2 : m_sFilterEnvelope.MaxY - m_sFilterEnvelope.MinY);
557 2 : m_nZoomLevel = std::max(
558 2 : m_poDS->GetMinZoomLevel(),
559 4 : std::min(static_cast<int>(0.5 + log(2 * MAX_GM / dfExtent) /
560 : log(2.0)),
561 4 : m_poDS->GetMaxZoomLevel()));
562 2 : CPLDebug("PMTiles", "Zoom level = %d", m_nZoomLevel);
563 : }
564 34 : ExtentToTileExtent(m_sFilterEnvelope, m_nFilterMinX, m_nFilterMinY,
565 34 : m_nFilterMaxX, m_nFilterMaxY);
566 : }
567 : else
568 : {
569 109 : if (m_bZoomLevelAuto)
570 : {
571 2 : m_nZoomLevel = m_poDS->GetMaxZoomLevel();
572 : }
573 109 : m_nFilterMinX = 0;
574 109 : m_nFilterMinY = 0;
575 109 : m_nFilterMaxX = (1 << m_nZoomLevel) - 1;
576 109 : m_nFilterMaxY = (1 << m_nZoomLevel) - 1;
577 : }
578 151 : }
|