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 40 : 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 40 : bool bZoomLevelFromSpatialFilter)
30 40 : : m_poDS(poDS), m_poFeatureDefn(new OGRFeatureDefn(pszLayerName)),
31 80 : m_bJsonField(bJsonField)
32 : {
33 40 : SetDescription(pszLayerName);
34 40 : m_poFeatureDefn->SetGeomType(eGeomType);
35 40 : OGRSpatialReference *poSRS = new OGRSpatialReference();
36 40 : poSRS->importFromEPSG(3857);
37 40 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
38 40 : poSRS->Release();
39 40 : m_poFeatureDefn->Reference();
40 :
41 40 : if (m_bJsonField)
42 : {
43 2 : OGRFieldDefn oFieldDefnId("mvt_id", OFTInteger64);
44 1 : m_poFeatureDefn->AddFieldDefn(&oFieldDefnId);
45 : }
46 : else
47 : {
48 39 : OGRMVTInitFields(m_poFeatureDefn, oFields, oAttributesFromTileStats);
49 : }
50 :
51 40 : m_sExtent.MinX = dfMinX;
52 40 : m_sExtent.MinY = dfMinY;
53 40 : m_sExtent.MaxX = dfMaxX;
54 40 : m_sExtent.MaxY = dfMaxY;
55 :
56 40 : m_nZoomLevel = nZoomLevel;
57 40 : m_bZoomLevelAuto = bZoomLevelFromSpatialFilter;
58 40 : 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 40 : 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 40 : if (m_bJsonField)
81 : {
82 2 : OGRFieldDefn oFieldDefn("json", OFTString);
83 1 : m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
84 : }
85 40 : }
86 :
87 : /************************************************************************/
88 : /* ~OGRPMTilesVectorLayer() */
89 : /************************************************************************/
90 :
91 80 : OGRPMTilesVectorLayer::~OGRPMTilesVectorLayer()
92 : {
93 40 : m_poFeatureDefn->Release();
94 80 : }
95 :
96 : /************************************************************************/
97 : /* ResetReading() */
98 : /************************************************************************/
99 :
100 401 : void OGRPMTilesVectorLayer::ResetReading()
101 : {
102 401 : m_poTileDS.reset();
103 401 : m_poTileLayer = nullptr;
104 401 : m_poTileIterator.reset();
105 401 : }
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 1919 : std::unique_ptr<OGRFeature> OGRPMTilesVectorLayer::GetNextSrcFeature()
334 : {
335 1919 : if (!m_poTileIterator)
336 : {
337 : int nMinTileX;
338 : int nMinTileY;
339 : int nMaxTileX;
340 : int nMaxTileY;
341 185 : 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 185 : if (m_nFilterMaxX < nMinTileX || m_nFilterMaxY < nMinTileY ||
347 185 : m_nFilterMinX > nMaxTileX || m_nFilterMinY > nMaxTileY)
348 : {
349 4 : return nullptr;
350 : }
351 :
352 181 : m_poTileIterator = std::make_unique<OGRPMTilesTileIterator>(
353 181 : m_poDS, m_nZoomLevel, m_nFilterMinX, m_nFilterMinY, m_nFilterMaxX,
354 181 : m_nFilterMaxY);
355 : }
356 :
357 1915 : OGRFeature *poTileFeat = nullptr;
358 3649 : if (!m_poTileLayer ||
359 1734 : (poTileFeat = m_poTileLayer->GetNextFeature()) == nullptr)
360 : {
361 784 : const char *const apszAllowedDrivers[] = {"MVT", nullptr};
362 :
363 : while (true)
364 : {
365 784 : const auto sTile = m_poTileIterator->GetNextTile();
366 784 : if (sTile.offset == 0)
367 : {
368 128 : return nullptr;
369 : }
370 :
371 656 : m_nX = sTile.x;
372 656 : m_nY = sTile.y;
373 :
374 : // Do the reset before the later 'm_osTileData = *posStr', otherwise
375 : // the destructor might read file content that is no longer valid
376 656 : m_poTileDS.reset();
377 :
378 656 : if (sTile.offset == m_nLastTileOffset)
379 : {
380 : // In case of run-length encoded tiles, we do not need to
381 : // re-read it from disk
382 : }
383 : else
384 : {
385 210 : m_nLastTileOffset = sTile.offset;
386 210 : CPLDebugOnly("PMTiles", "Opening tile X=%u, Y=%u, Z=%d",
387 : sTile.x, sTile.y, m_nZoomLevel);
388 :
389 : const auto *posStr =
390 210 : m_poDS->ReadTileData(sTile.offset, sTile.length);
391 210 : if (!posStr)
392 : {
393 0 : return nullptr;
394 : }
395 210 : m_osTileData = *posStr;
396 : }
397 :
398 : const std::string osTmpFilename = VSIMemGenerateHiddenFilename(
399 656 : CPLSPrintf("pmtiles_%u_%u.pbf", sTile.x, sTile.y));
400 656 : VSIFCloseL(VSIFileFromMemBuffer(
401 : osTmpFilename.c_str(),
402 656 : reinterpret_cast<GByte *>(&m_osTileData[0]),
403 656 : m_osTileData.size(), false));
404 :
405 656 : CPLStringList aosOpenOptions;
406 656 : aosOpenOptions.SetNameValue("X", CPLSPrintf("%u", sTile.x));
407 656 : aosOpenOptions.SetNameValue("Y", CPLSPrintf("%u", sTile.y));
408 656 : aosOpenOptions.SetNameValue("Z", CPLSPrintf("%d", m_nZoomLevel));
409 : aosOpenOptions.SetNameValue(
410 : "METADATA_FILE",
411 656 : m_bJsonField ? "" : m_poDS->GetMetadataFilename().c_str());
412 656 : if (!m_poDS->GetClipOpenOption().empty())
413 : {
414 : aosOpenOptions.SetNameValue(
415 2 : "CLIP", m_poDS->GetClipOpenOption().c_str());
416 : }
417 656 : m_poTileDS.reset(GDALDataset::Open(
418 1312 : ("MVT:" + osTmpFilename).c_str(),
419 : GDAL_OF_VECTOR | GDAL_OF_INTERNAL, apszAllowedDrivers,
420 656 : aosOpenOptions.List(), nullptr));
421 656 : if (m_poTileDS)
422 : {
423 656 : m_poTileDS->SetDescription(osTmpFilename.c_str());
424 656 : m_poTileDS->MarkSuppressOnClose();
425 656 : m_poTileLayer = m_poTileDS->GetLayerByName(GetDescription());
426 656 : if (m_poTileLayer)
427 : {
428 656 : poTileFeat = m_poTileLayer->GetNextFeature();
429 656 : if (poTileFeat)
430 : {
431 656 : break;
432 : }
433 : }
434 0 : m_poTileDS.reset();
435 0 : m_poTileLayer = nullptr;
436 : }
437 : else
438 : {
439 0 : VSIUnlink(osTmpFilename.c_str());
440 : }
441 0 : }
442 : }
443 :
444 1787 : return std::unique_ptr<OGRFeature>(poTileFeat);
445 : }
446 :
447 : /************************************************************************/
448 : /* CreateFeatureFrom() */
449 : /************************************************************************/
450 :
451 : std::unique_ptr<OGRFeature>
452 1808 : OGRPMTilesVectorLayer::CreateFeatureFrom(OGRFeature *poSrcFeature)
453 : {
454 : return std::unique_ptr<OGRFeature>(OGRMVTCreateFeatureFrom(
455 1808 : poSrcFeature, m_poFeatureDefn, m_bJsonField, GetSpatialRef()));
456 : }
457 :
458 : /************************************************************************/
459 : /* GetNextRawFeature() */
460 : /************************************************************************/
461 :
462 1918 : OGRFeature *OGRPMTilesVectorLayer::GetNextRawFeature()
463 : {
464 3836 : auto poSrcFeat = GetNextSrcFeature();
465 1918 : if (poSrcFeat == nullptr)
466 132 : return nullptr;
467 :
468 1786 : const GIntBig nFIDBase =
469 1786 : (static_cast<GIntBig>(m_nY) << m_nZoomLevel) | m_nX;
470 3572 : auto poFeature = CreateFeatureFrom(poSrcFeat.get());
471 1786 : poFeature->SetFID((poSrcFeat->GetFID() << (2 * m_nZoomLevel)) | nFIDBase);
472 :
473 1786 : return poFeature.release();
474 : }
475 :
476 : /************************************************************************/
477 : /* TestCapability() */
478 : /************************************************************************/
479 :
480 144 : int OGRPMTilesVectorLayer::TestCapability(const char *pszCap) const
481 : {
482 144 : if (EQUAL(pszCap, OLCStringsAsUTF8) ||
483 96 : EQUAL(pszCap, OLCFastSpatialFilter) || EQUAL(pszCap, OLCFastGetExtent))
484 : {
485 56 : return TRUE;
486 : }
487 :
488 88 : if (EQUAL(pszCap, OLCFastFeatureCount))
489 0 : return m_nFeatureCount >= 0 && !m_poFilterGeom && !m_poAttrQuery;
490 :
491 88 : return FALSE;
492 : }
493 :
494 : /************************************************************************/
495 : /* IGetExtent() */
496 : /************************************************************************/
497 :
498 23 : OGRErr OGRPMTilesVectorLayer::IGetExtent(int /* iGeomField */,
499 : OGREnvelope *psExtent, bool)
500 : {
501 23 : *psExtent = m_sExtent;
502 23 : return OGRERR_NONE;
503 : }
504 :
505 : /************************************************************************/
506 : /* ExtentToTileExtent() */
507 : /************************************************************************/
508 :
509 219 : void OGRPMTilesVectorLayer::ExtentToTileExtent(const OGREnvelope &sEnvelope,
510 : int &nTileMinX, int &nTileMinY,
511 : int &nTileMaxX,
512 : int &nTileMaxY) const
513 : {
514 219 : const double dfTileDim = 2 * MAX_GM / (1 << m_nZoomLevel);
515 219 : constexpr double EPS = 1e-5;
516 438 : nTileMinX = std::max(0, static_cast<int>(floor(
517 219 : (sEnvelope.MinX + MAX_GM) / dfTileDim + EPS)));
518 : // PMTiles and MVT uses a Y=MAX_GM as the y=0 tile
519 438 : nTileMinY = std::max(0, static_cast<int>(floor(
520 219 : (MAX_GM - sEnvelope.MaxY) / dfTileDim + EPS)));
521 219 : nTileMaxX = std::min(
522 438 : static_cast<int>(floor((sEnvelope.MaxX + MAX_GM) / dfTileDim + EPS)),
523 219 : (1 << m_nZoomLevel) - 1);
524 219 : nTileMaxY = std::min(
525 438 : static_cast<int>(floor((MAX_GM - sEnvelope.MinY) / dfTileDim + EPS)),
526 219 : (1 << m_nZoomLevel) - 1);
527 219 : }
528 :
529 : /************************************************************************/
530 : /* ISetSpatialFilter() */
531 : /************************************************************************/
532 :
533 150 : OGRErr OGRPMTilesVectorLayer::ISetSpatialFilter(int iGeomField,
534 : const OGRGeometry *poGeomIn)
535 : {
536 150 : OGRLayer::ISetSpatialFilter(iGeomField, poGeomIn);
537 :
538 150 : if (m_poFilterGeom != nullptr && m_sFilterEnvelope.MinX <= -MAX_GM &&
539 12 : m_sFilterEnvelope.MinY <= -MAX_GM && m_sFilterEnvelope.MaxX >= MAX_GM &&
540 8 : m_sFilterEnvelope.MaxY >= MAX_GM)
541 : {
542 8 : if (m_bZoomLevelAuto)
543 : {
544 0 : m_nZoomLevel = m_poDS->GetMinZoomLevel();
545 : }
546 8 : m_nFilterMinX = 0;
547 8 : m_nFilterMinY = 0;
548 8 : m_nFilterMaxX = (1 << m_nZoomLevel) - 1;
549 8 : m_nFilterMaxY = (1 << m_nZoomLevel) - 1;
550 : }
551 142 : else if (m_poFilterGeom != nullptr &&
552 34 : m_sFilterEnvelope.MinX >= -10 * MAX_GM &&
553 34 : m_sFilterEnvelope.MinY >= -10 * MAX_GM &&
554 34 : m_sFilterEnvelope.MaxX <= 10 * MAX_GM &&
555 34 : m_sFilterEnvelope.MaxY <= 10 * MAX_GM)
556 : {
557 34 : if (m_bZoomLevelAuto)
558 : {
559 : double dfExtent =
560 4 : std::min(m_sFilterEnvelope.MaxX - m_sFilterEnvelope.MinX,
561 2 : m_sFilterEnvelope.MaxY - m_sFilterEnvelope.MinY);
562 2 : m_nZoomLevel = std::max(
563 2 : m_poDS->GetMinZoomLevel(),
564 4 : std::min(static_cast<int>(0.5 + log(2 * MAX_GM / dfExtent) /
565 : log(2.0)),
566 4 : m_poDS->GetMaxZoomLevel()));
567 2 : CPLDebug("PMTiles", "Zoom level = %d", m_nZoomLevel);
568 : }
569 34 : ExtentToTileExtent(m_sFilterEnvelope, m_nFilterMinX, m_nFilterMinY,
570 34 : m_nFilterMaxX, m_nFilterMaxY);
571 : }
572 : else
573 : {
574 108 : if (m_bZoomLevelAuto)
575 : {
576 2 : m_nZoomLevel = m_poDS->GetMaxZoomLevel();
577 : }
578 108 : m_nFilterMinX = 0;
579 108 : m_nFilterMinY = 0;
580 108 : m_nFilterMaxX = (1 << m_nZoomLevel) - 1;
581 108 : m_nFilterMaxY = (1 << m_nZoomLevel) - 1;
582 : }
583 150 : return OGRERR_NONE;
584 : }
|