Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL MBTiles driver
4 : * Purpose: Implement GDAL MBTiles support using OGR SQLite driver
5 : * Author: Even Rouault, Even Rouault <even.rouault at spatialys.com>
6 : *
7 : **********************************************************************
8 : * Copyright (c) 2012-2016, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #if defined(HAVE_SQLITE) && defined(HAVE_GEOS)
14 : // Needed by mvtutils.h
15 : #define HAVE_MVT_WRITE_SUPPORT
16 : #endif
17 :
18 : #include "gdal_frmts.h"
19 : #include "gdal_pam.h"
20 : #include "ogr_api.h"
21 : #include "cpl_json.h"
22 : #include "cpl_vsil_curl_priv.h"
23 : #include "gpkgmbtilescommon.h"
24 : #include "gdal_utils.h"
25 : #include "gdalwarper.h"
26 : #include "mvtutils.h"
27 : #include "ogrsqlitevfs.h"
28 : #include "ogrsqlitebase.h"
29 :
30 : #include "zlib.h"
31 : #include "ogrlibjsonutils.h"
32 : #include "mbtiles.h"
33 :
34 : #include <math.h>
35 : #include <algorithm>
36 : #include <memory>
37 : #include <vector>
38 :
39 : static const char *const apszAllowedDrivers[] = {"JPEG", "PNG", "WEBP",
40 : nullptr};
41 :
42 : #define SRS_EPSG_3857 \
43 : "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS " \
44 : "84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS " \
45 : "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[" \
46 : "\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]]" \
47 : ",UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]," \
48 : "AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER[" \
49 : "\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_" \
50 : "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[" \
51 : "\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[" \
52 : "\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 " \
53 : "+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext " \
54 : "+no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]"
55 :
56 : #define SPHERICAL_RADIUS 6378137.0
57 : #define MAX_GM (SPHERICAL_RADIUS * M_PI) // 20037508.342789244
58 :
59 : // TileMatrixSet origin : caution this is in GeoPackage / WMTS convention ! That
60 : // is upper-left corner
61 : #define TMS_ORIGIN_X -MAX_GM
62 : #define TMS_ORIGIN_Y MAX_GM
63 :
64 : #if defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) || \
65 : defined(ALLOW_FORMAT_DUMPS)
66 : // Enable accepting a SQL dump (starting with a "-- SQL MBTILES" line) as a
67 : // valid file. This makes fuzzer life easier
68 : #define ENABLE_SQL_SQLITE_FORMAT
69 : #endif
70 :
71 : constexpr int knDEFAULT_BLOCK_SIZE = 256;
72 :
73 : class MBTilesBand;
74 :
75 : /************************************************************************/
76 : /* MBTILESOpenSQLiteDB() */
77 : /************************************************************************/
78 :
79 81 : static GDALDatasetH MBTILESOpenSQLiteDB(const char *pszFilename,
80 : GDALAccess eAccess)
81 : {
82 81 : const char *l_apszAllowedDrivers[] = {"SQLITE", nullptr};
83 162 : return GDALOpenEx((CPLString("SQLITE:") + pszFilename).c_str(),
84 : GDAL_OF_VECTOR | GDAL_OF_INTERNAL |
85 : ((eAccess == GA_Update) ? GDAL_OF_UPDATE : 0),
86 162 : l_apszAllowedDrivers, nullptr, nullptr);
87 : }
88 :
89 : /************************************************************************/
90 : /* ==================================================================== */
91 : /* MBTilesDataset */
92 : /* ==================================================================== */
93 : /************************************************************************/
94 :
95 : class MBTilesDataset final : public GDALPamDataset,
96 : public GDALGPKGMBTilesLikePseudoDataset
97 : {
98 : friend class MBTilesBand;
99 : friend class MBTilesVectorLayer;
100 :
101 : public:
102 : MBTilesDataset();
103 :
104 : ~MBTilesDataset() override;
105 :
106 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
107 : CPLErr SetGeoTransform(const GDALGeoTransform >) override;
108 : const OGRSpatialReference *GetSpatialRef() const override;
109 : CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
110 :
111 : char **GetMetadataDomainList() override;
112 : CSLConstList GetMetadata(const char *pszDomain = "") override;
113 : virtual const char *GetMetadataItem(const char *pszName,
114 : const char *pszDomain = "") override;
115 :
116 : CPLErr IBuildOverviews(const char *pszResampling, int nOverviews,
117 : const int *panOverviewList, int nBandsIn,
118 : const int * /* panBandList */,
119 : GDALProgressFunc pfnProgress, void *pProgressData,
120 : CSLConstList papszOptions) override;
121 :
122 1956 : int GetLayerCount() const override
123 : {
124 1956 : return static_cast<int>(m_apoLayers.size());
125 : }
126 :
127 : const OGRLayer *GetLayer(int) const override;
128 :
129 : static GDALDataset *Open(GDALOpenInfo *);
130 : static int Identify(GDALOpenInfo *);
131 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
132 : int nBandsIn, GDALDataType eDT,
133 : CSLConstList papszOptions);
134 : static GDALDataset *CreateCopy(const char *pszFilename,
135 : GDALDataset *poSrcDS, int bStrict,
136 : CSLConstList papszOptions,
137 : GDALProgressFunc pfnProgress,
138 : void *pProgressData);
139 :
140 : char *FindKey(int iPixel, int iLine);
141 :
142 : bool HasNonEmptyGrids();
143 :
144 : private:
145 : bool m_bWriteBounds;
146 : CPLString m_osBounds;
147 : CPLString m_osCenter;
148 : bool m_bWriteMinMaxZoom;
149 : MBTilesDataset *poMainDS;
150 : bool m_bGeoTransformValid;
151 : GDALGeoTransform m_gt{};
152 : int m_nMinZoomLevel = 0;
153 : OGRSpatialReference m_oSRS{};
154 :
155 : int m_nOverviewCount;
156 : MBTilesDataset **m_papoOverviewDS;
157 :
158 : GDALDatasetH hDS;
159 : sqlite3 *hDB;
160 :
161 : sqlite3_vfs *pMyVFS;
162 :
163 : bool bFetchedMetadata;
164 : CPLStringList aosList;
165 :
166 : int nHasNonEmptyGrids;
167 :
168 : bool m_bInFlushCache;
169 :
170 : CPLString m_osMetadataMemFilename;
171 : CPLString m_osClip;
172 : std::vector<std::unique_ptr<OGRLayer>> m_apoLayers;
173 :
174 : void ParseCompressionOptions(CSLConstList papszOptions);
175 : CPLErr FinalizeRasterRegistration();
176 : void ComputeTileAndPixelShifts();
177 : bool InitRaster(MBTilesDataset *poParentDS, int nZoomLevel, int nBandCount,
178 : int nTileSize, double dfGDALMinX, double dfGDALMinY,
179 : double dfGDALMaxX, double dfGDALMaxY);
180 :
181 : bool CreateInternal(const char *pszFilename, int nXSize, int nYSize,
182 : int nBandsIn, GDALDataType eDT,
183 : CSLConstList papszOptions);
184 : void InitVector(double dfMinX, double dfMinY, double dfMaxX, double dfMaxY,
185 : bool bZoomLevelFromSpatialFilter, bool bJsonField);
186 :
187 : protected:
188 : // Coming from GDALGPKGMBTilesLikePseudoDataset
189 :
190 : CPLErr IFlushCacheWithErrCode(bool bAtClosing) override;
191 :
192 2766 : int IGetRasterCount() override
193 : {
194 2766 : return nBands;
195 : }
196 :
197 4763 : GDALRasterBand *IGetRasterBand(int nBand) override
198 : {
199 4763 : return GetRasterBand(nBand);
200 : }
201 :
202 765 : sqlite3 *IGetDB() override
203 : {
204 765 : return hDB;
205 : }
206 :
207 1578 : bool IGetUpdate() override
208 : {
209 1578 : return eAccess == GA_Update;
210 : }
211 :
212 : bool ICanIWriteBlock() override;
213 : OGRErr IStartTransaction() override;
214 : OGRErr ICommitTransaction() override;
215 :
216 32 : const char *IGetFilename() override
217 : {
218 32 : return GetDescription();
219 : }
220 :
221 : int GetRowFromIntoTopConvention(int nRow) override;
222 : };
223 :
224 : /************************************************************************/
225 : /* ==================================================================== */
226 : /* MBTilesVectorLayer */
227 : /* ==================================================================== */
228 : /************************************************************************/
229 :
230 : class MBTilesVectorLayer final : public OGRLayer
231 : {
232 : MBTilesDataset *m_poDS = nullptr;
233 : OGRFeatureDefn *m_poFeatureDefn = nullptr;
234 : OGRLayerH m_hTileIteratorLyr = nullptr;
235 : bool m_bEOF = false;
236 : CPLString m_osTmpFilename;
237 : GDALDatasetH m_hTileDS = nullptr;
238 : GIntBig m_nFeatureCount = -1;
239 : int m_nX = 0;
240 : int m_nY = 0;
241 : OGREnvelope m_sExtent;
242 : int m_nFilterMinX = 0;
243 : int m_nFilterMinY = 0;
244 : int m_nFilterMaxX = 0;
245 : int m_nFilterMaxY = 0;
246 : int m_nZoomLevel = 0;
247 : bool m_bZoomLevelAuto = false;
248 : bool m_bJsonField = false;
249 :
250 : OGRFeature *GetNextRawFeature();
251 : OGRFeature *GetNextSrcFeature();
252 : OGRFeature *CreateFeatureFrom(OGRFeature *poSrcFeature) const;
253 :
254 : public:
255 : MBTilesVectorLayer(MBTilesDataset *poDS, const char *pszLayerName,
256 : const CPLJSONObject &oFields,
257 : const CPLJSONArray &oAttributesFromTileStats,
258 : bool bJsonField, double dfMinX, double dfMinY,
259 : double dfMaxX, double dfMaxY,
260 : OGRwkbGeometryType eGeomType,
261 : bool bZoomLevelFromSpatialFilter);
262 : ~MBTilesVectorLayer() override;
263 :
264 : void ResetReading() override;
265 : OGRFeature *GetNextFeature() override;
266 :
267 751 : const OGRFeatureDefn *GetLayerDefn() const override
268 : {
269 751 : return m_poFeatureDefn;
270 : }
271 :
272 : GIntBig GetFeatureCount(int bForce) override;
273 : int TestCapability(const char *) const override;
274 :
275 : OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
276 : bool bForce) override;
277 :
278 : virtual OGRErr ISetSpatialFilter(int iGeomField,
279 : const OGRGeometry *poGeom) override;
280 :
281 : OGRFeature *GetFeature(GIntBig nFID) override;
282 : };
283 :
284 : /************************************************************************/
285 : /* ==================================================================== */
286 : /* MBTilesBand */
287 : /* ==================================================================== */
288 : /************************************************************************/
289 :
290 : class MBTilesBand final : public GDALGPKGMBTilesLikeRasterBand
291 : {
292 : friend class MBTilesDataset;
293 :
294 : CPLString osLocationInfo;
295 :
296 : public:
297 : explicit MBTilesBand(MBTilesDataset *poDS, int nTileSize);
298 :
299 : int GetOverviewCount() override;
300 : GDALRasterBand *GetOverview(int nLevel) override;
301 :
302 : char **GetMetadataDomainList() override;
303 : virtual const char *GetMetadataItem(const char *pszName,
304 : const char *pszDomain = "") override;
305 : };
306 :
307 : /************************************************************************/
308 : /* MBTilesBand() */
309 : /************************************************************************/
310 :
311 1294 : MBTilesBand::MBTilesBand(MBTilesDataset *poDSIn, int nTileSize)
312 1294 : : GDALGPKGMBTilesLikeRasterBand(poDSIn, nTileSize, nTileSize)
313 : {
314 1294 : }
315 :
316 : /************************************************************************/
317 : /* utf8decode() */
318 : /************************************************************************/
319 :
320 0 : static unsigned utf8decode(const char *p, const char *end, int *len)
321 : {
322 0 : unsigned char c = *(unsigned char *)p;
323 0 : if (c < 0x80)
324 : {
325 0 : *len = 1;
326 0 : return c;
327 : }
328 0 : else if (c < 0xc2)
329 : {
330 0 : goto FAIL;
331 : }
332 0 : if (p + 1 >= end || (p[1] & 0xc0) != 0x80)
333 0 : goto FAIL;
334 0 : if (c < 0xe0)
335 : {
336 0 : *len = 2;
337 0 : return ((p[0] & 0x1f) << 6) + ((p[1] & 0x3f));
338 : }
339 0 : else if (c == 0xe0)
340 : {
341 0 : if (((unsigned char *)p)[1] < 0xa0)
342 0 : goto FAIL;
343 0 : goto UTF8_3;
344 : }
345 : #if STRICT_RFC3629
346 : else if (c == 0xed)
347 : {
348 : // RFC 3629 says surrogate chars are illegal.
349 : if (((unsigned char *)p)[1] >= 0xa0)
350 : goto FAIL;
351 : goto UTF8_3;
352 : }
353 : else if (c == 0xef)
354 : {
355 : // 0xfffe and 0xffff are also illegal characters
356 : if (((unsigned char *)p)[1] == 0xbf && ((unsigned char *)p)[2] >= 0xbe)
357 : goto FAIL;
358 : goto UTF8_3;
359 : }
360 : #endif
361 0 : else if (c < 0xf0)
362 : {
363 0 : UTF8_3:
364 0 : if (p + 2 >= end || (p[2] & 0xc0) != 0x80)
365 0 : goto FAIL;
366 0 : *len = 3;
367 0 : return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + ((p[2] & 0x3f));
368 : }
369 0 : else if (c == 0xf0)
370 : {
371 0 : if (((unsigned char *)p)[1] < 0x90)
372 0 : goto FAIL;
373 0 : goto UTF8_4;
374 : }
375 0 : else if (c < 0xf4)
376 : {
377 0 : UTF8_4:
378 0 : if (p + 3 >= end || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80)
379 0 : goto FAIL;
380 0 : *len = 4;
381 : #if STRICT_RFC3629
382 : // RFC 3629 says all codes ending in fffe or ffff are illegal:
383 : if ((p[1] & 0xf) == 0xf && ((unsigned char *)p)[2] == 0xbf &&
384 : ((unsigned char *)p)[3] >= 0xbe)
385 : goto FAIL;
386 : #endif
387 0 : return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) +
388 0 : ((p[2] & 0x3f) << 6) + ((p[3] & 0x3f));
389 : }
390 0 : else if (c == 0xf4)
391 : {
392 0 : if (((unsigned char *)p)[1] > 0x8f)
393 0 : goto FAIL; // after 0x10ffff
394 0 : goto UTF8_4;
395 : }
396 : else
397 : {
398 0 : FAIL:
399 0 : *len = 1;
400 0 : return 0xfffd; // Unicode REPLACEMENT CHARACTER
401 : }
402 : }
403 :
404 : /************************************************************************/
405 : /* HasNonEmptyGrids() */
406 : /************************************************************************/
407 :
408 0 : bool MBTilesDataset::HasNonEmptyGrids()
409 : {
410 : OGRLayerH hSQLLyr;
411 : OGRFeatureH hFeat;
412 :
413 0 : if (poMainDS)
414 0 : return poMainDS->HasNonEmptyGrids();
415 :
416 0 : if (nHasNonEmptyGrids >= 0)
417 0 : return nHasNonEmptyGrids != FALSE;
418 :
419 0 : nHasNonEmptyGrids = false;
420 :
421 0 : if (GDALDatasetGetLayerByName(hDS, "grids") == nullptr)
422 0 : return false;
423 :
424 0 : const char *pszSQL = "SELECT type FROM sqlite_master WHERE name = 'grids'";
425 0 : CPLDebug("MBTILES", "%s", pszSQL);
426 0 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
427 0 : if (hSQLLyr == nullptr)
428 0 : return false;
429 :
430 0 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
431 0 : if (hFeat == nullptr || !OGR_F_IsFieldSetAndNotNull(hFeat, 0))
432 : {
433 0 : OGR_F_Destroy(hFeat);
434 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
435 0 : return false;
436 : }
437 :
438 0 : bool bGridsIsView = strcmp(OGR_F_GetFieldAsString(hFeat, 0), "view") == 0;
439 :
440 0 : OGR_F_Destroy(hFeat);
441 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
442 :
443 0 : nHasNonEmptyGrids = TRUE;
444 :
445 : /* In the case 'grids' is a view (and a join between the 'map' and
446 : * 'grid_utfgrid' layers */
447 : /* the cost of evaluating a join is very long, even if grid_utfgrid is empty
448 : */
449 : /* so check it is not empty */
450 0 : if (bGridsIsView)
451 : {
452 : OGRLayerH hGridUTFGridLyr;
453 0 : hGridUTFGridLyr = GDALDatasetGetLayerByName(hDS, "grid_utfgrid");
454 0 : if (hGridUTFGridLyr != nullptr)
455 : {
456 0 : OGR_L_ResetReading(hGridUTFGridLyr);
457 0 : hFeat = OGR_L_GetNextFeature(hGridUTFGridLyr);
458 0 : OGR_F_Destroy(hFeat);
459 :
460 0 : nHasNonEmptyGrids = hFeat != nullptr;
461 : }
462 : }
463 :
464 0 : return nHasNonEmptyGrids != FALSE;
465 : }
466 :
467 : /************************************************************************/
468 : /* FindKey() */
469 : /************************************************************************/
470 :
471 0 : char *MBTilesDataset::FindKey(int iPixel, int iLine)
472 : {
473 : int nBlockXSize;
474 : int nBlockYSize;
475 0 : GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
476 :
477 : // Compute shift between GDAL origin and TileMatrixSet origin
478 : // Caution this is in GeoPackage / WMTS convention ! That is upper-left
479 : // corner
480 0 : const int nShiftXPixels =
481 0 : (int)floor(0.5 + (m_gt.xorig - TMS_ORIGIN_X) / m_gt.xscale);
482 0 : const int nShiftYPixelsFromGPKGOrigin =
483 0 : (int)floor(0.5 + (m_gt.yorig - TMS_ORIGIN_Y) / m_gt.yscale);
484 :
485 0 : const int iLineFromGPKGOrigin = iLine + nShiftYPixelsFromGPKGOrigin;
486 0 : const int iLineFromMBTilesOrigin =
487 0 : m_nTileMatrixHeight * nBlockYSize - 1 - iLineFromGPKGOrigin;
488 0 : const int iPixelFromMBTilesOrigin = iPixel + nShiftXPixels;
489 :
490 0 : const int nTileColumn = iPixelFromMBTilesOrigin / nBlockXSize;
491 0 : const int nTileRow = iLineFromMBTilesOrigin / nBlockYSize;
492 0 : int nColInTile = iPixelFromMBTilesOrigin % nBlockXSize;
493 0 : int nRowInTile = nBlockYSize - 1 - (iLineFromMBTilesOrigin % nBlockYSize);
494 :
495 0 : char *pszKey = nullptr;
496 :
497 0 : json_object *poGrid = nullptr;
498 : int i;
499 :
500 : /* See https://github.com/mapbox/utfgrid-spec/blob/master/1.0/utfgrid.md */
501 : /* for the explanation of the following process */
502 : const char *pszSQL =
503 0 : CPLSPrintf("SELECT grid FROM grids WHERE "
504 : "zoom_level = %d AND tile_column = %d AND tile_row = %d",
505 : m_nZoomLevel, nTileColumn, nTileRow);
506 0 : CPLDebug("MBTILES", "%s", pszSQL);
507 0 : OGRLayerH hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
508 0 : if (hSQLLyr == nullptr)
509 0 : return nullptr;
510 :
511 0 : OGRFeatureH hFeat = OGR_L_GetNextFeature(hSQLLyr);
512 0 : if (hFeat == nullptr || !OGR_F_IsFieldSetAndNotNull(hFeat, 0))
513 : {
514 0 : OGR_F_Destroy(hFeat);
515 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
516 0 : return nullptr;
517 : }
518 :
519 0 : int nDataSize = 0;
520 0 : GByte *pabyData = OGR_F_GetFieldAsBinary(hFeat, 0, &nDataSize);
521 :
522 0 : int nUncompressedSize = nBlockXSize * nBlockYSize;
523 0 : GByte *pabyUncompressed = (GByte *)VSIMalloc(nUncompressedSize + 1);
524 0 : if (pabyUncompressed == nullptr)
525 : {
526 0 : OGR_F_Destroy(hFeat);
527 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
528 0 : return nullptr;
529 : }
530 :
531 : z_stream sStream;
532 0 : memset(&sStream, 0, sizeof(sStream));
533 0 : if (inflateInit(&sStream) != Z_OK)
534 : {
535 0 : OGR_F_Destroy(hFeat);
536 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
537 0 : CPLFree(pabyUncompressed);
538 0 : return nullptr;
539 : }
540 0 : sStream.next_in = pabyData;
541 0 : sStream.avail_in = nDataSize;
542 0 : sStream.next_out = pabyUncompressed;
543 0 : sStream.avail_out = nUncompressedSize;
544 0 : int nStatus = inflate(&sStream, Z_FINISH);
545 0 : inflateEnd(&sStream);
546 0 : if (nStatus != Z_OK && nStatus != Z_STREAM_END)
547 : {
548 0 : CPLDebug("MBTILES", "Error unzipping grid");
549 0 : nUncompressedSize = 0;
550 0 : pabyUncompressed[nUncompressedSize] = 0;
551 : }
552 : else
553 : {
554 0 : nUncompressedSize -= sStream.avail_out;
555 0 : pabyUncompressed[nUncompressedSize] = 0;
556 : // CPLDebug("MBTILES", "Grid size = %d", nUncompressedSize);
557 : // CPLDebug("MBTILES", "Grid value = %s", (const
558 : // char*)pabyUncompressed);
559 : }
560 :
561 0 : json_object *jsobj = nullptr;
562 :
563 0 : if (nUncompressedSize == 0)
564 : {
565 0 : goto end;
566 : }
567 :
568 0 : if (!OGRJSonParse((const char *)pabyUncompressed, &jsobj, true))
569 : {
570 0 : goto end;
571 : }
572 :
573 0 : if (json_object_is_type(jsobj, json_type_object))
574 : {
575 0 : poGrid = CPL_json_object_object_get(jsobj, "grid");
576 : }
577 0 : if (poGrid != nullptr && json_object_is_type(poGrid, json_type_array))
578 : {
579 : int nFactor;
580 : json_object *poRow;
581 0 : char *pszRow = nullptr;
582 :
583 0 : const int nLines = static_cast<int>(json_object_array_length(poGrid));
584 0 : if (nLines == 0)
585 0 : goto end;
586 :
587 0 : nFactor = nBlockXSize / nLines;
588 0 : nRowInTile /= nFactor;
589 0 : nColInTile /= nFactor;
590 :
591 0 : poRow = json_object_array_get_idx(poGrid, nRowInTile);
592 :
593 : /* Extract line of interest in grid */
594 0 : if (poRow != nullptr && json_object_is_type(poRow, json_type_string))
595 : {
596 0 : pszRow = CPLStrdup(json_object_get_string(poRow));
597 : }
598 :
599 0 : if (pszRow == nullptr)
600 0 : goto end;
601 :
602 : /* Unapply JSON encoding */
603 0 : for (i = 0; pszRow[i] != '\0'; i++)
604 : {
605 0 : unsigned char c = ((GByte *)pszRow)[i];
606 0 : if (c >= 93)
607 0 : c--;
608 0 : if (c >= 35)
609 0 : c--;
610 0 : if (c < 32)
611 : {
612 0 : CPLDebug("MBTILES", "Invalid character at byte %d", i);
613 0 : break;
614 : }
615 0 : c -= 32;
616 0 : ((GByte *)pszRow)[i] = c;
617 : }
618 :
619 0 : if (pszRow[i] == '\0')
620 : {
621 0 : char *pszEnd = pszRow + i;
622 :
623 0 : int iCol = 0;
624 0 : i = 0;
625 0 : int nKey = -1;
626 0 : while (pszRow + i < pszEnd)
627 : {
628 0 : int len = 0;
629 0 : unsigned int res = utf8decode(pszRow + i, pszEnd, &len);
630 :
631 : /* Invalid UTF8 ? */
632 0 : if (res > 127 && len == 1)
633 0 : break;
634 :
635 0 : if (iCol == nColInTile)
636 : {
637 0 : nKey = (int)res;
638 : // CPLDebug("MBTILES", "Key index = %d", nKey);
639 0 : break;
640 : }
641 0 : i += len;
642 0 : iCol++;
643 : }
644 :
645 : /* Find key */
646 0 : json_object *poKeys = CPL_json_object_object_get(jsobj, "keys");
647 0 : if (nKey >= 0 && poKeys != nullptr &&
648 0 : json_object_is_type(poKeys, json_type_array) &&
649 0 : nKey < static_cast<int>(json_object_array_length(poKeys)))
650 : {
651 0 : json_object *poKey = json_object_array_get_idx(poKeys, nKey);
652 0 : if (poKey != nullptr &&
653 0 : json_object_is_type(poKey, json_type_string))
654 : {
655 0 : pszKey = CPLStrdup(json_object_get_string(poKey));
656 : }
657 : }
658 : }
659 :
660 0 : CPLFree(pszRow);
661 : }
662 :
663 0 : end:
664 0 : if (jsobj)
665 0 : json_object_put(jsobj);
666 0 : VSIFree(pabyUncompressed);
667 0 : OGR_F_Destroy(hFeat);
668 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
669 :
670 0 : return pszKey;
671 : }
672 :
673 : /************************************************************************/
674 : /* GetMetadataDomainList() */
675 : /************************************************************************/
676 :
677 0 : char **MBTilesBand::GetMetadataDomainList()
678 : {
679 0 : return CSLAddString(GDALPamRasterBand::GetMetadataDomainList(),
680 0 : "LocationInfo");
681 : }
682 :
683 : /************************************************************************/
684 : /* GetMetadataItem() */
685 : /************************************************************************/
686 :
687 57 : const char *MBTilesBand::GetMetadataItem(const char *pszName,
688 : const char *pszDomain)
689 : {
690 57 : MBTilesDataset *poGDS = (MBTilesDataset *)poDS;
691 :
692 : /* ==================================================================== */
693 : /* LocationInfo handling. */
694 : /* ==================================================================== */
695 57 : if (poGDS->hDS != nullptr && pszDomain != nullptr &&
696 20 : EQUAL(pszDomain, "LocationInfo") &&
697 0 : (STARTS_WITH_CI(pszName, "Pixel_") ||
698 0 : STARTS_WITH_CI(pszName, "GeoPixel_")))
699 : {
700 : int iPixel, iLine;
701 :
702 0 : if (!poGDS->HasNonEmptyGrids())
703 0 : return nullptr;
704 :
705 : /* --------------------------------------------------------------------
706 : */
707 : /* What pixel are we aiming at? */
708 : /* --------------------------------------------------------------------
709 : */
710 0 : if (STARTS_WITH_CI(pszName, "Pixel_"))
711 : {
712 0 : if (sscanf(pszName + 6, "%d_%d", &iPixel, &iLine) != 2)
713 0 : return nullptr;
714 : }
715 0 : else if (STARTS_WITH_CI(pszName, "GeoPixel_"))
716 : {
717 0 : GDALGeoTransform gt;
718 0 : GDALGeoTransform invGT;
719 : double dfGeoX, dfGeoY;
720 :
721 0 : dfGeoX = CPLAtof(pszName + 9);
722 0 : const char *pszUnderscore = strchr(pszName + 9, '_');
723 0 : if (!pszUnderscore)
724 0 : return nullptr;
725 0 : dfGeoY = CPLAtof(pszUnderscore + 1);
726 :
727 0 : if (GetDataset() == nullptr)
728 0 : return nullptr;
729 :
730 0 : if (GetDataset()->GetGeoTransform(gt) != CE_None)
731 0 : return nullptr;
732 :
733 0 : if (!GDALInvGeoTransform(gt.data(), invGT.data()))
734 0 : return nullptr;
735 :
736 0 : iPixel =
737 0 : (int)floor(invGT[0] + invGT[1] * dfGeoX + invGT[2] * dfGeoY);
738 0 : iLine =
739 0 : (int)floor(invGT[3] + invGT[4] * dfGeoX + invGT[5] * dfGeoY);
740 : }
741 : else
742 0 : return nullptr;
743 :
744 0 : if (iPixel < 0 || iLine < 0 || iPixel >= GetXSize() ||
745 0 : iLine >= GetYSize())
746 0 : return nullptr;
747 :
748 0 : char *pszKey = poGDS->FindKey(iPixel, iLine);
749 :
750 0 : if (pszKey != nullptr)
751 : {
752 : // CPLDebug("MBTILES", "Key = %s", pszKey);
753 :
754 0 : osLocationInfo = "<LocationInfo>";
755 0 : osLocationInfo += "<Key>";
756 : char *pszXMLEscaped =
757 0 : CPLEscapeString(pszKey, -1, CPLES_XML_BUT_QUOTES);
758 0 : osLocationInfo += pszXMLEscaped;
759 0 : CPLFree(pszXMLEscaped);
760 0 : osLocationInfo += "</Key>";
761 :
762 0 : if (GDALDatasetGetLayerByName(poGDS->hDS, "grid_data") != nullptr &&
763 0 : strchr(pszKey, '\'') == nullptr)
764 : {
765 : OGRLayerH hSQLLyr;
766 : OGRFeatureH hFeat;
767 :
768 : const char *pszSQL =
769 0 : CPLSPrintf("SELECT key_json FROM keymap WHERE "
770 : "key_name = '%s'",
771 : pszKey);
772 0 : CPLDebug("MBTILES", "%s", pszSQL);
773 : hSQLLyr =
774 0 : GDALDatasetExecuteSQL(poGDS->hDS, pszSQL, nullptr, nullptr);
775 0 : if (hSQLLyr)
776 : {
777 0 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
778 0 : if (hFeat != nullptr &&
779 0 : OGR_F_IsFieldSetAndNotNull(hFeat, 0))
780 : {
781 0 : const char *pszJSon = OGR_F_GetFieldAsString(hFeat, 0);
782 : // CPLDebug("MBTILES", "JSon = %s", pszJSon);
783 :
784 0 : osLocationInfo += "<JSon>";
785 : #ifdef CPLES_XML_BUT_QUOTES
786 : pszXMLEscaped =
787 0 : CPLEscapeString(pszJSon, -1, CPLES_XML_BUT_QUOTES);
788 : #else
789 : pszXMLEscaped = CPLEscapeString(pszJSon, -1, CPLES_XML);
790 : #endif
791 0 : osLocationInfo += pszXMLEscaped;
792 0 : CPLFree(pszXMLEscaped);
793 0 : osLocationInfo += "</JSon>";
794 : }
795 0 : OGR_F_Destroy(hFeat);
796 : }
797 0 : GDALDatasetReleaseResultSet(poGDS->hDS, hSQLLyr);
798 : }
799 :
800 0 : osLocationInfo += "</LocationInfo>";
801 :
802 0 : CPLFree(pszKey);
803 :
804 0 : return osLocationInfo.c_str();
805 : }
806 :
807 0 : return nullptr;
808 : }
809 : else
810 57 : return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
811 : }
812 :
813 : /************************************************************************/
814 : /* GetOverviewCount() */
815 : /************************************************************************/
816 :
817 9 : int MBTilesBand::GetOverviewCount()
818 : {
819 9 : MBTilesDataset *poGDS = (MBTilesDataset *)poDS;
820 :
821 9 : if (poGDS->m_nOverviewCount >= 1)
822 5 : return poGDS->m_nOverviewCount;
823 : else
824 4 : return GDALPamRasterBand::GetOverviewCount();
825 : }
826 :
827 : /************************************************************************/
828 : /* GetOverview() */
829 : /************************************************************************/
830 :
831 7 : GDALRasterBand *MBTilesBand::GetOverview(int nLevel)
832 : {
833 7 : MBTilesDataset *poGDS = (MBTilesDataset *)poDS;
834 :
835 7 : if (poGDS->m_nOverviewCount == 0)
836 0 : return GDALPamRasterBand::GetOverview(nLevel);
837 :
838 7 : if (nLevel < 0 || nLevel >= poGDS->m_nOverviewCount)
839 0 : return nullptr;
840 :
841 7 : GDALDataset *poOvrDS = poGDS->m_papoOverviewDS[nLevel];
842 7 : if (poOvrDS)
843 7 : return poOvrDS->GetRasterBand(nBand);
844 : else
845 0 : return nullptr;
846 : }
847 :
848 : /************************************************************************/
849 : /* MBTilesDataset() */
850 : /************************************************************************/
851 :
852 462 : MBTilesDataset::MBTilesDataset()
853 : {
854 462 : m_bWriteBounds = true;
855 462 : m_bWriteMinMaxZoom = true;
856 462 : poMainDS = nullptr;
857 462 : m_nOverviewCount = 0;
858 462 : hDS = nullptr;
859 462 : m_papoOverviewDS = nullptr;
860 462 : bFetchedMetadata = false;
861 462 : nHasNonEmptyGrids = -1;
862 462 : hDB = nullptr;
863 462 : pMyVFS = nullptr;
864 :
865 462 : m_bGeoTransformValid = false;
866 462 : m_bInFlushCache = false;
867 :
868 462 : m_osRasterTable = "tiles";
869 462 : m_eTF = GPKG_TF_PNG;
870 :
871 462 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
872 462 : m_oSRS.importFromEPSG(3857);
873 462 : }
874 :
875 : /************************************************************************/
876 : /* ~MBTilesDataset() */
877 : /************************************************************************/
878 :
879 924 : MBTilesDataset::~MBTilesDataset()
880 : {
881 : // Need to explicitly clear it before close hDS
882 462 : m_apoLayers.clear();
883 :
884 462 : FlushCache(true);
885 :
886 462 : if (poMainDS == nullptr)
887 : {
888 116 : if (m_papoOverviewDS)
889 : {
890 417 : for (int i = 0; i < m_nOverviewCount; i++)
891 346 : delete m_papoOverviewDS[i];
892 71 : CPLFree(m_papoOverviewDS);
893 : }
894 :
895 116 : if (hDS != nullptr)
896 : {
897 64 : GDALClose(hDS);
898 64 : hDB = nullptr;
899 : }
900 116 : if (hDB != nullptr)
901 : {
902 52 : sqlite3_close(hDB);
903 :
904 52 : if (pMyVFS)
905 : {
906 26 : sqlite3_vfs_unregister(pMyVFS);
907 26 : CPLFree(pMyVFS->pAppData);
908 26 : CPLFree(pMyVFS);
909 : }
910 : }
911 : }
912 :
913 462 : if (!m_osMetadataMemFilename.empty())
914 : {
915 33 : VSIUnlink(m_osMetadataMemFilename);
916 : }
917 924 : }
918 :
919 : /************************************************************************/
920 : /* IStartTransaction() */
921 : /************************************************************************/
922 :
923 38 : OGRErr MBTilesDataset::IStartTransaction()
924 : {
925 38 : char *pszErrMsg = nullptr;
926 38 : const int rc = sqlite3_exec(hDB, "BEGIN", nullptr, nullptr, &pszErrMsg);
927 38 : if (rc != SQLITE_OK)
928 : {
929 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s transaction failed: %s",
930 : "BEGIN", pszErrMsg);
931 0 : sqlite3_free(pszErrMsg);
932 0 : return OGRERR_FAILURE;
933 : }
934 :
935 38 : return OGRERR_NONE;
936 : }
937 :
938 : /************************************************************************/
939 : /* ICommitTransaction() */
940 : /************************************************************************/
941 :
942 38 : OGRErr MBTilesDataset::ICommitTransaction()
943 : {
944 38 : char *pszErrMsg = nullptr;
945 38 : const int rc = sqlite3_exec(hDB, "COMMIT", nullptr, nullptr, &pszErrMsg);
946 38 : if (rc != SQLITE_OK)
947 : {
948 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s transaction failed: %s",
949 : "COMMIT", pszErrMsg);
950 0 : sqlite3_free(pszErrMsg);
951 0 : return OGRERR_FAILURE;
952 : }
953 :
954 38 : return OGRERR_NONE;
955 : }
956 :
957 : /************************************************************************/
958 : /* ICanIWriteBlock() */
959 : /************************************************************************/
960 :
961 248 : bool MBTilesDataset::ICanIWriteBlock()
962 : {
963 248 : if (eAccess != GA_Update)
964 : {
965 0 : CPLError(
966 : CE_Failure, CPLE_NotSupported,
967 : "IWriteBlock() not supported on dataset opened in read-only mode");
968 0 : return false;
969 : }
970 :
971 248 : if (!m_bGeoTransformValid)
972 : {
973 0 : CPLError(CE_Failure, CPLE_NotSupported,
974 : "IWriteBlock() not supported if georeferencing not set");
975 0 : return false;
976 : }
977 248 : return true;
978 : }
979 :
980 : /************************************************************************/
981 : /* IFlushCacheWithErrCode() */
982 : /************************************************************************/
983 :
984 5620 : CPLErr MBTilesDataset::IFlushCacheWithErrCode(bool bAtClosing)
985 :
986 : {
987 5620 : if (m_bInFlushCache)
988 4264 : return CE_None;
989 1356 : m_bInFlushCache = true;
990 : // Short circuit GDALPamDataset to avoid serialization to .aux.xml
991 1356 : GDALDataset::FlushCache(bAtClosing);
992 :
993 1356 : CPLErr eErr = FlushTiles();
994 :
995 1356 : m_bInFlushCache = false;
996 1356 : return eErr;
997 : }
998 :
999 : /************************************************************************/
1000 : /* ICanIWriteBlock() */
1001 : /************************************************************************/
1002 :
1003 765 : int MBTilesDataset::GetRowFromIntoTopConvention(int nRow)
1004 : {
1005 765 : return m_nTileMatrixHeight - 1 - nRow;
1006 : }
1007 :
1008 : /************************************************************************/
1009 : /* GetGeoTransform() */
1010 : /************************************************************************/
1011 :
1012 41 : CPLErr MBTilesDataset::GetGeoTransform(GDALGeoTransform >) const
1013 : {
1014 41 : gt = m_gt;
1015 41 : return (m_bGeoTransformValid) ? CE_None : CE_Failure;
1016 : }
1017 :
1018 : /************************************************************************/
1019 : /* SphericalMercatorToLongLat() */
1020 : /************************************************************************/
1021 :
1022 99 : static void SphericalMercatorToLongLat(double *x, double *y)
1023 : {
1024 99 : double lng = *x / SPHERICAL_RADIUS / M_PI * 180;
1025 99 : double lat = 2 * (atan(exp(*y / SPHERICAL_RADIUS)) - M_PI / 4) / M_PI * 180;
1026 99 : *x = lng;
1027 99 : *y = lat;
1028 99 : }
1029 :
1030 : /************************************************************************/
1031 : /* LongLatToSphericalMercator() */
1032 : /************************************************************************/
1033 :
1034 120 : static void LongLatToSphericalMercator(double *x, double *y)
1035 : {
1036 120 : double X = SPHERICAL_RADIUS * (*x) / 180 * M_PI;
1037 120 : double Y = SPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
1038 120 : *x = X;
1039 120 : *y = Y;
1040 120 : }
1041 :
1042 : /************************************************************************/
1043 : /* SetGeoTransform() */
1044 : /************************************************************************/
1045 :
1046 38 : CPLErr MBTilesDataset::SetGeoTransform(const GDALGeoTransform >)
1047 : {
1048 38 : if (eAccess != GA_Update)
1049 : {
1050 1 : CPLError(CE_Failure, CPLE_NotSupported,
1051 : "SetGeoTransform() not supported on read-only dataset");
1052 1 : return CE_Failure;
1053 : }
1054 37 : if (m_bGeoTransformValid)
1055 : {
1056 1 : CPLError(CE_Failure, CPLE_NotSupported,
1057 : "Cannot modify geotransform once set");
1058 1 : return CE_Failure;
1059 : }
1060 36 : if (gt.xrot != 0.0 || gt.yrot != 0 || gt.yscale > 0.0)
1061 : {
1062 1 : CPLError(CE_Failure, CPLE_NotSupported,
1063 : "Only north-up non rotated geotransform supported");
1064 1 : return CE_Failure;
1065 : }
1066 :
1067 35 : if (m_bWriteBounds)
1068 : {
1069 66 : CPLString osBounds(m_osBounds);
1070 33 : if (osBounds.empty())
1071 : {
1072 33 : double minx = gt.xorig;
1073 33 : double miny = gt.yorig + nRasterYSize * gt.yscale;
1074 33 : double maxx = gt.xorig + nRasterXSize * gt.xscale;
1075 33 : double maxy = gt.yorig;
1076 :
1077 33 : SphericalMercatorToLongLat(&minx, &miny);
1078 33 : SphericalMercatorToLongLat(&maxx, &maxy);
1079 33 : if (fabs(minx + 180) < 1e-7)
1080 : {
1081 3 : minx = -180.0;
1082 : }
1083 33 : if (fabs(maxx - 180) < 1e-7)
1084 : {
1085 3 : maxx = 180.0;
1086 : }
1087 :
1088 : // Clamp latitude so that when transformed back to EPSG:3857, we
1089 : // don't have too big northings
1090 33 : double tmpx = 0.0;
1091 33 : double ok_maxy = MAX_GM;
1092 33 : SphericalMercatorToLongLat(&tmpx, &ok_maxy);
1093 33 : if (maxy > ok_maxy)
1094 0 : maxy = ok_maxy;
1095 33 : if (miny < -ok_maxy)
1096 0 : miny = -ok_maxy;
1097 :
1098 33 : osBounds.Printf("%.17g,%.17g,%.17g,%.17g", minx, miny, maxx, maxy);
1099 : }
1100 :
1101 33 : char *pszSQL = sqlite3_mprintf(
1102 : "INSERT INTO metadata (name, value) VALUES ('bounds', '%q')",
1103 : osBounds.c_str());
1104 33 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
1105 33 : sqlite3_free(pszSQL);
1106 :
1107 33 : if (!m_osCenter.empty())
1108 : {
1109 0 : pszSQL = sqlite3_mprintf(
1110 : "INSERT INTO metadata (name, value) VALUES ('center', '%q')",
1111 : m_osCenter.c_str());
1112 0 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
1113 0 : sqlite3_free(pszSQL);
1114 : }
1115 : }
1116 :
1117 : int nBlockXSize;
1118 : int nBlockYSize;
1119 35 : GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
1120 35 : const double dfPixelXSizeZoomLevel0 = 2 * MAX_GM / nBlockXSize;
1121 35 : const double dfPixelYSizeZoomLevel0 = 2 * MAX_GM / nBlockYSize;
1122 361 : for (m_nZoomLevel = 0; m_nZoomLevel < 25; m_nZoomLevel++)
1123 : {
1124 360 : double dfExpectedPixelXSize =
1125 360 : dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
1126 360 : double dfExpectedPixelYSize =
1127 360 : dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
1128 360 : if (fabs(gt.xscale - dfExpectedPixelXSize) <
1129 360 : 1e-8 * dfExpectedPixelXSize &&
1130 34 : fabs(fabs(gt.yscale) - dfExpectedPixelYSize) <
1131 34 : 1e-8 * dfExpectedPixelYSize)
1132 : {
1133 34 : break;
1134 : }
1135 : }
1136 35 : if (m_nZoomLevel == 25)
1137 : {
1138 1 : m_nZoomLevel = -1;
1139 1 : CPLError(CE_Failure, CPLE_NotSupported,
1140 : "Could not find an appropriate zoom level that matches raster "
1141 : "pixel size");
1142 1 : return CE_Failure;
1143 : }
1144 :
1145 34 : m_gt = gt;
1146 34 : m_bGeoTransformValid = true;
1147 :
1148 34 : return FinalizeRasterRegistration();
1149 : }
1150 :
1151 : /************************************************************************/
1152 : /* ComputeTileAndPixelShifts() */
1153 : /************************************************************************/
1154 :
1155 444 : void MBTilesDataset::ComputeTileAndPixelShifts()
1156 : {
1157 : int nTileWidth, nTileHeight;
1158 444 : GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
1159 :
1160 : // Compute shift between GDAL origin and TileMatrixSet origin
1161 : // Caution this is in GeoPackage / WMTS convention ! That is upper-left
1162 : // corner
1163 444 : int nShiftXPixels =
1164 444 : (int)floor(0.5 + (m_gt.xorig - TMS_ORIGIN_X) / m_gt.xscale);
1165 444 : m_nShiftXTiles = (int)floor(1.0 * nShiftXPixels / nTileWidth);
1166 444 : m_nShiftXPixelsMod =
1167 444 : ((nShiftXPixels % nTileWidth) + nTileWidth) % nTileWidth;
1168 444 : int nShiftYPixels =
1169 444 : (int)floor(0.5 + (m_gt.yorig - TMS_ORIGIN_Y) / m_gt.yscale);
1170 444 : m_nShiftYTiles = (int)floor(1.0 * nShiftYPixels / nTileHeight);
1171 444 : m_nShiftYPixelsMod =
1172 444 : ((nShiftYPixels % nTileHeight) + nTileHeight) % nTileHeight;
1173 444 : }
1174 :
1175 : /************************************************************************/
1176 : /* FinalizeRasterRegistration() */
1177 : /************************************************************************/
1178 :
1179 34 : CPLErr MBTilesDataset::FinalizeRasterRegistration()
1180 : {
1181 34 : m_nTileMatrixWidth = (1 << m_nZoomLevel);
1182 34 : m_nTileMatrixHeight = (1 << m_nZoomLevel);
1183 :
1184 34 : ComputeTileAndPixelShifts();
1185 :
1186 34 : double dfGDALMinX = m_gt.xorig;
1187 34 : double dfGDALMinY = m_gt.yorig + nRasterYSize * m_gt.yscale;
1188 34 : double dfGDALMaxX = m_gt.xorig + nRasterXSize * m_gt.xscale;
1189 34 : double dfGDALMaxY = m_gt.yorig;
1190 :
1191 34 : m_nOverviewCount = m_nZoomLevel;
1192 68 : m_papoOverviewDS = (MBTilesDataset **)CPLCalloc(sizeof(MBTilesDataset *),
1193 34 : m_nOverviewCount);
1194 :
1195 34 : if (m_bWriteMinMaxZoom)
1196 : {
1197 34 : char *pszSQL = sqlite3_mprintf(
1198 : "INSERT INTO metadata (name, value) VALUES ('minzoom', '%d')",
1199 : m_nZoomLevel);
1200 34 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
1201 34 : sqlite3_free(pszSQL);
1202 34 : pszSQL = sqlite3_mprintf(
1203 : "INSERT INTO metadata (name, value) VALUES ('maxzoom', '%d')",
1204 : m_nZoomLevel);
1205 34 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
1206 34 : sqlite3_free(pszSQL);
1207 : }
1208 :
1209 335 : for (int i = 0; i < m_nOverviewCount; i++)
1210 : {
1211 301 : MBTilesDataset *poOvrDS = new MBTilesDataset();
1212 301 : poOvrDS->ShareLockWithParentDataset(this);
1213 : int nBlockSize;
1214 301 : GetRasterBand(1)->GetBlockSize(&nBlockSize, &nBlockSize);
1215 301 : poOvrDS->InitRaster(this, i, nBands, nBlockSize, dfGDALMinX, dfGDALMinY,
1216 : dfGDALMaxX, dfGDALMaxY);
1217 :
1218 301 : m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
1219 : }
1220 :
1221 34 : return CE_None;
1222 : }
1223 :
1224 : /************************************************************************/
1225 : /* InitRaster() */
1226 : /************************************************************************/
1227 :
1228 410 : bool MBTilesDataset::InitRaster(MBTilesDataset *poParentDS, int nZoomLevel,
1229 : int nBandCount, int nTileSize,
1230 : double dfGDALMinX, double dfGDALMinY,
1231 : double dfGDALMaxX, double dfGDALMaxY)
1232 : {
1233 410 : m_nZoomLevel = nZoomLevel;
1234 410 : m_nTileMatrixWidth = 1 << nZoomLevel;
1235 410 : m_nTileMatrixHeight = 1 << nZoomLevel;
1236 :
1237 410 : const int nTileWidth = nTileSize;
1238 410 : const int nTileHeight = nTileSize;
1239 410 : const double dfPixelXSize = 2 * MAX_GM / nTileWidth / (1 << nZoomLevel);
1240 410 : const double dfPixelYSize = 2 * MAX_GM / nTileHeight / (1 << nZoomLevel);
1241 :
1242 410 : m_bGeoTransformValid = true;
1243 410 : m_gt.xorig = dfGDALMinX;
1244 410 : m_gt.xscale = dfPixelXSize;
1245 410 : m_gt.yorig = dfGDALMaxY;
1246 410 : m_gt.yscale = -dfPixelYSize;
1247 410 : double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
1248 410 : double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
1249 410 : if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
1250 0 : return false;
1251 410 : nRasterXSize = (int)dfRasterXSize;
1252 410 : nRasterYSize = (int)dfRasterYSize;
1253 :
1254 410 : m_pabyCachedTiles =
1255 410 : (GByte *)VSI_MALLOC3_VERBOSE(4 * 4, nTileWidth, nTileHeight);
1256 410 : if (m_pabyCachedTiles == nullptr)
1257 : {
1258 0 : return false;
1259 : }
1260 :
1261 410 : if (poParentDS)
1262 : {
1263 346 : eAccess = poParentDS->eAccess;
1264 : }
1265 :
1266 1619 : for (int i = 1; i <= nBandCount; i++)
1267 1209 : SetBand(i, new MBTilesBand(this, nTileSize));
1268 :
1269 410 : ComputeTileAndPixelShifts();
1270 :
1271 410 : GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
1272 : GDAL_MDD_IMAGE_STRUCTURE);
1273 410 : GDALDataset::SetMetadataItem("ZOOM_LEVEL", CPLSPrintf("%d", m_nZoomLevel));
1274 :
1275 410 : if (poParentDS)
1276 : {
1277 346 : m_poParentDS = poParentDS;
1278 346 : poMainDS = poParentDS;
1279 346 : hDS = poParentDS->hDS;
1280 346 : hDB = poParentDS->hDB;
1281 346 : m_eTF = poParentDS->m_eTF;
1282 346 : m_nQuality = poParentDS->m_nQuality;
1283 346 : m_nZLevel = poParentDS->m_nZLevel;
1284 346 : m_bDither = poParentDS->m_bDither;
1285 346 : m_osWHERE = poParentDS->m_osWHERE;
1286 346 : SetDescription(CPLSPrintf("%s - zoom_level=%d",
1287 346 : poParentDS->GetDescription(), m_nZoomLevel));
1288 : }
1289 :
1290 410 : return true;
1291 : }
1292 :
1293 : /************************************************************************/
1294 : /* GetSpatialRef() */
1295 : /************************************************************************/
1296 :
1297 3 : const OGRSpatialReference *MBTilesDataset::GetSpatialRef() const
1298 : {
1299 3 : return &m_oSRS;
1300 : }
1301 :
1302 : /************************************************************************/
1303 : /* SetSpatialRef() */
1304 : /************************************************************************/
1305 :
1306 3 : CPLErr MBTilesDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1307 : {
1308 3 : if (eAccess != GA_Update)
1309 : {
1310 1 : CPLError(CE_Failure, CPLE_NotSupported,
1311 : "SetSpatialRef() not supported on read-only dataset");
1312 1 : return CE_Failure;
1313 : }
1314 :
1315 2 : if (poSRS == nullptr || poSRS->GetAuthorityName() == nullptr ||
1316 1 : !EQUAL(poSRS->GetAuthorityName(), "EPSG") ||
1317 5 : poSRS->GetAuthorityCode() == nullptr ||
1318 1 : !EQUAL(poSRS->GetAuthorityCode(), "3857"))
1319 : {
1320 1 : CPLError(CE_Failure, CPLE_NotSupported,
1321 : "Only EPSG:3857 supported on MBTiles dataset");
1322 1 : return CE_Failure;
1323 : }
1324 1 : return CE_None;
1325 : }
1326 :
1327 : /************************************************************************/
1328 : /* GetMetadataDomainList() */
1329 : /************************************************************************/
1330 :
1331 0 : char **MBTilesDataset::GetMetadataDomainList()
1332 : {
1333 0 : return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
1334 0 : "", nullptr);
1335 : }
1336 :
1337 : /************************************************************************/
1338 : /* GetMetadata() */
1339 : /************************************************************************/
1340 :
1341 111 : CSLConstList MBTilesDataset::GetMetadata(const char *pszDomain)
1342 : {
1343 111 : if (hDS == nullptr || (pszDomain != nullptr && !EQUAL(pszDomain, "")))
1344 32 : return GDALPamDataset::GetMetadata(pszDomain);
1345 :
1346 79 : if (bFetchedMetadata)
1347 15 : return aosList.List();
1348 :
1349 64 : bFetchedMetadata = true;
1350 64 : aosList = CPLStringList(GDALPamDataset::GetMetadata());
1351 :
1352 64 : OGRLayerH hSQLLyr = GDALDatasetExecuteSQL(
1353 : hDS, "SELECT name, value FROM metadata WHERE name != 'json' LIMIT 1000",
1354 : nullptr, nullptr);
1355 64 : if (hSQLLyr == nullptr)
1356 0 : return nullptr;
1357 :
1358 64 : if (OGR_FD_GetFieldCount(OGR_L_GetLayerDefn(hSQLLyr)) != 2)
1359 : {
1360 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
1361 0 : return nullptr;
1362 : }
1363 :
1364 : OGRFeatureH hFeat;
1365 615 : while ((hFeat = OGR_L_GetNextFeature(hSQLLyr)) != nullptr)
1366 : {
1367 1102 : if (OGR_F_IsFieldSetAndNotNull(hFeat, 0) &&
1368 551 : OGR_F_IsFieldSetAndNotNull(hFeat, 1))
1369 : {
1370 1102 : CPLString osName = OGR_F_GetFieldAsString(hFeat, 0);
1371 1102 : CPLString osValue = OGR_F_GetFieldAsString(hFeat, 1);
1372 1102 : if (osName[0] != '\0' && !STARTS_WITH(osValue, "function(") &&
1373 551 : strstr(osValue, "<img ") == nullptr &&
1374 551 : strstr(osValue, "<p>") == nullptr &&
1375 1653 : strstr(osValue, "</p>") == nullptr &&
1376 551 : strstr(osValue, "<div") == nullptr)
1377 : {
1378 551 : aosList.AddNameValue(osName, osValue);
1379 : }
1380 : }
1381 551 : OGR_F_Destroy(hFeat);
1382 : }
1383 64 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
1384 :
1385 64 : return aosList.List();
1386 : }
1387 :
1388 : /************************************************************************/
1389 : /* GetMetadataItem() */
1390 : /************************************************************************/
1391 :
1392 119 : const char *MBTilesDataset::GetMetadataItem(const char *pszName,
1393 : const char *pszDomain)
1394 : {
1395 119 : if (pszDomain == nullptr || EQUAL(pszDomain, ""))
1396 : {
1397 90 : const char *pszValue = CSLFetchNameValue(GetMetadata(), pszName);
1398 90 : if (pszValue)
1399 69 : return pszValue;
1400 : }
1401 50 : return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
1402 : }
1403 :
1404 : /************************************************************************/
1405 : /* GetLayer() */
1406 : /************************************************************************/
1407 :
1408 60 : const OGRLayer *MBTilesDataset::GetLayer(int iLayer) const
1409 :
1410 : {
1411 60 : if (iLayer < 0 || iLayer >= GetLayerCount())
1412 2 : return nullptr;
1413 58 : return m_apoLayers[iLayer].get();
1414 : }
1415 :
1416 : /************************************************************************/
1417 : /* MBTilesVectorLayer() */
1418 : /************************************************************************/
1419 :
1420 38 : MBTilesVectorLayer::MBTilesVectorLayer(
1421 : MBTilesDataset *poDS, const char *pszLayerName,
1422 : const CPLJSONObject &oFields, const CPLJSONArray &oAttributesFromTileStats,
1423 : bool bJsonField, double dfMinX, double dfMinY, double dfMaxX, double dfMaxY,
1424 38 : OGRwkbGeometryType eGeomType, bool bZoomLevelFromSpatialFilter)
1425 38 : : m_poDS(poDS), m_poFeatureDefn(new OGRFeatureDefn(pszLayerName)),
1426 76 : m_bJsonField(bJsonField)
1427 : {
1428 38 : SetDescription(pszLayerName);
1429 38 : m_poFeatureDefn->SetGeomType(eGeomType);
1430 38 : OGRSpatialReference *poSRS = new OGRSpatialReference();
1431 38 : poSRS->SetFromUserInput(SRS_EPSG_3857);
1432 38 : m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
1433 38 : poSRS->Release();
1434 38 : m_poFeatureDefn->Reference();
1435 :
1436 38 : if (m_bJsonField)
1437 : {
1438 2 : OGRFieldDefn oFieldDefnId("mvt_id", OFTInteger64);
1439 1 : m_poFeatureDefn->AddFieldDefn(&oFieldDefnId);
1440 : }
1441 : else
1442 : {
1443 37 : OGRMVTInitFields(m_poFeatureDefn, oFields, oAttributesFromTileStats);
1444 : }
1445 :
1446 38 : m_sExtent.MinX = dfMinX;
1447 38 : m_sExtent.MinY = dfMinY;
1448 38 : m_sExtent.MaxX = dfMaxX;
1449 38 : m_sExtent.MaxY = dfMaxY;
1450 :
1451 38 : m_nZoomLevel = m_poDS->m_nZoomLevel;
1452 38 : m_bZoomLevelAuto = bZoomLevelFromSpatialFilter;
1453 38 : MBTilesVectorLayer::SetSpatialFilter(nullptr);
1454 :
1455 : // If the metadata contains an empty fields object, this may be a sign
1456 : // that it doesn't know the schema. In that case check if a tile has
1457 : // attributes, and in that case create a json field.
1458 38 : if (!m_bJsonField && oFields.IsValid() && oFields.GetChildren().empty())
1459 : {
1460 8 : m_bJsonField = true;
1461 8 : OGRFeature *poSrcFeature = GetNextSrcFeature();
1462 8 : m_bJsonField = false;
1463 :
1464 8 : if (poSrcFeature)
1465 : {
1466 : // There is at least the mvt_id field
1467 8 : if (poSrcFeature->GetFieldCount() > 1)
1468 : {
1469 1 : m_bJsonField = true;
1470 : }
1471 8 : delete poSrcFeature;
1472 : }
1473 8 : MBTilesVectorLayer::ResetReading();
1474 : }
1475 :
1476 38 : if (m_bJsonField)
1477 : {
1478 4 : OGRFieldDefn oFieldDefn("json", OFTString);
1479 2 : m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
1480 : }
1481 38 : }
1482 :
1483 : /************************************************************************/
1484 : /* ~MBTilesVectorLayer() */
1485 : /************************************************************************/
1486 :
1487 76 : MBTilesVectorLayer::~MBTilesVectorLayer()
1488 : {
1489 38 : m_poFeatureDefn->Release();
1490 38 : if (m_hTileIteratorLyr)
1491 15 : GDALDatasetReleaseResultSet(m_poDS->hDS, m_hTileIteratorLyr);
1492 38 : if (!m_osTmpFilename.empty())
1493 : {
1494 15 : VSIUnlink(m_osTmpFilename);
1495 : }
1496 38 : if (m_hTileDS)
1497 11 : GDALClose(m_hTileDS);
1498 76 : }
1499 :
1500 : /************************************************************************/
1501 : /* TestCapability() */
1502 : /************************************************************************/
1503 :
1504 36 : int MBTilesVectorLayer::TestCapability(const char *pszCap) const
1505 : {
1506 36 : if (EQUAL(pszCap, OLCStringsAsUTF8) ||
1507 24 : EQUAL(pszCap, OLCFastSpatialFilter) || EQUAL(pszCap, OLCFastGetExtent))
1508 : {
1509 14 : return TRUE;
1510 : }
1511 22 : return FALSE;
1512 : }
1513 :
1514 : /************************************************************************/
1515 : /* IGetExtent() */
1516 : /************************************************************************/
1517 :
1518 4 : OGRErr MBTilesVectorLayer::IGetExtent(int /* iGeomField */,
1519 : OGREnvelope *psExtent, bool /* bForce */)
1520 : {
1521 4 : *psExtent = m_sExtent;
1522 4 : return OGRERR_NONE;
1523 : }
1524 :
1525 : /************************************************************************/
1526 : /* ResetReading() */
1527 : /************************************************************************/
1528 :
1529 111 : void MBTilesVectorLayer::ResetReading()
1530 : {
1531 111 : if (m_hTileDS)
1532 46 : GDALClose(m_hTileDS);
1533 111 : m_hTileDS = nullptr;
1534 111 : m_bEOF = false;
1535 111 : if (m_hTileIteratorLyr)
1536 96 : GDALDatasetReleaseResultSet(m_poDS->hDS, m_hTileIteratorLyr);
1537 111 : CPLString osSQL;
1538 : osSQL.Printf("SELECT tile_column, tile_row, tile_data FROM tiles "
1539 : "WHERE zoom_level = %d "
1540 : "AND tile_column BETWEEN %d AND %d "
1541 : "AND tile_row BETWEEN %d AND %d",
1542 : m_nZoomLevel, m_nFilterMinX, m_nFilterMaxX, m_nFilterMinY,
1543 111 : m_nFilterMaxY);
1544 111 : m_hTileIteratorLyr =
1545 111 : GDALDatasetExecuteSQL(m_poDS->hDS, osSQL.c_str(), nullptr, nullptr);
1546 111 : }
1547 :
1548 : /************************************************************************/
1549 : /* ISetSpatialFilter() */
1550 : /************************************************************************/
1551 :
1552 62 : OGRErr MBTilesVectorLayer::ISetSpatialFilter(int iGeomField,
1553 : const OGRGeometry *poGeomIn)
1554 : {
1555 62 : OGRErr eErr = OGRLayer::ISetSpatialFilter(iGeomField, poGeomIn);
1556 62 : if (eErr == OGRERR_NONE)
1557 : {
1558 62 : if (m_poFilterGeom != nullptr && m_sFilterEnvelope.MinX <= -MAX_GM &&
1559 2 : m_sFilterEnvelope.MinY <= -MAX_GM &&
1560 2 : m_sFilterEnvelope.MaxX >= MAX_GM &&
1561 2 : m_sFilterEnvelope.MaxY >= MAX_GM)
1562 : {
1563 2 : if (m_bZoomLevelAuto)
1564 : {
1565 0 : m_nZoomLevel = m_poDS->m_nMinZoomLevel;
1566 : }
1567 2 : m_nFilterMinX = 0;
1568 2 : m_nFilterMinY = 0;
1569 2 : m_nFilterMaxX = (1 << m_nZoomLevel) - 1;
1570 2 : m_nFilterMaxY = (1 << m_nZoomLevel) - 1;
1571 : }
1572 60 : else if (m_poFilterGeom != nullptr &&
1573 6 : m_sFilterEnvelope.MinX >= -10 * MAX_GM &&
1574 6 : m_sFilterEnvelope.MinY >= -10 * MAX_GM &&
1575 6 : m_sFilterEnvelope.MaxX <= 10 * MAX_GM &&
1576 6 : m_sFilterEnvelope.MaxY <= 10 * MAX_GM)
1577 : {
1578 6 : if (m_bZoomLevelAuto)
1579 : {
1580 : double dfExtent =
1581 0 : std::min(m_sFilterEnvelope.MaxX - m_sFilterEnvelope.MinX,
1582 0 : m_sFilterEnvelope.MaxY - m_sFilterEnvelope.MinY);
1583 0 : m_nZoomLevel = std::max(
1584 0 : m_poDS->m_nMinZoomLevel,
1585 0 : std::min(static_cast<int>(0.5 + log(2 * MAX_GM / dfExtent) /
1586 : log(2.0)),
1587 0 : m_poDS->m_nZoomLevel));
1588 0 : CPLDebug("MBTILES", "Zoom level = %d", m_nZoomLevel);
1589 : }
1590 6 : const double dfTileDim = 2 * MAX_GM / (1 << m_nZoomLevel);
1591 6 : m_nFilterMinX = std::max(
1592 12 : 0, static_cast<int>(
1593 6 : floor((m_sFilterEnvelope.MinX + MAX_GM) / dfTileDim)));
1594 6 : m_nFilterMinY = std::max(
1595 12 : 0, static_cast<int>(
1596 6 : floor((m_sFilterEnvelope.MinY + MAX_GM) / dfTileDim)));
1597 6 : m_nFilterMaxX =
1598 12 : std::min(static_cast<int>(ceil(
1599 6 : (m_sFilterEnvelope.MaxX + MAX_GM) / dfTileDim)),
1600 6 : (1 << m_nZoomLevel) - 1);
1601 6 : m_nFilterMaxY =
1602 12 : std::min(static_cast<int>(ceil(
1603 6 : (m_sFilterEnvelope.MaxY + MAX_GM) / dfTileDim)),
1604 6 : (1 << m_nZoomLevel) - 1);
1605 : }
1606 : else
1607 : {
1608 54 : if (m_bZoomLevelAuto)
1609 : {
1610 0 : m_nZoomLevel = m_poDS->m_nZoomLevel;
1611 : }
1612 54 : m_nFilterMinX = 0;
1613 54 : m_nFilterMinY = 0;
1614 54 : m_nFilterMaxX = (1 << m_nZoomLevel) - 1;
1615 54 : m_nFilterMaxY = (1 << m_nZoomLevel) - 1;
1616 : }
1617 : }
1618 62 : return eErr;
1619 : }
1620 :
1621 : /************************************************************************/
1622 : /* GetNextFeature() */
1623 : /************************************************************************/
1624 :
1625 159 : OGRFeature *MBTilesVectorLayer::GetNextFeature()
1626 : {
1627 : while (true)
1628 : {
1629 159 : OGRFeature *poFeature = GetNextRawFeature();
1630 159 : if (poFeature == nullptr)
1631 32 : return nullptr;
1632 :
1633 296 : if ((m_poFilterGeom == nullptr ||
1634 241 : FilterGeometry(poFeature->GetGeometryRef())) &&
1635 114 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
1636 : {
1637 86 : return poFeature;
1638 : }
1639 :
1640 41 : delete poFeature;
1641 41 : }
1642 : }
1643 :
1644 : /************************************************************************/
1645 : /* GetFeatureCount() */
1646 : /************************************************************************/
1647 :
1648 15 : GIntBig MBTilesVectorLayer::GetFeatureCount(int bForce)
1649 : {
1650 15 : if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
1651 : {
1652 9 : if (m_nFeatureCount < 0)
1653 : {
1654 1 : m_nFeatureCount = 0;
1655 1 : ResetReading();
1656 5 : while (m_hTileIteratorLyr != nullptr)
1657 : {
1658 5 : OGRFeatureH hFeat = OGR_L_GetNextFeature(m_hTileIteratorLyr);
1659 5 : if (hFeat == nullptr)
1660 : {
1661 1 : break;
1662 : }
1663 4 : m_nX = OGR_F_GetFieldAsInteger(hFeat, 0);
1664 : // MBTiles y origin is bottom based, whereas MVT directory
1665 : // is top based
1666 4 : m_nY =
1667 4 : (1 << m_nZoomLevel) - 1 - OGR_F_GetFieldAsInteger(hFeat, 1);
1668 4 : int nDataSize = 0;
1669 4 : GByte *pabyData = OGR_F_GetFieldAsBinary(hFeat, 2, &nDataSize);
1670 4 : GByte *pabyDataDup = static_cast<GByte *>(CPLMalloc(nDataSize));
1671 4 : memcpy(pabyDataDup, pabyData, nDataSize);
1672 4 : OGR_F_Destroy(hFeat);
1673 :
1674 4 : if (!m_osTmpFilename.empty())
1675 : {
1676 3 : VSIUnlink(m_osTmpFilename);
1677 : }
1678 : m_osTmpFilename = VSIMemGenerateHiddenFilename(
1679 4 : CPLSPrintf("mvt_%d_%d.pbf", m_nX, m_nY));
1680 4 : VSIFCloseL(VSIFileFromMemBuffer(m_osTmpFilename, pabyDataDup,
1681 : nDataSize, true));
1682 :
1683 4 : const char *l_apszAllowedDrivers[] = {"MVT", nullptr};
1684 4 : if (m_hTileDS)
1685 0 : GDALClose(m_hTileDS);
1686 4 : char **papszOpenOptions = nullptr;
1687 : papszOpenOptions =
1688 4 : CSLSetNameValue(papszOpenOptions, "METADATA_FILE",
1689 4 : m_poDS->m_osMetadataMemFilename.c_str());
1690 4 : m_hTileDS =
1691 4 : GDALOpenEx(("MVT:" + m_osTmpFilename).c_str(),
1692 : GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
1693 : l_apszAllowedDrivers, papszOpenOptions, nullptr);
1694 4 : CSLDestroy(papszOpenOptions);
1695 4 : if (m_hTileDS)
1696 : {
1697 : OGRLayerH hLayer =
1698 4 : GDALDatasetGetLayerByName(m_hTileDS, GetName());
1699 4 : if (hLayer)
1700 : {
1701 4 : m_nFeatureCount += OGR_L_GetFeatureCount(hLayer, true);
1702 : }
1703 4 : GDALClose(m_hTileDS);
1704 4 : m_hTileDS = nullptr;
1705 : }
1706 : }
1707 1 : ResetReading();
1708 : }
1709 9 : return m_nFeatureCount;
1710 : }
1711 6 : return OGRLayer::GetFeatureCount(bForce);
1712 : }
1713 :
1714 : /************************************************************************/
1715 : /* GetNextSrcFeature() */
1716 : /************************************************************************/
1717 :
1718 167 : OGRFeature *MBTilesVectorLayer::GetNextSrcFeature()
1719 : {
1720 167 : if (m_bEOF)
1721 : {
1722 3 : return nullptr;
1723 : }
1724 164 : if (m_hTileIteratorLyr == nullptr)
1725 : {
1726 14 : ResetReading();
1727 14 : if (m_hTileIteratorLyr == nullptr)
1728 : {
1729 0 : return nullptr;
1730 : }
1731 : }
1732 :
1733 164 : OGRFeatureH hTileFeat = nullptr;
1734 271 : if (m_hTileDS == nullptr ||
1735 107 : (hTileFeat = OGR_L_GetNextFeature(
1736 107 : GDALDatasetGetLayerByName(m_hTileDS, GetName()))) == nullptr)
1737 : {
1738 : while (true)
1739 : {
1740 173 : OGRFeatureH hFeat = OGR_L_GetNextFeature(m_hTileIteratorLyr);
1741 173 : if (hFeat == nullptr)
1742 : {
1743 29 : m_bEOF = true;
1744 29 : return nullptr;
1745 : }
1746 144 : m_nX = OGR_F_GetFieldAsInteger(hFeat, 0);
1747 : // MBTiles y origin is bottom based, whereas MVT directory
1748 : // is top based
1749 144 : m_nY = (1 << m_nZoomLevel) - 1 - OGR_F_GetFieldAsInteger(hFeat, 1);
1750 144 : CPLDebug("MBTiles", "X=%d, Y=%d", m_nX, m_nY);
1751 :
1752 144 : int nDataSize = 0;
1753 144 : GByte *pabyData = OGR_F_GetFieldAsBinary(hFeat, 2, &nDataSize);
1754 144 : GByte *pabyDataDup = static_cast<GByte *>(CPLMalloc(nDataSize));
1755 144 : memcpy(pabyDataDup, pabyData, nDataSize);
1756 144 : OGR_F_Destroy(hFeat);
1757 :
1758 144 : if (!m_osTmpFilename.empty())
1759 : {
1760 130 : VSIUnlink(m_osTmpFilename);
1761 : }
1762 : m_osTmpFilename = VSIMemGenerateHiddenFilename(
1763 144 : CPLSPrintf("mvt_%d_%d.pbf", m_nX, m_nY));
1764 144 : VSIFCloseL(VSIFileFromMemBuffer(m_osTmpFilename, pabyDataDup,
1765 : nDataSize, true));
1766 :
1767 144 : const char *l_apszAllowedDrivers[] = {"MVT", nullptr};
1768 144 : if (m_hTileDS)
1769 75 : GDALClose(m_hTileDS);
1770 144 : char **papszOpenOptions = nullptr;
1771 : papszOpenOptions =
1772 144 : CSLSetNameValue(papszOpenOptions, "X", CPLSPrintf("%d", m_nX));
1773 : papszOpenOptions =
1774 144 : CSLSetNameValue(papszOpenOptions, "Y", CPLSPrintf("%d", m_nY));
1775 144 : papszOpenOptions = CSLSetNameValue(papszOpenOptions, "Z",
1776 : CPLSPrintf("%d", m_nZoomLevel));
1777 144 : papszOpenOptions = CSLSetNameValue(
1778 : papszOpenOptions, "METADATA_FILE",
1779 144 : m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
1780 144 : if (!m_poDS->m_osClip.empty())
1781 : {
1782 : papszOpenOptions =
1783 3 : CSLSetNameValue(papszOpenOptions, "CLIP", m_poDS->m_osClip);
1784 : }
1785 144 : m_hTileDS =
1786 144 : GDALOpenEx(("MVT:" + m_osTmpFilename).c_str(),
1787 : GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
1788 : l_apszAllowedDrivers, papszOpenOptions, nullptr);
1789 144 : CSLDestroy(papszOpenOptions);
1790 144 : if (m_hTileDS)
1791 : {
1792 144 : if (GDALDatasetGetLayerByName(m_hTileDS, GetName()))
1793 : {
1794 136 : hTileFeat = OGR_L_GetNextFeature(
1795 136 : GDALDatasetGetLayerByName(m_hTileDS, GetName()));
1796 136 : if (hTileFeat)
1797 132 : break;
1798 : }
1799 12 : GDALClose(m_hTileDS);
1800 12 : m_hTileDS = nullptr;
1801 : }
1802 12 : }
1803 : }
1804 :
1805 135 : return reinterpret_cast<OGRFeature *>(hTileFeat);
1806 : }
1807 :
1808 : /************************************************************************/
1809 : /* CreateFeatureFrom() */
1810 : /************************************************************************/
1811 :
1812 : OGRFeature *
1813 129 : MBTilesVectorLayer::CreateFeatureFrom(OGRFeature *poSrcFeature) const
1814 : {
1815 :
1816 129 : return OGRMVTCreateFeatureFrom(poSrcFeature, m_poFeatureDefn, m_bJsonField,
1817 258 : GetSpatialRef());
1818 : }
1819 :
1820 : /************************************************************************/
1821 : /* GetNextRawFeature() */
1822 : /************************************************************************/
1823 :
1824 159 : OGRFeature *MBTilesVectorLayer::GetNextRawFeature()
1825 : {
1826 159 : OGRFeature *poSrcFeat = GetNextSrcFeature();
1827 159 : if (poSrcFeat == nullptr)
1828 32 : return nullptr;
1829 :
1830 127 : const GIntBig nFIDBase =
1831 127 : (static_cast<GIntBig>(m_nY) << m_nZoomLevel) | m_nX;
1832 127 : OGRFeature *poFeature = CreateFeatureFrom(poSrcFeat);
1833 127 : poFeature->SetFID((poSrcFeat->GetFID() << (2 * m_nZoomLevel)) | nFIDBase);
1834 127 : delete poSrcFeat;
1835 :
1836 127 : return poFeature;
1837 : }
1838 :
1839 : /************************************************************************/
1840 : /* GetFeature() */
1841 : /************************************************************************/
1842 :
1843 5 : OGRFeature *MBTilesVectorLayer::GetFeature(GIntBig nFID)
1844 : {
1845 5 : const int nZ = m_nZoomLevel;
1846 5 : const int nX = static_cast<int>(nFID & ((1 << nZ) - 1));
1847 5 : const int nY = static_cast<int>((nFID >> nZ) & ((1 << nZ) - 1));
1848 5 : const GIntBig nTileFID = nFID >> (2 * nZ);
1849 :
1850 10 : CPLString osSQL;
1851 : osSQL.Printf("SELECT tile_data FROM tiles "
1852 : "WHERE zoom_level = %d AND "
1853 : "tile_column = %d AND tile_row = %d",
1854 5 : m_nZoomLevel, nX, (1 << nZ) - 1 - nY);
1855 : auto hSQLLyr =
1856 5 : GDALDatasetExecuteSQL(m_poDS->hDS, osSQL.c_str(), nullptr, nullptr);
1857 5 : if (hSQLLyr == nullptr)
1858 0 : return nullptr;
1859 5 : auto hFeat = OGR_L_GetNextFeature(hSQLLyr);
1860 5 : if (hFeat == nullptr)
1861 : {
1862 0 : GDALDatasetReleaseResultSet(m_poDS->hDS, hSQLLyr);
1863 0 : return nullptr;
1864 : }
1865 5 : int nDataSize = 0;
1866 5 : GByte *pabyData = OGR_F_GetFieldAsBinary(hFeat, 0, &nDataSize);
1867 5 : GByte *pabyDataDup = static_cast<GByte *>(CPLMalloc(nDataSize));
1868 5 : memcpy(pabyDataDup, pabyData, nDataSize);
1869 5 : OGR_F_Destroy(hFeat);
1870 5 : GDALDatasetReleaseResultSet(m_poDS->hDS, hSQLLyr);
1871 :
1872 : const CPLString osTmpFilename = VSIMemGenerateHiddenFilename(
1873 5 : CPLSPrintf("mvt_get_feature_%d_%d.pbf", m_nX, m_nY));
1874 5 : VSIFCloseL(
1875 : VSIFileFromMemBuffer(osTmpFilename, pabyDataDup, nDataSize, true));
1876 :
1877 5 : const char *l_apszAllowedDrivers[] = {"MVT", nullptr};
1878 5 : char **papszOpenOptions = nullptr;
1879 : papszOpenOptions =
1880 5 : CSLSetNameValue(papszOpenOptions, "X", CPLSPrintf("%d", nX));
1881 : papszOpenOptions =
1882 5 : CSLSetNameValue(papszOpenOptions, "Y", CPLSPrintf("%d", nY));
1883 : papszOpenOptions =
1884 5 : CSLSetNameValue(papszOpenOptions, "Z", CPLSPrintf("%d", m_nZoomLevel));
1885 5 : papszOpenOptions = CSLSetNameValue(
1886 : papszOpenOptions, "METADATA_FILE",
1887 5 : m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
1888 5 : if (!m_poDS->m_osClip.empty())
1889 : {
1890 : papszOpenOptions =
1891 0 : CSLSetNameValue(papszOpenOptions, "CLIP", m_poDS->m_osClip);
1892 : }
1893 5 : auto hTileDS = GDALOpenEx(("MVT:" + osTmpFilename).c_str(),
1894 : GDAL_OF_VECTOR | GDAL_OF_INTERNAL,
1895 : l_apszAllowedDrivers, papszOpenOptions, nullptr);
1896 5 : CSLDestroy(papszOpenOptions);
1897 :
1898 5 : OGRFeature *poFeature = nullptr;
1899 5 : if (hTileDS)
1900 : {
1901 5 : OGRLayerH hLayer = GDALDatasetGetLayerByName(hTileDS, GetName());
1902 5 : if (hLayer)
1903 : {
1904 : OGRFeature *poUnderlyingFeature = reinterpret_cast<OGRFeature *>(
1905 5 : OGR_L_GetFeature(hLayer, nTileFID));
1906 5 : if (poUnderlyingFeature)
1907 : {
1908 2 : poFeature = CreateFeatureFrom(poUnderlyingFeature);
1909 2 : poFeature->SetFID(nFID);
1910 : }
1911 5 : delete poUnderlyingFeature;
1912 : }
1913 : }
1914 5 : GDALClose(hTileDS);
1915 :
1916 5 : VSIUnlink(osTmpFilename);
1917 :
1918 5 : return poFeature;
1919 : }
1920 :
1921 : /************************************************************************/
1922 : /* InitVector() */
1923 : /************************************************************************/
1924 :
1925 33 : void MBTilesDataset::InitVector(double dfMinX, double dfMinY, double dfMaxX,
1926 : double dfMaxY, bool bZoomLevelFromSpatialFilter,
1927 : bool bJsonField)
1928 : {
1929 33 : const char *pszSQL = "SELECT value FROM metadata WHERE name = 'json'";
1930 33 : CPLDebug("MBTILES", "%s", pszSQL);
1931 66 : CPLJSONDocument oJsonDoc;
1932 66 : CPLJSONDocument oDoc;
1933 33 : auto hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
1934 33 : if (hSQLLyr)
1935 : {
1936 33 : auto hFeat = OGR_L_GetNextFeature(hSQLLyr);
1937 33 : if (hFeat)
1938 : {
1939 33 : auto pszJson = OGR_F_GetFieldAsString(hFeat, 0);
1940 33 : oDoc.GetRoot().Add("json", pszJson);
1941 33 : CPL_IGNORE_RET_VAL(
1942 33 : oJsonDoc.LoadMemory(reinterpret_cast<const GByte *>(pszJson)));
1943 33 : OGR_F_Destroy(hFeat);
1944 : }
1945 33 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
1946 : }
1947 :
1948 : m_osMetadataMemFilename =
1949 33 : VSIMemGenerateHiddenFilename("mbtiles_metadata.json");
1950 33 : oDoc.Save(m_osMetadataMemFilename);
1951 :
1952 66 : CPLJSONArray oVectorLayers;
1953 33 : oVectorLayers.Deinit();
1954 :
1955 66 : CPLJSONArray oTileStatLayers;
1956 33 : oTileStatLayers.Deinit();
1957 :
1958 33 : oVectorLayers = oJsonDoc.GetRoot().GetArray("vector_layers");
1959 :
1960 33 : oTileStatLayers = oJsonDoc.GetRoot().GetArray("tilestats/layers");
1961 :
1962 71 : for (int i = 0; i < oVectorLayers.Size(); i++)
1963 : {
1964 114 : CPLJSONObject oId = oVectorLayers[i].GetObj("id");
1965 38 : if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
1966 : {
1967 38 : OGRwkbGeometryType eGeomType = wkbUnknown;
1968 38 : if (oTileStatLayers.IsValid())
1969 : {
1970 37 : eGeomType = OGRMVTFindGeomTypeFromTileStat(
1971 74 : oTileStatLayers, oId.ToString().c_str());
1972 : }
1973 :
1974 114 : CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
1975 : CPLJSONArray oAttributesFromTileStats =
1976 : OGRMVTFindAttributesFromTileStat(oTileStatLayers,
1977 76 : oId.ToString().c_str());
1978 38 : m_apoLayers.push_back(std::make_unique<MBTilesVectorLayer>(
1979 76 : this, oId.ToString().c_str(), oFields, oAttributesFromTileStats,
1980 : bJsonField, dfMinX, dfMinY, dfMaxX, dfMaxY, eGeomType,
1981 : bZoomLevelFromSpatialFilter));
1982 : }
1983 : }
1984 33 : }
1985 :
1986 : /************************************************************************/
1987 : /* Identify() */
1988 : /************************************************************************/
1989 :
1990 75469 : int MBTilesDataset::Identify(GDALOpenInfo *poOpenInfo)
1991 : {
1992 : #ifdef ENABLE_SQL_SQLITE_FORMAT
1993 75469 : if (poOpenInfo->pabyHeader &&
1994 14953 : STARTS_WITH((const char *)poOpenInfo->pabyHeader, "-- SQL MBTILES"))
1995 : {
1996 2 : return TRUE;
1997 : }
1998 : #endif
1999 :
2000 75467 : if ((poOpenInfo->IsExtensionEqualToCI("MBTILES") ||
2001 : // Allow direct Amazon S3 signed URLs that contains .mbtiles in the
2002 : // middle of the URL
2003 75132 : strstr(poOpenInfo->pszFilename, ".mbtiles") != nullptr) &&
2004 150760 : poOpenInfo->nHeaderBytes >= 1024 && poOpenInfo->pabyHeader &&
2005 161 : STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, "SQLite Format 3"))
2006 : {
2007 161 : return TRUE;
2008 : }
2009 :
2010 75306 : return FALSE;
2011 : }
2012 :
2013 : /************************************************************************/
2014 : /* MBTilesGetMinMaxZoomLevel() */
2015 : /************************************************************************/
2016 :
2017 81 : static int MBTilesGetMinMaxZoomLevel(GDALDatasetH hDS, int bHasMap,
2018 : int &nMinLevel, int &nMaxLevel)
2019 : {
2020 : OGRLayerH hSQLLyr;
2021 : OGRFeatureH hFeat;
2022 81 : int bHasMinMaxLevel = FALSE;
2023 :
2024 81 : const char *pszSQL =
2025 : "SELECT value FROM metadata WHERE name = 'minzoom' UNION ALL "
2026 : "SELECT value FROM metadata WHERE name = 'maxzoom'";
2027 81 : CPLDebug("MBTILES", "%s", pszSQL);
2028 81 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2029 81 : if (hSQLLyr)
2030 : {
2031 81 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2032 81 : if (hFeat)
2033 : {
2034 77 : int bHasMinLevel = FALSE;
2035 77 : if (OGR_F_IsFieldSetAndNotNull(hFeat, 0))
2036 : {
2037 77 : nMinLevel = OGR_F_GetFieldAsInteger(hFeat, 0);
2038 77 : bHasMinLevel = TRUE;
2039 : }
2040 77 : OGR_F_Destroy(hFeat);
2041 :
2042 77 : if (bHasMinLevel)
2043 : {
2044 77 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2045 77 : if (hFeat)
2046 : {
2047 77 : if (OGR_F_IsFieldSetAndNotNull(hFeat, 0))
2048 : {
2049 77 : nMaxLevel = OGR_F_GetFieldAsInteger(hFeat, 0);
2050 77 : bHasMinMaxLevel = TRUE;
2051 : }
2052 77 : OGR_F_Destroy(hFeat);
2053 : }
2054 : }
2055 : }
2056 :
2057 81 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2058 : }
2059 :
2060 81 : if (!bHasMinMaxLevel)
2061 : {
2062 : #define OPTIMIZED_FOR_VSICURL
2063 : #ifdef OPTIMIZED_FOR_VSICURL
2064 : int iLevel;
2065 40 : for (iLevel = 0; nMinLevel < 0 && iLevel <= 32; iLevel++)
2066 : {
2067 36 : pszSQL = CPLSPrintf(
2068 : "SELECT zoom_level FROM %s WHERE zoom_level = %d LIMIT 1",
2069 : (bHasMap) ? "map" : "tiles", iLevel);
2070 36 : CPLDebug("MBTILES", "%s", pszSQL);
2071 36 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2072 36 : if (hSQLLyr)
2073 : {
2074 36 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2075 36 : if (hFeat)
2076 : {
2077 3 : nMinLevel = iLevel;
2078 3 : OGR_F_Destroy(hFeat);
2079 : }
2080 36 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2081 : }
2082 : }
2083 :
2084 4 : if (nMinLevel < 0)
2085 1 : return FALSE;
2086 :
2087 99 : for (iLevel = 32; nMaxLevel < 0 && iLevel >= nMinLevel; iLevel--)
2088 : {
2089 96 : pszSQL = CPLSPrintf(
2090 : "SELECT zoom_level FROM %s WHERE zoom_level = %d LIMIT 1",
2091 : (bHasMap) ? "map" : "tiles", iLevel);
2092 96 : CPLDebug("MBTILES", "%s", pszSQL);
2093 96 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2094 96 : if (hSQLLyr)
2095 : {
2096 96 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2097 96 : if (hFeat)
2098 : {
2099 3 : nMaxLevel = iLevel;
2100 3 : bHasMinMaxLevel = TRUE;
2101 3 : OGR_F_Destroy(hFeat);
2102 : }
2103 96 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2104 : }
2105 : }
2106 : #else
2107 : pszSQL = "SELECT min(zoom_level), max(zoom_level) FROM tiles";
2108 : CPLDebug("MBTILES", "%s", pszSQL);
2109 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, NULL, nullptr);
2110 : if (hSQLLyr == NULL)
2111 : {
2112 : return FALSE;
2113 : }
2114 :
2115 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2116 : if (hFeat == NULL)
2117 : {
2118 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2119 : return FALSE;
2120 : }
2121 :
2122 : if (OGR_F_IsFieldSetAndNotNull(hFeat, 0) &&
2123 : OGR_F_IsFieldSetAndNotNull(hFeat, 1))
2124 : {
2125 : nMinLevel = OGR_F_GetFieldAsInteger(hFeat, 0);
2126 : nMaxLevel = OGR_F_GetFieldAsInteger(hFeat, 1);
2127 : bHasMinMaxLevel = TRUE;
2128 : }
2129 :
2130 : OGR_F_Destroy(hFeat);
2131 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2132 : #endif
2133 : }
2134 :
2135 80 : return bHasMinMaxLevel;
2136 : }
2137 :
2138 : /************************************************************************/
2139 : /* MBTilesTileCoordToWorldCoord() */
2140 : /************************************************************************/
2141 :
2142 16 : static double MBTilesTileCoordToWorldCoord(double dfTileCoord, int nZoomLevel)
2143 : {
2144 16 : return -MAX_GM + 2 * MAX_GM * (dfTileCoord / (1 << nZoomLevel));
2145 : }
2146 :
2147 : /************************************************************************/
2148 : /* MBTilesWorldCoordToTileCoord() */
2149 : /************************************************************************/
2150 :
2151 256 : static double MBTilesWorldCoordToTileCoord(double dfWorldCoord, int nZoomLevel)
2152 : {
2153 256 : return (dfWorldCoord + MAX_GM) / (2 * MAX_GM) * (1 << nZoomLevel);
2154 : }
2155 :
2156 : /************************************************************************/
2157 : /* MBTilesGetBounds() */
2158 : /************************************************************************/
2159 :
2160 80 : static bool MBTilesGetBounds(GDALDatasetH hDS, bool bUseBounds, int nMaxLevel,
2161 : double &minX, double &minY, double &maxX,
2162 : double &maxY)
2163 : {
2164 80 : bool bHasBounds = false;
2165 : OGRLayerH hSQLLyr;
2166 : OGRFeatureH hFeat;
2167 :
2168 80 : if (bUseBounds)
2169 : {
2170 79 : const char *pszSQL = "SELECT value FROM metadata WHERE name = 'bounds'";
2171 79 : CPLDebug("MBTILES", "%s", pszSQL);
2172 79 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2173 79 : if (hSQLLyr)
2174 : {
2175 79 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2176 79 : if (hFeat != nullptr)
2177 : {
2178 77 : const char *pszBounds = OGR_F_GetFieldAsString(hFeat, 0);
2179 77 : char **papszTokens = CSLTokenizeString2(pszBounds, ",", 0);
2180 77 : if (CSLCount(papszTokens) != 4 ||
2181 76 : fabs(CPLAtof(papszTokens[0])) > 180 ||
2182 60 : fabs(CPLAtof(papszTokens[1])) >= 89.99 ||
2183 60 : fabs(CPLAtof(papszTokens[2])) > 180 ||
2184 60 : fabs(CPLAtof(papszTokens[3])) >= 89.99 ||
2185 213 : CPLAtof(papszTokens[0]) > CPLAtof(papszTokens[2]) ||
2186 60 : CPLAtof(papszTokens[1]) > CPLAtof(papszTokens[3]))
2187 : {
2188 17 : CPLError(CE_Warning, CPLE_AppDefined,
2189 : "Invalid value for 'bounds' metadata. Ignoring it "
2190 : "and fall back to present tile extent");
2191 : }
2192 : else
2193 : {
2194 60 : minX = CPLAtof(papszTokens[0]);
2195 60 : minY = CPLAtof(papszTokens[1]);
2196 60 : maxX = CPLAtof(papszTokens[2]);
2197 60 : maxY = CPLAtof(papszTokens[3]);
2198 60 : LongLatToSphericalMercator(&minX, &minY);
2199 60 : LongLatToSphericalMercator(&maxX, &maxY);
2200 :
2201 : // Clamp northings
2202 60 : if (maxY > MAX_GM)
2203 0 : maxY = MAX_GM;
2204 60 : if (minY < -MAX_GM)
2205 8 : minY = -MAX_GM;
2206 :
2207 60 : bHasBounds = true;
2208 : }
2209 :
2210 77 : CSLDestroy(papszTokens);
2211 :
2212 77 : OGR_F_Destroy(hFeat);
2213 : }
2214 79 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2215 : }
2216 : }
2217 :
2218 80 : if (!bHasBounds)
2219 : {
2220 : const char *pszSQL =
2221 20 : CPLSPrintf("SELECT min(tile_column), max(tile_column), "
2222 : "min(tile_row), max(tile_row) FROM tiles "
2223 : "WHERE zoom_level = %d",
2224 : nMaxLevel);
2225 20 : CPLDebug("MBTILES", "%s", pszSQL);
2226 20 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2227 20 : if (hSQLLyr == nullptr)
2228 : {
2229 0 : return false;
2230 : }
2231 :
2232 20 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2233 20 : if (hFeat == nullptr)
2234 : {
2235 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2236 0 : return false;
2237 : }
2238 :
2239 20 : if (OGR_F_IsFieldSetAndNotNull(hFeat, 0) &&
2240 4 : OGR_F_IsFieldSetAndNotNull(hFeat, 1) &&
2241 28 : OGR_F_IsFieldSetAndNotNull(hFeat, 2) &&
2242 4 : OGR_F_IsFieldSetAndNotNull(hFeat, 3))
2243 : {
2244 4 : int nMinTileCol = OGR_F_GetFieldAsInteger(hFeat, 0);
2245 4 : int nMaxTileCol = OGR_F_GetFieldAsInteger(hFeat, 1);
2246 4 : int nMinTileRow = OGR_F_GetFieldAsInteger(hFeat, 2);
2247 4 : int nMaxTileRow = OGR_F_GetFieldAsInteger(hFeat, 3);
2248 4 : if (nMaxTileCol < INT_MAX && nMaxTileRow < INT_MAX)
2249 : {
2250 4 : minX = MBTilesTileCoordToWorldCoord(nMinTileCol, nMaxLevel);
2251 4 : minY = MBTilesTileCoordToWorldCoord(nMinTileRow, nMaxLevel);
2252 4 : maxX = MBTilesTileCoordToWorldCoord(nMaxTileCol + 1, nMaxLevel);
2253 4 : maxY = MBTilesTileCoordToWorldCoord(nMaxTileRow + 1, nMaxLevel);
2254 4 : bHasBounds = true;
2255 : }
2256 : }
2257 :
2258 20 : OGR_F_Destroy(hFeat);
2259 20 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2260 : }
2261 :
2262 80 : return bHasBounds;
2263 : }
2264 :
2265 : /************************************************************************/
2266 : /* MBTilesCurlReadCbk() */
2267 : /************************************************************************/
2268 :
2269 : typedef struct
2270 : {
2271 : int nBands;
2272 : int nSize;
2273 : } TileProperties;
2274 :
2275 : /* We spy the data received by CURL for the initial request where we try */
2276 : /* to get a first tile to see its characteristics. We just need the header */
2277 : /* to determine that, so let's make VSICurl stop reading after we have found it
2278 : */
2279 :
2280 2 : static int MBTilesCurlReadCbk(CPL_UNUSED VSILFILE *fp, void *pabyBuffer,
2281 : size_t nBufferSize, void *pfnUserData)
2282 : {
2283 2 : TileProperties *psTP = static_cast<TileProperties *>(pfnUserData);
2284 :
2285 2 : const GByte abyPNGSig[] = {0x89, 0x50, 0x4E, 0x47,
2286 : 0x0D, 0x0A, 0x1A, 0x0A, /* PNG signature */
2287 : 0x00, 0x00, 0x00, 0x0D, /* IHDR length */
2288 : 0x49, 0x48, 0x44, 0x52 /* IHDR chunk */};
2289 :
2290 : /* JPEG SOF0 (Start Of Frame 0) marker */
2291 2 : const GByte abyJPEG1CompSig[] = {0xFF, 0xC0, /* marker */
2292 : 0x00, 0x0B, /* data length = 8 + 1 * 3 */
2293 : 0x08, /* depth : 8 bit */};
2294 2 : const GByte abyJPEG3CompSig[] = {0xFF, 0xC0, /* marker */
2295 : 0x00, 0x11, /* data length = 8 + 3 * 3 */
2296 : 0x08, /* depth : 8 bit */};
2297 :
2298 : int i;
2299 16354 : for (i = 0; i < (int)nBufferSize - (int)sizeof(abyPNGSig); i++)
2300 : {
2301 16352 : if (memcmp(((GByte *)pabyBuffer) + i, abyPNGSig, sizeof(abyPNGSig)) ==
2302 0 : 0 &&
2303 0 : i + sizeof(abyPNGSig) + 4 + 4 + 1 + 1 < nBufferSize)
2304 : {
2305 0 : GByte *ptr = ((GByte *)(pabyBuffer)) + i + (int)sizeof(abyPNGSig);
2306 :
2307 : int nWidth;
2308 0 : memcpy(&nWidth, ptr, 4);
2309 0 : CPL_MSBPTR32(&nWidth);
2310 0 : ptr += 4;
2311 :
2312 : int nHeight;
2313 0 : memcpy(&nHeight, ptr, 4);
2314 0 : CPL_MSBPTR32(&nHeight);
2315 0 : ptr += 4;
2316 :
2317 0 : GByte nDepth = *ptr;
2318 0 : ptr += 1;
2319 :
2320 0 : GByte nColorType = *ptr;
2321 0 : CPLDebug("MBTILES",
2322 : "PNG: nWidth=%d nHeight=%d depth=%d nColorType=%d", nWidth,
2323 : nHeight, nDepth, nColorType);
2324 :
2325 0 : psTP->nBands = -2;
2326 0 : psTP->nSize = nWidth;
2327 0 : if (nWidth == nHeight && nDepth == 8)
2328 : {
2329 0 : if (nColorType == 0)
2330 0 : psTP->nBands = 1; /* Gray */
2331 0 : else if (nColorType == 2)
2332 0 : psTP->nBands = 3; /* RGB */
2333 0 : else if (nColorType == 3)
2334 : {
2335 : /* This might also be a color table with transparency */
2336 : /* but we cannot tell ! */
2337 0 : psTP->nBands = -1;
2338 0 : return TRUE;
2339 : }
2340 0 : else if (nColorType == 4)
2341 0 : psTP->nBands = 2; /* Gray + alpha */
2342 0 : else if (nColorType == 6)
2343 0 : psTP->nBands = 4; /* RGB + alpha */
2344 : }
2345 :
2346 0 : return FALSE;
2347 : }
2348 : }
2349 :
2350 16366 : for (i = 0; i < (int)nBufferSize - ((int)sizeof(abyJPEG1CompSig) + 5); i++)
2351 : {
2352 16364 : if (memcmp(((GByte *)pabyBuffer) + i, abyJPEG1CompSig,
2353 0 : sizeof(abyJPEG1CompSig)) == 0 &&
2354 0 : ((GByte *)pabyBuffer)[sizeof(abyJPEG1CompSig) + 4] == 1)
2355 : {
2356 : GUInt16 nWidth;
2357 0 : memcpy(&nWidth, &(((GByte *)pabyBuffer)[sizeof(abyJPEG1CompSig)]),
2358 : 2);
2359 0 : CPL_MSBPTR16(&nWidth);
2360 : GUInt16 nHeight;
2361 0 : memcpy(&nHeight,
2362 0 : &(((GByte *)pabyBuffer)[sizeof(abyJPEG1CompSig) + 2]), 2);
2363 0 : CPL_MSBPTR16(&nHeight);
2364 :
2365 0 : CPLDebug("MBTILES", "JPEG: nWidth=%d nHeight=%d depth=%d nBands=%d",
2366 : nWidth, nHeight, 8, 1);
2367 :
2368 0 : psTP->nBands = -2;
2369 0 : if (nWidth == nHeight)
2370 : {
2371 0 : psTP->nSize = nWidth;
2372 0 : psTP->nBands = 1;
2373 : }
2374 :
2375 0 : return FALSE;
2376 : }
2377 16364 : else if (memcmp(((GByte *)pabyBuffer) + i, abyJPEG3CompSig,
2378 3 : sizeof(abyJPEG3CompSig)) == 0 &&
2379 3 : ((GByte *)pabyBuffer)[sizeof(abyJPEG3CompSig) + 4] == 3)
2380 : {
2381 : GUInt16 nWidth;
2382 0 : memcpy(&nWidth, &(((GByte *)pabyBuffer)[sizeof(abyJPEG3CompSig)]),
2383 : 2);
2384 0 : CPL_MSBPTR16(&nWidth);
2385 : GUInt16 nHeight;
2386 0 : memcpy(&nHeight,
2387 0 : &(((GByte *)pabyBuffer)[sizeof(abyJPEG3CompSig) + 2]), 2);
2388 0 : CPL_MSBPTR16(&nHeight);
2389 :
2390 0 : CPLDebug("MBTILES", "JPEG: nWidth=%d nHeight=%d depth=%d nBands=%d",
2391 : nWidth, nHeight, 8, 3);
2392 :
2393 0 : psTP->nBands = -2;
2394 0 : if (nWidth == nHeight)
2395 : {
2396 0 : psTP->nSize = nWidth;
2397 0 : psTP->nBands = 3;
2398 : }
2399 :
2400 0 : return FALSE;
2401 : }
2402 : }
2403 :
2404 2 : return TRUE;
2405 : }
2406 :
2407 : /************************************************************************/
2408 : /* MBTilesGetBandCountAndTileSize() */
2409 : /************************************************************************/
2410 :
2411 64 : static int MBTilesGetBandCountAndTileSize(bool bIsVSICURL, GDALDatasetH &hDS,
2412 : int nMaxLevel, int nMinTileRow,
2413 : int nMaxTileRow, int nMinTileCol,
2414 : int nMaxTileCol, int &nTileSize)
2415 : {
2416 : OGRLayerH hSQLLyr;
2417 : OGRFeatureH hFeat;
2418 64 : VSILFILE *fpCURLOGR = nullptr;
2419 64 : int bFirstSelect = TRUE;
2420 :
2421 64 : int nBands = -1;
2422 64 : nTileSize = 0;
2423 :
2424 : /* Get the VSILFILE associated with the OGR SQLite DB */
2425 128 : CPLString osDSName(GDALGetDescription(hDS));
2426 64 : if (bIsVSICURL)
2427 : {
2428 3 : auto poDS = dynamic_cast<OGRSQLiteBaseDataSource *>(
2429 6 : GDALDataset::FromHandle(hDS));
2430 3 : CPLAssert(poDS);
2431 3 : if (poDS)
2432 : {
2433 3 : fpCURLOGR = poDS->GetVSILFILE();
2434 : }
2435 : }
2436 :
2437 : const char *pszSQL =
2438 128 : CPLSPrintf("SELECT tile_data FROM tiles WHERE "
2439 : "tile_column = %d AND tile_row = %d AND zoom_level = %d",
2440 64 : nMinTileCol / 2 + nMaxTileCol / 2,
2441 64 : nMinTileRow / 2 + nMaxTileRow / 2, nMaxLevel);
2442 64 : CPLDebug("MBTILES", "%s", pszSQL);
2443 :
2444 64 : if (fpCURLOGR)
2445 : {
2446 : /* Install a spy on the file connection that will intercept */
2447 : /* PNG or JPEG headers, to interrupt their downloading */
2448 : /* once the header is found. Speeds up dataset opening. */
2449 3 : CPLErrorReset();
2450 : TileProperties tp;
2451 3 : tp.nBands = -1;
2452 3 : tp.nSize = 0;
2453 3 : VSICurlInstallReadCbk(fpCURLOGR, MBTilesCurlReadCbk, &tp, TRUE);
2454 3 : nBands = tp.nBands;
2455 3 : nTileSize = tp.nSize;
2456 :
2457 3 : CPLErrorReset();
2458 3 : CPLPushErrorHandler(CPLQuietErrorHandler);
2459 3 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2460 3 : CPLPopErrorHandler();
2461 :
2462 3 : VSICurlUninstallReadCbk(fpCURLOGR);
2463 :
2464 : /* Did the spy intercept something interesting ? */
2465 : // cppcheck-suppress knownConditionTrueFalse
2466 3 : if (nBands != -1)
2467 : {
2468 0 : CPLErrorReset();
2469 :
2470 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2471 0 : hSQLLyr = nullptr;
2472 :
2473 : // Re-open OGR SQLite DB, because with our spy we have simulated an
2474 : // I/O error that SQLite will have difficulties to recover within
2475 : // the existing connection. This will be fast because
2476 : // the /vsicurl/ cache has cached the already read blocks.
2477 0 : GDALClose(hDS);
2478 0 : hDS = MBTILESOpenSQLiteDB(osDSName.c_str(), GA_ReadOnly);
2479 0 : if (hDS == nullptr)
2480 0 : return -1;
2481 :
2482 : /* Unrecognized form of PNG. Error out */
2483 0 : if (nBands <= 0)
2484 0 : return -1;
2485 :
2486 0 : return nBands;
2487 : }
2488 3 : else if (CPLGetLastErrorType() == CE_Failure)
2489 : {
2490 0 : CPLError(CE_Failure, CPLGetLastErrorNo(), "%s",
2491 : CPLGetLastErrorMsg());
2492 : }
2493 : }
2494 : else
2495 : {
2496 61 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2497 : }
2498 :
2499 : while (true)
2500 : {
2501 81 : if (hSQLLyr == nullptr && bFirstSelect)
2502 : {
2503 17 : bFirstSelect = FALSE;
2504 17 : pszSQL = CPLSPrintf("SELECT tile_data FROM tiles WHERE "
2505 : "zoom_level = %d LIMIT 1",
2506 : nMaxLevel);
2507 17 : CPLDebug("MBTILES", "%s", pszSQL);
2508 17 : hSQLLyr = GDALDatasetExecuteSQL(hDS, pszSQL, nullptr, nullptr);
2509 17 : if (hSQLLyr == nullptr)
2510 0 : return -1;
2511 : }
2512 :
2513 81 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2514 81 : if (hFeat == nullptr)
2515 : {
2516 23 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2517 23 : hSQLLyr = nullptr;
2518 23 : if (!bFirstSelect)
2519 6 : return -1;
2520 : }
2521 : else
2522 58 : break;
2523 : }
2524 :
2525 116 : const CPLString osMemFileName(VSIMemGenerateHiddenFilename("mvt_temp.db"));
2526 :
2527 58 : int nDataSize = 0;
2528 58 : GByte *pabyData = OGR_F_GetFieldAsBinary(hFeat, 0, &nDataSize);
2529 :
2530 58 : VSIFCloseL(VSIFileFromMemBuffer(osMemFileName.c_str(), pabyData, nDataSize,
2531 : FALSE));
2532 :
2533 58 : GDALDatasetH hDSTile = GDALOpenEx(osMemFileName.c_str(), GDAL_OF_RASTER,
2534 : apszAllowedDrivers, nullptr, nullptr);
2535 58 : if (hDSTile == nullptr)
2536 : {
2537 28 : VSIUnlink(osMemFileName.c_str());
2538 28 : OGR_F_Destroy(hFeat);
2539 28 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2540 28 : return -1;
2541 : }
2542 :
2543 30 : nBands = GDALGetRasterCount(hDSTile);
2544 :
2545 17 : if ((nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4) ||
2546 77 : GDALGetRasterXSize(hDSTile) != GDALGetRasterYSize(hDSTile) ||
2547 30 : GDALGetRasterDataType(GDALGetRasterBand(hDSTile, 1)) != GDT_UInt8)
2548 : {
2549 0 : CPLError(CE_Failure, CPLE_NotSupported,
2550 : "Unsupported tile characteristics");
2551 0 : GDALClose(hDSTile);
2552 0 : VSIUnlink(osMemFileName.c_str());
2553 0 : OGR_F_Destroy(hFeat);
2554 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2555 0 : return -1;
2556 : }
2557 :
2558 30 : nTileSize = GDALGetRasterXSize(hDSTile);
2559 : GDALColorTableH hCT =
2560 30 : GDALGetRasterColorTable(GDALGetRasterBand(hDSTile, 1));
2561 30 : if (nBands == 1 && hCT != nullptr)
2562 : {
2563 8 : nBands = 3;
2564 8 : if (GDALGetColorEntryCount(hCT) > 0)
2565 : {
2566 : /* Typical of paletted PNG with transparency */
2567 8 : const GDALColorEntry *psEntry = GDALGetColorEntry(hCT, 0);
2568 8 : if (psEntry->c4 == 0)
2569 0 : nBands = 4;
2570 : }
2571 : }
2572 :
2573 30 : GDALClose(hDSTile);
2574 30 : VSIUnlink(osMemFileName.c_str());
2575 30 : OGR_F_Destroy(hFeat);
2576 30 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2577 :
2578 30 : return nBands;
2579 : }
2580 :
2581 : /************************************************************************/
2582 : /* Open() */
2583 : /************************************************************************/
2584 :
2585 81 : GDALDataset *MBTilesDataset::Open(GDALOpenInfo *poOpenInfo)
2586 : {
2587 162 : CPLString osFileName;
2588 162 : CPLString osTableName;
2589 :
2590 81 : if (!Identify(poOpenInfo))
2591 0 : return nullptr;
2592 :
2593 81 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
2594 53 : (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
2595 16 : (poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0)
2596 : {
2597 0 : return nullptr;
2598 : }
2599 :
2600 : /* -------------------------------------------------------------------- */
2601 : /* Open underlying OGR DB */
2602 : /* -------------------------------------------------------------------- */
2603 :
2604 : GDALDatasetH hDS =
2605 81 : MBTILESOpenSQLiteDB(poOpenInfo->pszFilename, poOpenInfo->eAccess);
2606 :
2607 81 : MBTilesDataset *poDS = nullptr;
2608 :
2609 81 : if (hDS == nullptr)
2610 0 : goto end;
2611 :
2612 : /* -------------------------------------------------------------------- */
2613 : /* Build dataset */
2614 : /* -------------------------------------------------------------------- */
2615 : {
2616 81 : CPLString osMetadataTableName, osRasterTableName;
2617 81 : CPLString osSQL;
2618 : OGRLayerH hMetadataLyr, hRasterLyr;
2619 : OGRFeatureH hFeat;
2620 : int nBands;
2621 81 : OGRLayerH hSQLLyr = nullptr;
2622 81 : int nMinLevel = -1;
2623 81 : int nMaxLevel = -1;
2624 81 : int bHasMinMaxLevel = FALSE;
2625 : int bHasMap;
2626 :
2627 81 : osMetadataTableName = "metadata";
2628 :
2629 : hMetadataLyr =
2630 81 : GDALDatasetGetLayerByName(hDS, osMetadataTableName.c_str());
2631 81 : if (hMetadataLyr == nullptr)
2632 0 : goto end;
2633 :
2634 81 : osRasterTableName += "tiles";
2635 :
2636 81 : hRasterLyr = GDALDatasetGetLayerByName(hDS, osRasterTableName.c_str());
2637 81 : if (hRasterLyr == nullptr)
2638 0 : goto end;
2639 :
2640 81 : bHasMap = GDALDatasetGetLayerByName(hDS, "map") != nullptr;
2641 81 : if (bHasMap)
2642 : {
2643 0 : bHasMap = FALSE;
2644 :
2645 0 : hSQLLyr = GDALDatasetExecuteSQL(
2646 : hDS, "SELECT type FROM sqlite_master WHERE name = 'tiles'",
2647 : nullptr, nullptr);
2648 0 : if (hSQLLyr != nullptr)
2649 : {
2650 0 : hFeat = OGR_L_GetNextFeature(hSQLLyr);
2651 0 : if (hFeat)
2652 : {
2653 0 : if (OGR_F_IsFieldSetAndNotNull(hFeat, 0))
2654 : {
2655 0 : bHasMap = strcmp(OGR_F_GetFieldAsString(hFeat, 0),
2656 0 : "view") == 0;
2657 0 : if (!bHasMap)
2658 : {
2659 0 : CPLDebug("MBTILES", "Weird! 'tiles' is not a view, "
2660 : "but 'map' exists");
2661 : }
2662 : }
2663 0 : OGR_F_Destroy(hFeat);
2664 : }
2665 0 : GDALDatasetReleaseResultSet(hDS, hSQLLyr);
2666 : }
2667 : }
2668 :
2669 : /* --------------------------------------------------------------------
2670 : */
2671 : /* Get minimum and maximum zoom levels */
2672 : /* --------------------------------------------------------------------
2673 : */
2674 :
2675 : bHasMinMaxLevel =
2676 81 : MBTilesGetMinMaxZoomLevel(hDS, bHasMap, nMinLevel, nMaxLevel);
2677 :
2678 : const char *pszZoomLevel =
2679 81 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "ZOOM_LEVEL");
2680 81 : if (pszZoomLevel != nullptr)
2681 0 : nMaxLevel = atoi(pszZoomLevel);
2682 :
2683 81 : if (bHasMinMaxLevel && (nMinLevel < 0 || nMinLevel > nMaxLevel))
2684 : {
2685 0 : CPLError(CE_Failure, CPLE_AppDefined,
2686 : "Inconsistent values : min(zoom_level) = %d, "
2687 : "max(zoom_level) = %d",
2688 : nMinLevel, nMaxLevel);
2689 0 : goto end;
2690 : }
2691 :
2692 81 : if (bHasMinMaxLevel && nMaxLevel > 22)
2693 : {
2694 0 : CPLError(CE_Failure, CPLE_NotSupported,
2695 : "zoom_level > 22 not supported");
2696 0 : goto end;
2697 : }
2698 :
2699 81 : if (!bHasMinMaxLevel)
2700 : {
2701 1 : CPLError(CE_Failure, CPLE_AppDefined,
2702 : "Cannot find min and max zoom_level");
2703 1 : goto end;
2704 : }
2705 :
2706 : /* --------------------------------------------------------------------
2707 : */
2708 : /* Get bounds */
2709 : /* --------------------------------------------------------------------
2710 : */
2711 80 : double dfMinX = 0.0;
2712 80 : double dfMinY = 0.0;
2713 80 : double dfMaxX = 0.0;
2714 80 : double dfMaxY = 0.0;
2715 160 : bool bUseBounds = CPLFetchBool(
2716 80 : const_cast<const char **>(poOpenInfo->papszOpenOptions),
2717 : "USE_BOUNDS", true);
2718 : const char *pszMinX =
2719 80 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MINX");
2720 : const char *pszMinY =
2721 80 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MINY");
2722 : const char *pszMaxX =
2723 80 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAXX");
2724 : const char *pszMaxY =
2725 80 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAXY");
2726 : bool bHasBounds;
2727 80 : if (pszMinX != nullptr && pszMinY != nullptr && pszMaxX != nullptr &&
2728 : pszMaxY != nullptr)
2729 : {
2730 0 : bHasBounds = true;
2731 : }
2732 : else
2733 : {
2734 80 : bHasBounds = MBTilesGetBounds(hDS, bUseBounds, nMaxLevel, dfMinX,
2735 : dfMinY, dfMaxX, dfMaxY);
2736 : }
2737 80 : if (!bHasBounds)
2738 : {
2739 16 : CPLError(CE_Failure, CPLE_AppDefined,
2740 : "Cannot find min and max tile numbers");
2741 16 : goto end;
2742 : }
2743 64 : if (pszMinX != nullptr)
2744 0 : dfMinX = CPLAtof(pszMinX);
2745 64 : if (pszMinY != nullptr)
2746 0 : dfMinY = CPLAtof(pszMinY);
2747 64 : if (pszMaxX != nullptr)
2748 0 : dfMaxX = CPLAtof(pszMaxX);
2749 64 : if (pszMaxY != nullptr)
2750 0 : dfMaxY = CPLAtof(pszMaxY);
2751 :
2752 : /* --------------------------------------------------------------------
2753 : */
2754 : /* Get number of bands */
2755 : /* --------------------------------------------------------------------
2756 : */
2757 : int nMinTileCol =
2758 64 : static_cast<int>(MBTilesWorldCoordToTileCoord(dfMinX, nMaxLevel));
2759 : int nMinTileRow =
2760 64 : static_cast<int>(MBTilesWorldCoordToTileCoord(dfMinY, nMaxLevel));
2761 : int nMaxTileCol =
2762 64 : static_cast<int>(MBTilesWorldCoordToTileCoord(dfMaxX, nMaxLevel));
2763 : int nMaxTileRow =
2764 64 : static_cast<int>(MBTilesWorldCoordToTileCoord(dfMaxY, nMaxLevel));
2765 64 : int nTileSize = 0;
2766 128 : nBands = MBTilesGetBandCountAndTileSize(
2767 64 : STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsicurl/"), hDS,
2768 : nMaxLevel, nMinTileRow, nMaxTileRow, nMinTileCol, nMaxTileCol,
2769 : nTileSize);
2770 64 : bool bFoundRasterTile = nBands > 0;
2771 64 : if (!bFoundRasterTile)
2772 34 : nTileSize = knDEFAULT_BLOCK_SIZE;
2773 :
2774 : // Force 4 bands by default (see #6119)
2775 64 : nBands = 4;
2776 :
2777 64 : const char *pszBandCount = CSLFetchNameValueDef(
2778 64 : poOpenInfo->papszOpenOptions, "BAND_COUNT",
2779 : CPLGetConfigOption("MBTILES_BAND_COUNT", nullptr));
2780 64 : if (pszBandCount)
2781 : {
2782 1 : int nTmpBands = atoi(pszBandCount);
2783 1 : if (nTmpBands >= 1 && nTmpBands <= 4)
2784 1 : nBands = nTmpBands;
2785 : }
2786 :
2787 64 : if (poOpenInfo->eAccess == GA_Update)
2788 : {
2789 : // So that we can edit all potential overviews
2790 4 : nMinLevel = 0;
2791 : }
2792 :
2793 : /* --------------------------------------------------------------------
2794 : */
2795 : /* Set dataset attributes */
2796 : /* --------------------------------------------------------------------
2797 : */
2798 :
2799 64 : poDS = new MBTilesDataset();
2800 64 : poDS->eAccess = poOpenInfo->eAccess;
2801 64 : poDS->hDS = hDS;
2802 64 : poDS->hDB = (sqlite3 *)GDALGetInternalHandle((GDALDatasetH)hDS,
2803 : "SQLITE_HANDLE");
2804 64 : CPLAssert(poDS->hDB != nullptr);
2805 :
2806 : /* poDS will release it from now */
2807 64 : hDS = nullptr;
2808 :
2809 : poDS->m_osClip =
2810 64 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CLIP", "");
2811 64 : poDS->m_nMinZoomLevel = nMinLevel;
2812 64 : bool bRasterOK = poDS->InitRaster(nullptr, nMaxLevel, nBands, nTileSize,
2813 : dfMinX, dfMinY, dfMaxX, dfMaxY);
2814 :
2815 64 : const char *pszFormat = poDS->GetMetadataItem("format");
2816 64 : if (pszFormat != nullptr && EQUAL(pszFormat, "pbf"))
2817 : {
2818 34 : if ((poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
2819 : {
2820 1 : CPLDebug("MBTiles", "This files contain vector tiles, "
2821 : "but driver open in raster-only mode");
2822 1 : delete poDS;
2823 1 : return nullptr;
2824 : }
2825 66 : poDS->InitVector(dfMinX, dfMinY, dfMaxX, dfMaxY,
2826 33 : CPLFetchBool(poOpenInfo->papszOpenOptions,
2827 : "ZOOM_LEVEL_AUTO",
2828 33 : CPLTestBool(CPLGetConfigOption(
2829 : "MVT_ZOOM_LEVEL_AUTO", "NO"))),
2830 33 : CPLFetchBool(poOpenInfo->papszOpenOptions,
2831 : "JSON_FIELD", false));
2832 : }
2833 30 : else if ((pszFormat != nullptr && !EQUAL(pszFormat, "pbf")) ||
2834 : bFoundRasterTile)
2835 : {
2836 30 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
2837 : {
2838 1 : CPLDebug("MBTiles", "This files contain raster tiles, "
2839 : "but driver open in vector-only mode");
2840 1 : delete poDS;
2841 1 : return nullptr;
2842 : }
2843 : }
2844 :
2845 62 : if ((pszFormat == nullptr || !EQUAL(pszFormat, "pbf")) && !bRasterOK)
2846 : {
2847 0 : delete poDS;
2848 0 : return nullptr;
2849 : }
2850 :
2851 62 : if (poDS->eAccess == GA_Update)
2852 : {
2853 4 : if (pszFormat != nullptr &&
2854 4 : (EQUAL(pszFormat, "jpg") || EQUAL(pszFormat, "jpeg")))
2855 : {
2856 0 : poDS->m_eTF = GPKG_TF_JPEG;
2857 : }
2858 4 : else if (pszFormat != nullptr && (EQUAL(pszFormat, "webp")))
2859 : {
2860 0 : poDS->m_eTF = GPKG_TF_WEBP;
2861 : }
2862 :
2863 : const char *pszTF =
2864 4 : CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TILE_FORMAT");
2865 4 : if (pszTF)
2866 : {
2867 0 : poDS->m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2868 0 : if ((pszFormat != nullptr &&
2869 0 : (EQUAL(pszFormat, "jpg") || EQUAL(pszFormat, "jpeg")) &&
2870 0 : poDS->m_eTF != GPKG_TF_JPEG) ||
2871 0 : (pszFormat != nullptr && EQUAL(pszFormat, "webp") &&
2872 0 : poDS->m_eTF != GPKG_TF_WEBP) ||
2873 0 : (pszFormat != nullptr && EQUAL(pszFormat, "png") &&
2874 0 : poDS->m_eTF == GPKG_TF_JPEG))
2875 : {
2876 0 : CPLError(CE_Warning, CPLE_AppDefined,
2877 : "Format metadata = '%s', but TILE_FORMAT='%s'",
2878 : pszFormat, pszTF);
2879 : }
2880 : }
2881 :
2882 4 : poDS->ParseCompressionOptions(poOpenInfo->papszOpenOptions);
2883 : }
2884 :
2885 : /* --------------------------------------------------------------------
2886 : */
2887 : /* Add overview levels as internal datasets */
2888 : /* --------------------------------------------------------------------
2889 : */
2890 75 : for (int iLevel = nMaxLevel - 1; iLevel >= nMinLevel; iLevel--)
2891 : {
2892 45 : MBTilesDataset *poOvrDS = new MBTilesDataset();
2893 45 : poOvrDS->ShareLockWithParentDataset(poDS);
2894 45 : poOvrDS->InitRaster(poDS, iLevel, nBands, nTileSize, dfMinX, dfMinY,
2895 : dfMaxX, dfMaxY);
2896 :
2897 90 : poDS->m_papoOverviewDS = (MBTilesDataset **)CPLRealloc(
2898 45 : poDS->m_papoOverviewDS,
2899 45 : sizeof(MBTilesDataset *) * (poDS->m_nOverviewCount + 1));
2900 45 : poDS->m_papoOverviewDS[poDS->m_nOverviewCount++] = poOvrDS;
2901 :
2902 77 : if (poOvrDS->GetRasterXSize() < 256 &&
2903 32 : poOvrDS->GetRasterYSize() < 256)
2904 : {
2905 32 : break;
2906 : }
2907 : }
2908 :
2909 : /* --------------------------------------------------------------------
2910 : */
2911 : /* Initialize any PAM information. */
2912 : /* --------------------------------------------------------------------
2913 : */
2914 62 : poDS->SetDescription(poOpenInfo->pszFilename);
2915 :
2916 62 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "/vsicurl/"))
2917 59 : poDS->TryLoadXML();
2918 : else
2919 : {
2920 3 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
2921 : }
2922 : }
2923 :
2924 79 : end:
2925 79 : if (hDS)
2926 17 : GDALClose(hDS);
2927 :
2928 79 : return poDS;
2929 : }
2930 :
2931 : /************************************************************************/
2932 : /* Create() */
2933 : /************************************************************************/
2934 :
2935 93 : GDALDataset *MBTilesDataset::Create(const char *pszFilename, int nXSize,
2936 : int nYSize, int nBandsIn, GDALDataType eDT,
2937 : CSLConstList papszOptions)
2938 : {
2939 : #ifdef HAVE_MVT_WRITE_SUPPORT
2940 93 : if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eDT == GDT_Unknown)
2941 : {
2942 41 : char **papszOptionsMod = CSLDuplicate(papszOptions);
2943 41 : papszOptionsMod = CSLSetNameValue(papszOptionsMod, "FORMAT", "MBTILES");
2944 41 : GDALDataset *poRet = OGRMVTWriterDatasetCreate(
2945 : pszFilename, nXSize, nYSize, nBandsIn, eDT, papszOptionsMod);
2946 41 : CSLDestroy(papszOptionsMod);
2947 41 : return poRet;
2948 : }
2949 : #endif
2950 :
2951 52 : MBTilesDataset *poDS = new MBTilesDataset();
2952 52 : if (!poDS->CreateInternal(pszFilename, nXSize, nYSize, nBandsIn, eDT,
2953 : papszOptions))
2954 : {
2955 14 : delete poDS;
2956 14 : poDS = nullptr;
2957 : }
2958 52 : return poDS;
2959 : }
2960 :
2961 : /************************************************************************/
2962 : /* CreateInternal() */
2963 : /************************************************************************/
2964 :
2965 52 : bool MBTilesDataset::CreateInternal(const char *pszFilename, int nXSize,
2966 : int nYSize, int nBandsIn, GDALDataType eDT,
2967 : CSLConstList papszOptions)
2968 : {
2969 52 : if (eDT != GDT_UInt8)
2970 : {
2971 0 : CPLError(CE_Failure, CPLE_NotSupported, "Only Byte supported");
2972 0 : return false;
2973 : }
2974 52 : if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 && nBandsIn != 4)
2975 : {
2976 0 : CPLError(CE_Failure, CPLE_NotSupported,
2977 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or 4 "
2978 : "(RGBA) band dataset supported");
2979 0 : return false;
2980 : }
2981 :
2982 : // for test/debug purposes only. true is the nominal value
2983 52 : m_bPNGSupports2Bands =
2984 52 : CPLTestBool(CPLGetConfigOption("MBTILES_PNG_SUPPORTS_2BANDS", "TRUE"));
2985 52 : m_bPNGSupportsCT =
2986 52 : CPLTestBool(CPLGetConfigOption("MBTILES_PNG_SUPPORTS_CT", "TRUE"));
2987 52 : m_bWriteBounds = CPLFetchBool(const_cast<const char **>(papszOptions),
2988 : "WRITE_BOUNDS", true);
2989 52 : m_bWriteMinMaxZoom = CPLFetchBool(const_cast<const char **>(papszOptions),
2990 : "WRITE_MINMAXZOOM", true);
2991 : int nBlockSize = std::max(
2992 52 : 64, std::min(8192, atoi(CSLFetchNameValueDef(
2993 : papszOptions, "BLOCKSIZE",
2994 52 : CPLSPrintf("%d", knDEFAULT_BLOCK_SIZE)))));
2995 52 : m_osBounds = CSLFetchNameValueDef(papszOptions, "BOUNDS", "");
2996 52 : m_osCenter = CSLFetchNameValueDef(papszOptions, "CENTER", "");
2997 :
2998 52 : VSIUnlink(pszFilename);
2999 52 : SetDescription(pszFilename);
3000 :
3001 : int rc;
3002 52 : if (STARTS_WITH(pszFilename, "/vsi"))
3003 : {
3004 26 : pMyVFS = OGRSQLiteCreateVFS(nullptr, nullptr);
3005 26 : sqlite3_vfs_register(pMyVFS, 0);
3006 26 : rc = sqlite3_open_v2(pszFilename, &hDB,
3007 : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
3008 26 : pMyVFS->zName);
3009 : }
3010 : else
3011 : {
3012 26 : rc = sqlite3_open(pszFilename, &hDB);
3013 : }
3014 :
3015 52 : if (rc != SQLITE_OK)
3016 : {
3017 4 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
3018 4 : return false;
3019 : }
3020 :
3021 48 : sqlite3_exec(hDB, "PRAGMA synchronous = OFF", nullptr, nullptr, nullptr);
3022 :
3023 48 : rc = sqlite3_exec(hDB,
3024 : "CREATE TABLE tiles ("
3025 : "zoom_level INTEGER NOT NULL,"
3026 : "tile_column INTEGER NOT NULL,"
3027 : "tile_row INTEGER NOT NULL,"
3028 : "tile_data BLOB NOT NULL,"
3029 : "UNIQUE (zoom_level, tile_column, tile_row) )",
3030 : nullptr, nullptr, nullptr);
3031 48 : if (rc != SQLITE_OK)
3032 : {
3033 8 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create tiles table");
3034 8 : return false;
3035 : }
3036 :
3037 40 : rc = sqlite3_exec(hDB, "CREATE TABLE metadata (name TEXT, value TEXT)",
3038 : nullptr, nullptr, nullptr);
3039 40 : if (rc != SQLITE_OK)
3040 : {
3041 2 : CPLError(CE_Failure, CPLE_FileIO, "Cannot create metadata table");
3042 2 : return false;
3043 : }
3044 :
3045 : const std::string osName = CSLFetchNameValueDef(
3046 114 : papszOptions, "NAME", CPLGetBasenameSafe(pszFilename).c_str());
3047 38 : char *pszSQL = sqlite3_mprintf(
3048 : "INSERT INTO metadata (name, value) VALUES ('name', '%q')",
3049 : osName.c_str());
3050 38 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3051 38 : sqlite3_free(pszSQL);
3052 :
3053 38 : const char *pszType = CSLFetchNameValueDef(papszOptions, "TYPE", "overlay");
3054 38 : pszSQL = sqlite3_mprintf(
3055 : "INSERT INTO metadata (name, value) VALUES ('type', '%q')", pszType);
3056 38 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3057 38 : sqlite3_free(pszSQL);
3058 :
3059 : const std::string osDescription = CSLFetchNameValueDef(
3060 114 : papszOptions, "DESCRIPTION", CPLGetBasenameSafe(pszFilename).c_str());
3061 38 : pszSQL = sqlite3_mprintf(
3062 : "INSERT INTO metadata (name, value) VALUES ('description', '%q')",
3063 : osDescription.c_str());
3064 38 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3065 38 : sqlite3_free(pszSQL);
3066 :
3067 : const std::string osElevationType =
3068 76 : CSLFetchNameValueDef(papszOptions, "ELEVATION_TYPE", "");
3069 38 : if (!osElevationType.empty())
3070 : {
3071 0 : pszSQL = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES "
3072 : "('elevation_type', '%q')",
3073 : osElevationType.c_str());
3074 0 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3075 0 : sqlite3_free(pszSQL);
3076 : }
3077 :
3078 38 : const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
3079 38 : if (pszTF)
3080 13 : m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
3081 :
3082 38 : const char *pszVersion = CSLFetchNameValueDef(
3083 38 : papszOptions, "VERSION", (m_eTF == GPKG_TF_WEBP) ? "1.3" : "1.1");
3084 38 : pszSQL = sqlite3_mprintf(
3085 : "INSERT INTO metadata (name, value) VALUES ('version', '%q')",
3086 : pszVersion);
3087 38 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3088 38 : sqlite3_free(pszSQL);
3089 :
3090 38 : const char *pszFormat = CSLFetchNameValueDef(
3091 : papszOptions, "FORMAT", GDALMBTilesGetTileFormatName(m_eTF));
3092 38 : pszSQL = sqlite3_mprintf(
3093 : "INSERT INTO metadata (name, value) VALUES ('format', '%q')",
3094 : pszFormat);
3095 38 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3096 38 : sqlite3_free(pszSQL);
3097 :
3098 38 : m_bNew = true;
3099 38 : eAccess = GA_Update;
3100 38 : nRasterXSize = nXSize;
3101 38 : nRasterYSize = nYSize;
3102 :
3103 38 : m_pabyCachedTiles =
3104 38 : (GByte *)VSI_MALLOC3_VERBOSE(4 * 4, nBlockSize, nBlockSize);
3105 38 : if (m_pabyCachedTiles == nullptr)
3106 : {
3107 0 : return false;
3108 : }
3109 :
3110 123 : for (int i = 1; i <= nBandsIn; i++)
3111 85 : SetBand(i, new MBTilesBand(this, nBlockSize));
3112 :
3113 38 : ParseCompressionOptions(papszOptions);
3114 :
3115 38 : return true;
3116 : }
3117 :
3118 : /************************************************************************/
3119 : /* CreateCopy() */
3120 : /************************************************************************/
3121 :
3122 : typedef struct
3123 : {
3124 : const char *pszName;
3125 : GDALResampleAlg eResampleAlg;
3126 : } WarpResamplingAlg;
3127 :
3128 : static const WarpResamplingAlg asResamplingAlg[] = {
3129 : {"NEAREST", GRA_NearestNeighbour},
3130 : {"BILINEAR", GRA_Bilinear},
3131 : {"CUBIC", GRA_Cubic},
3132 : {"CUBICSPLINE", GRA_CubicSpline},
3133 : {"LANCZOS", GRA_Lanczos},
3134 : {"MODE", GRA_Mode},
3135 : {"AVERAGE", GRA_Average},
3136 : {"RMS", GRA_RMS},
3137 : };
3138 :
3139 49 : GDALDataset *MBTilesDataset::CreateCopy(const char *pszFilename,
3140 : GDALDataset *poSrcDS, int /*bStrict*/,
3141 : CSLConstList papszOptions,
3142 : GDALProgressFunc pfnProgress,
3143 : void *pProgressData)
3144 : {
3145 :
3146 49 : int nBands = poSrcDS->GetRasterCount();
3147 49 : if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
3148 : {
3149 2 : CPLError(CE_Failure, CPLE_NotSupported,
3150 : "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or 4 "
3151 : "(RGBA) band dataset supported");
3152 2 : return nullptr;
3153 : }
3154 :
3155 47 : char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", SRS_EPSG_3857);
3156 :
3157 47 : void *hTransformArg = nullptr;
3158 :
3159 : // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
3160 : // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
3161 : // EPSG:3857.
3162 47 : GDALGeoTransform srcGT;
3163 47 : std::unique_ptr<GDALDataset> poTmpDS;
3164 47 : bool bModifiedMaxLat = false;
3165 47 : bool bModifiedMinLat = false;
3166 47 : const auto poSrcSRS = poSrcDS->GetSpatialRef();
3167 94 : if (poSrcDS->GetGeoTransform(srcGT) == CE_None && srcGT[2] == 0 &&
3168 94 : srcGT[4] == 0 && srcGT[5] < 0)
3169 : {
3170 47 : if (poSrcSRS && poSrcSRS->IsGeographic())
3171 : {
3172 31 : double maxLat = srcGT[3];
3173 31 : double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
3174 : // Corresponds to the latitude of MAX_GM
3175 31 : constexpr double MAX_LAT = 85.0511287798066;
3176 31 : if (maxLat > MAX_LAT)
3177 : {
3178 3 : maxLat = MAX_LAT;
3179 3 : bModifiedMaxLat = true;
3180 : }
3181 31 : if (minLat < -MAX_LAT)
3182 : {
3183 3 : minLat = -MAX_LAT;
3184 3 : bModifiedMinLat = true;
3185 : }
3186 31 : if (bModifiedMaxLat || bModifiedMinLat)
3187 : {
3188 6 : CPLStringList aosOptions;
3189 3 : aosOptions.AddString("-of");
3190 3 : aosOptions.AddString("VRT");
3191 3 : aosOptions.AddString("-projwin");
3192 3 : aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
3193 3 : aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
3194 : aosOptions.AddString(CPLSPrintf(
3195 3 : "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
3196 3 : aosOptions.AddString(CPLSPrintf("%.17g", minLat));
3197 : auto psOptions =
3198 3 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
3199 3 : poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
3200 : "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
3201 3 : GDALTranslateOptionsFree(psOptions);
3202 3 : if (poTmpDS)
3203 : {
3204 3 : hTransformArg = GDALCreateGenImgProjTransformer2(
3205 3 : GDALDataset::FromHandle(poTmpDS.get()), nullptr,
3206 : papszTO);
3207 : }
3208 : }
3209 : }
3210 : }
3211 47 : if (hTransformArg == nullptr)
3212 : {
3213 : hTransformArg =
3214 44 : GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
3215 : }
3216 47 : if (hTransformArg == nullptr)
3217 : {
3218 0 : CSLDestroy(papszTO);
3219 0 : return nullptr;
3220 : }
3221 :
3222 47 : GDALTransformerInfo *psInfo = (GDALTransformerInfo *)hTransformArg;
3223 47 : GDALGeoTransform gt;
3224 : double adfExtent[4];
3225 : int nXSize, nYSize;
3226 :
3227 47 : if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
3228 : gt.data(), &nXSize, &nYSize, adfExtent,
3229 47 : 0) != CE_None)
3230 : {
3231 0 : CSLDestroy(papszTO);
3232 0 : GDALDestroyGenImgProjTransformer(hTransformArg);
3233 0 : return nullptr;
3234 : }
3235 :
3236 47 : GDALDestroyGenImgProjTransformer(hTransformArg);
3237 47 : hTransformArg = nullptr;
3238 47 : poTmpDS.reset();
3239 :
3240 47 : if (bModifiedMaxLat || bModifiedMinLat)
3241 : {
3242 3 : if (bModifiedMaxLat)
3243 : {
3244 3 : const double maxNorthing = MAX_GM;
3245 3 : gt.yorig = maxNorthing;
3246 3 : adfExtent[3] = maxNorthing;
3247 : }
3248 3 : if (bModifiedMinLat)
3249 : {
3250 3 : const double minNorthing = -MAX_GM;
3251 3 : adfExtent[1] = minNorthing;
3252 : }
3253 :
3254 3 : if (poSrcSRS && poSrcSRS->IsGeographic())
3255 : {
3256 3 : if (srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1] == 180)
3257 : {
3258 3 : adfExtent[2] = MAX_GM;
3259 : }
3260 : }
3261 : }
3262 :
3263 : int nZoomLevel;
3264 47 : double dfComputedRes = gt.xscale;
3265 47 : double dfPrevRes = 0.0;
3266 47 : double dfRes = 0.0;
3267 : int nBlockSize = std::max(
3268 47 : 64, std::min(8192, atoi(CSLFetchNameValueDef(
3269 : papszOptions, "BLOCKSIZE",
3270 47 : CPLSPrintf("%d", knDEFAULT_BLOCK_SIZE)))));
3271 47 : const double dfPixelXSizeZoomLevel0 = 2 * MAX_GM / nBlockSize;
3272 444 : for (nZoomLevel = 0; nZoomLevel < 25; nZoomLevel++)
3273 : {
3274 444 : dfRes = dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
3275 444 : if (dfComputedRes > dfRes)
3276 47 : break;
3277 397 : dfPrevRes = dfRes;
3278 : }
3279 47 : if (nZoomLevel == 25)
3280 : {
3281 0 : CPLError(CE_Failure, CPLE_AppDefined,
3282 : "Could not find an appropriate zoom level");
3283 0 : CSLDestroy(papszTO);
3284 0 : return nullptr;
3285 : }
3286 :
3287 : const char *pszZoomLevelStrategy =
3288 47 : CSLFetchNameValueDef(papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
3289 47 : if (fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
3290 : {
3291 47 : if (EQUAL(pszZoomLevelStrategy, "LOWER"))
3292 : {
3293 3 : if (nZoomLevel > 0)
3294 3 : nZoomLevel--;
3295 : }
3296 44 : else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
3297 : {
3298 : /* do nothing */
3299 : }
3300 44 : else if (nZoomLevel > 0)
3301 : {
3302 43 : if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
3303 41 : nZoomLevel--;
3304 : }
3305 : }
3306 :
3307 47 : dfRes = dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
3308 :
3309 47 : double dfMinX = adfExtent[0];
3310 47 : double dfMinY = adfExtent[1];
3311 47 : double dfMaxX = adfExtent[2];
3312 47 : double dfMaxY = adfExtent[3];
3313 :
3314 47 : nXSize = (int)(0.5 + (dfMaxX - dfMinX) / dfRes);
3315 47 : nYSize = (int)(0.5 + (dfMaxY - dfMinY) / dfRes);
3316 47 : gt.xscale = dfRes;
3317 47 : gt.yscale = -dfRes;
3318 :
3319 47 : int nTargetBands = nBands;
3320 : /* For grey level or RGB, if there's reprojection involved, add an alpha */
3321 : /* channel */
3322 80 : if ((nBands == 1 &&
3323 47 : poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
3324 : nBands == 3)
3325 : {
3326 86 : OGRSpatialReference oSrcSRS;
3327 43 : oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
3328 43 : oSrcSRS.AutoIdentifyEPSG();
3329 60 : if (oSrcSRS.GetAuthorityCode() == nullptr ||
3330 17 : atoi(oSrcSRS.GetAuthorityCode()) != 3857)
3331 : {
3332 33 : nTargetBands++;
3333 : }
3334 : }
3335 :
3336 47 : GDALResampleAlg eResampleAlg = GRA_Bilinear;
3337 47 : const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
3338 47 : if (pszResampling)
3339 : {
3340 4 : for (size_t iAlg = 0;
3341 4 : iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
3342 : iAlg++)
3343 : {
3344 4 : if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
3345 : {
3346 4 : eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
3347 4 : break;
3348 : }
3349 : }
3350 : }
3351 :
3352 33 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
3353 80 : eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
3354 : {
3355 0 : CPLError(
3356 : CE_Warning, CPLE_AppDefined,
3357 : "Input dataset has a color table, which will likely lead to "
3358 : "bad results when using a resampling method other than "
3359 : "nearest neighbour or mode. Converting the dataset to 24/32 bit "
3360 : "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
3361 : }
3362 :
3363 47 : GDALDataset *poDS = Create(pszFilename, nXSize, nYSize, nTargetBands,
3364 : GDT_UInt8, papszOptions);
3365 47 : if (poDS == nullptr)
3366 : {
3367 14 : CSLDestroy(papszTO);
3368 14 : return nullptr;
3369 : }
3370 33 : poDS->SetGeoTransform(gt);
3371 35 : if (nTargetBands == 1 && nBands == 1 &&
3372 2 : poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
3373 : {
3374 4 : poDS->GetRasterBand(1)->SetColorTable(
3375 2 : poSrcDS->GetRasterBand(1)->GetColorTable());
3376 : }
3377 :
3378 33 : hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
3379 33 : CSLDestroy(papszTO);
3380 33 : if (hTransformArg == nullptr)
3381 : {
3382 0 : CPLError(CE_Failure, CPLE_AppDefined,
3383 : "GDALCreateGenImgProjTransformer2 failed");
3384 0 : delete poDS;
3385 0 : return nullptr;
3386 : }
3387 :
3388 : /* -------------------------------------------------------------------- */
3389 : /* Warp the transformer with a linear approximator */
3390 : /* -------------------------------------------------------------------- */
3391 33 : hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
3392 : hTransformArg, 0.125);
3393 33 : GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
3394 :
3395 : /* -------------------------------------------------------------------- */
3396 : /* Setup warp options. */
3397 : /* -------------------------------------------------------------------- */
3398 33 : GDALWarpOptions *psWO = GDALCreateWarpOptions();
3399 :
3400 33 : psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
3401 33 : psWO->eWorkingDataType = GDT_UInt8;
3402 :
3403 33 : psWO->eResampleAlg = eResampleAlg;
3404 :
3405 33 : psWO->hSrcDS = poSrcDS;
3406 33 : psWO->hDstDS = poDS;
3407 :
3408 33 : psWO->pfnTransformer = GDALApproxTransform;
3409 33 : psWO->pTransformerArg = hTransformArg;
3410 :
3411 33 : psWO->pfnProgress = pfnProgress;
3412 33 : psWO->pProgressArg = pProgressData;
3413 :
3414 : /* -------------------------------------------------------------------- */
3415 : /* Setup band mapping. */
3416 : /* -------------------------------------------------------------------- */
3417 :
3418 33 : if (nBands == 2 || nBands == 4)
3419 2 : psWO->nBandCount = nBands - 1;
3420 : else
3421 31 : psWO->nBandCount = nBands;
3422 :
3423 33 : psWO->panSrcBands = (int *)CPLMalloc(psWO->nBandCount * sizeof(int));
3424 33 : psWO->panDstBands = (int *)CPLMalloc(psWO->nBandCount * sizeof(int));
3425 :
3426 92 : for (int i = 0; i < psWO->nBandCount; i++)
3427 : {
3428 59 : psWO->panSrcBands[i] = i + 1;
3429 59 : psWO->panDstBands[i] = i + 1;
3430 : }
3431 :
3432 33 : if (nBands == 2 || nBands == 4)
3433 : {
3434 2 : psWO->nSrcAlphaBand = nBands;
3435 : }
3436 33 : if (nTargetBands == 2 || nTargetBands == 4)
3437 : {
3438 21 : psWO->nDstAlphaBand = nTargetBands;
3439 : }
3440 :
3441 : /* -------------------------------------------------------------------- */
3442 : /* Initialize and execute the warp. */
3443 : /* -------------------------------------------------------------------- */
3444 33 : GDALWarpOperation oWO;
3445 :
3446 33 : CPLErr eErr = oWO.Initialize(psWO);
3447 33 : if (eErr == CE_None)
3448 : {
3449 : /*if( bMulti )
3450 : eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
3451 : else*/
3452 33 : eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
3453 : }
3454 33 : if (eErr != CE_None)
3455 : {
3456 0 : delete poDS;
3457 0 : poDS = nullptr;
3458 : }
3459 :
3460 33 : GDALDestroyTransformer(hTransformArg);
3461 33 : GDALDestroyWarpOptions(psWO);
3462 :
3463 33 : return poDS;
3464 : }
3465 :
3466 : /************************************************************************/
3467 : /* ParseCompressionOptions() */
3468 : /************************************************************************/
3469 :
3470 42 : void MBTilesDataset::ParseCompressionOptions(CSLConstList papszOptions)
3471 : {
3472 42 : const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
3473 42 : if (pszZLevel)
3474 0 : m_nZLevel = atoi(pszZLevel);
3475 :
3476 42 : const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
3477 42 : if (pszQuality)
3478 2 : m_nQuality = atoi(pszQuality);
3479 :
3480 42 : const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
3481 42 : if (pszDither)
3482 1 : m_bDither = CPLTestBool(pszDither);
3483 42 : }
3484 :
3485 : /************************************************************************/
3486 : /* IBuildOverviews() */
3487 : /************************************************************************/
3488 :
3489 8 : static int GetFloorPowerOfTwo(int n)
3490 : {
3491 8 : int p2 = 1;
3492 20 : while ((n = n >> 1) > 0)
3493 : {
3494 12 : p2 <<= 1;
3495 : }
3496 8 : return p2;
3497 : }
3498 :
3499 7 : CPLErr MBTilesDataset::IBuildOverviews(
3500 : const char *pszResampling, int nOverviews, const int *panOverviewList,
3501 : int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3502 : void *pProgressData, CSLConstList papszOptions)
3503 : {
3504 7 : if (GetAccess() != GA_Update)
3505 : {
3506 0 : CPLError(CE_Failure, CPLE_NotSupported,
3507 : "Overview building not supported on a database opened in "
3508 : "read-only mode");
3509 0 : return CE_Failure;
3510 : }
3511 7 : if (m_poParentDS != nullptr)
3512 : {
3513 0 : CPLError(CE_Failure, CPLE_NotSupported,
3514 : "Overview building not supported on overview dataset");
3515 0 : return CE_Failure;
3516 : }
3517 :
3518 7 : if (nOverviews == 0)
3519 : {
3520 2 : for (int i = 0; i < m_nOverviewCount; i++)
3521 1 : m_papoOverviewDS[i]->FlushCache(false);
3522 1 : char *pszSQL = sqlite3_mprintf(
3523 : "DELETE FROM 'tiles' WHERE zoom_level < %d", m_nZoomLevel);
3524 1 : char *pszErrMsg = nullptr;
3525 1 : int ret = sqlite3_exec(hDB, pszSQL, nullptr, nullptr, &pszErrMsg);
3526 1 : sqlite3_free(pszSQL);
3527 1 : if (ret != SQLITE_OK)
3528 : {
3529 0 : CPLError(CE_Failure, CPLE_AppDefined, "Failure: %s",
3530 0 : pszErrMsg ? pszErrMsg : "");
3531 0 : sqlite3_free(pszErrMsg);
3532 0 : return CE_Failure;
3533 : }
3534 :
3535 1 : int nRows = 0;
3536 1 : int nCols = 0;
3537 1 : char **papszResult = nullptr;
3538 1 : sqlite3_get_table(
3539 : hDB, "SELECT * FROM metadata WHERE name = 'minzoom' LIMIT 2",
3540 : &papszResult, &nRows, &nCols, nullptr);
3541 1 : sqlite3_free_table(papszResult);
3542 1 : if (nRows == 1)
3543 : {
3544 1 : pszSQL = sqlite3_mprintf(
3545 : "UPDATE metadata SET value = %d WHERE name = 'minzoom'",
3546 : m_nZoomLevel);
3547 1 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3548 1 : sqlite3_free(pszSQL);
3549 : }
3550 :
3551 1 : return CE_None;
3552 : }
3553 :
3554 6 : if (nBandsIn != nBands)
3555 : {
3556 0 : CPLError(CE_Failure, CPLE_NotSupported,
3557 : "Generation of overviews only"
3558 : "supported when operating on all bands.");
3559 0 : return CE_Failure;
3560 : }
3561 :
3562 6 : if (m_nOverviewCount == 0)
3563 : {
3564 0 : CPLError(CE_Failure, CPLE_AppDefined,
3565 : "Image too small to support overviews");
3566 0 : return CE_Failure;
3567 : }
3568 :
3569 6 : FlushCache(false);
3570 :
3571 44 : const auto GetOverviewIndex = [](int nVal)
3572 : {
3573 44 : int iOvr = -1;
3574 111 : while (nVal > 1)
3575 : {
3576 67 : nVal >>= 1;
3577 67 : iOvr++;
3578 : }
3579 44 : return iOvr;
3580 : };
3581 :
3582 14 : for (int i = 0; i < nOverviews; i++)
3583 : {
3584 8 : if (panOverviewList[i] < 2)
3585 : {
3586 0 : CPLError(CE_Failure, CPLE_IllegalArg,
3587 0 : "Overview factor '%d' must be >= 2", panOverviewList[i]);
3588 0 : return CE_Failure;
3589 : }
3590 :
3591 8 : if (GetFloorPowerOfTwo(panOverviewList[i]) != panOverviewList[i])
3592 : {
3593 0 : CPLError(CE_Failure, CPLE_IllegalArg,
3594 : "Overview factor '%d' is not a power of 2",
3595 0 : panOverviewList[i]);
3596 0 : return CE_Failure;
3597 : }
3598 8 : const int iOvr = GetOverviewIndex(panOverviewList[i]);
3599 8 : if (iOvr >= m_nOverviewCount)
3600 : {
3601 1 : CPLDebug("MBTILES",
3602 : "Requested overview factor %d leads to too small overview "
3603 : "and will be ignored",
3604 1 : panOverviewList[i]);
3605 : }
3606 : }
3607 :
3608 : GDALRasterBand ***papapoOverviewBands =
3609 6 : (GDALRasterBand ***)CPLCalloc(sizeof(void *), nBands);
3610 6 : int iCurOverview = 0;
3611 27 : for (int iBand = 0; iBand < nBands; iBand++)
3612 : {
3613 42 : papapoOverviewBands[iBand] =
3614 21 : (GDALRasterBand **)CPLCalloc(sizeof(void *), nOverviews);
3615 21 : iCurOverview = 0;
3616 49 : for (int i = 0; i < nOverviews; i++)
3617 : {
3618 28 : const int iOvr = GetOverviewIndex(panOverviewList[i]);
3619 28 : if (iOvr < m_nOverviewCount)
3620 : {
3621 24 : MBTilesDataset *poODS = m_papoOverviewDS[iOvr];
3622 48 : papapoOverviewBands[iBand][iCurOverview] =
3623 24 : poODS->GetRasterBand(iBand + 1);
3624 24 : iCurOverview++;
3625 : }
3626 : }
3627 : }
3628 :
3629 12 : CPLErr eErr = GDALRegenerateOverviewsMultiBand(
3630 6 : nBands, papoBands, iCurOverview, papapoOverviewBands, pszResampling,
3631 : pfnProgress, pProgressData, papszOptions);
3632 :
3633 27 : for (int iBand = 0; iBand < nBands; iBand++)
3634 : {
3635 21 : CPLFree(papapoOverviewBands[iBand]);
3636 : }
3637 6 : CPLFree(papapoOverviewBands);
3638 :
3639 6 : if (eErr == CE_None)
3640 : {
3641 : // Determine new minzoom value from the existing one and the new
3642 : // requested overview levels
3643 6 : int nMinZoom = m_nZoomLevel;
3644 6 : bool bHasMinZoomMetadata = false;
3645 6 : int nRows = 0;
3646 6 : int nCols = 0;
3647 6 : char **papszResult = nullptr;
3648 6 : sqlite3_get_table(
3649 : hDB, "SELECT value FROM metadata WHERE name = 'minzoom' LIMIT 2",
3650 : &papszResult, &nRows, &nCols, nullptr);
3651 6 : if (nRows == 1 && nCols == 1 && papszResult[1])
3652 : {
3653 6 : bHasMinZoomMetadata = true;
3654 6 : nMinZoom = atoi(papszResult[1]);
3655 : }
3656 6 : sqlite3_free_table(papszResult);
3657 6 : if (bHasMinZoomMetadata)
3658 : {
3659 14 : for (int i = 0; i < nOverviews; i++)
3660 : {
3661 8 : const int iOvr = GetOverviewIndex(panOverviewList[i]);
3662 8 : if (iOvr < m_nOverviewCount)
3663 : {
3664 7 : const MBTilesDataset *poODS = m_papoOverviewDS[iOvr];
3665 7 : nMinZoom = std::min(nMinZoom, poODS->m_nZoomLevel);
3666 : }
3667 : }
3668 :
3669 6 : char *pszSQL = sqlite3_mprintf(
3670 : "UPDATE metadata SET value = '%d' WHERE name = 'minzoom'",
3671 : nMinZoom);
3672 6 : sqlite3_exec(hDB, pszSQL, nullptr, nullptr, nullptr);
3673 6 : sqlite3_free(pszSQL);
3674 : }
3675 : }
3676 :
3677 6 : return eErr;
3678 : }
3679 :
3680 : /************************************************************************/
3681 : /* GDALRegister_MBTiles() */
3682 : /************************************************************************/
3683 :
3684 2135 : void GDALRegister_MBTiles()
3685 :
3686 : {
3687 2135 : if (!GDAL_CHECK_VERSION("MBTiles driver"))
3688 0 : return;
3689 :
3690 2135 : if (GDALGetDriverByName("MBTiles") != nullptr)
3691 263 : return;
3692 :
3693 1872 : GDALDriver *poDriver = new GDALDriver();
3694 :
3695 1872 : poDriver->SetDescription("MBTiles");
3696 1872 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
3697 1872 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
3698 1872 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MBTiles");
3699 1872 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
3700 1872 : "drivers/raster/mbtiles.html");
3701 1872 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "mbtiles");
3702 1872 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte");
3703 :
3704 1872 : poDriver->SetMetadataItem(
3705 : GDAL_DMD_OPENOPTIONLIST,
3706 : "<OpenOptionList>"
3707 : " <Option name='ZOOM_LEVEL' scope='raster,vector' type='integer' "
3708 : "description='Zoom level of full resolution. If not specified, maximum "
3709 : "non-empty zoom level'/>"
3710 : " <Option name='BAND_COUNT' scope='raster' type='string-select' "
3711 : "description='Number of raster bands' default='AUTO'>"
3712 : " <Value>AUTO</Value>"
3713 : " <Value>1</Value>"
3714 : " <Value>2</Value>"
3715 : " <Value>3</Value>"
3716 : " <Value>4</Value>"
3717 : " </Option>"
3718 : " <Option name='MINX' scope='raster,vector' type='float' "
3719 : "description='Minimum X of area of interest'/>"
3720 : " <Option name='MINY' scope='raster,vector' type='float' "
3721 : "description='Minimum Y of area of interest'/>"
3722 : " <Option name='MAXX' scope='raster,vector' type='float' "
3723 : "description='Maximum X of area of interest'/>"
3724 : " <Option name='MAXY' scope='raster,vector' type='float' "
3725 : "description='Maximum Y of area of interest'/>"
3726 : " <Option name='USE_BOUNDS' scope='raster,vector' type='boolean' "
3727 : "description='Whether to use the bounds metadata, when available, to "
3728 : "determine the AOI' default='YES'/>" MBTILES_COMPRESSION_OPTIONS
3729 : " <Option name='CLIP' scope='vector' type='boolean' "
3730 : "description='Whether to clip geometries to tile extent' "
3731 : "default='YES'/>"
3732 : " <Option name='ZOOM_LEVEL_AUTO' scope='vector' type='boolean' "
3733 : "description='Whether to auto-select the zoom level for vector layers "
3734 : "according to spatial filter extent. Only for display purpose' "
3735 : "default='NO'/>"
3736 : " <Option name='JSON_FIELD' scope='vector' type='boolean' "
3737 : "description='For vector layers, "
3738 : "whether to put all attributes as a serialized JSon dictionary'/>"
3739 1872 : "</OpenOptionList>");
3740 :
3741 1872 : poDriver->SetMetadataItem(
3742 : GDAL_DMD_CREATIONOPTIONLIST,
3743 : "<CreationOptionList>" MBTILES_RASTER_CREATION_OPTIONS
3744 : #ifdef HAVE_MVT_WRITE_SUPPORT
3745 : MVT_MBTILES_COMMON_DSCO
3746 : #endif
3747 1872 : "</CreationOptionList>");
3748 1872 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
3749 :
3750 : #ifdef HAVE_MVT_WRITE_SUPPORT
3751 1872 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
3752 1872 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
3753 1872 : "Integer Integer64 Real String");
3754 1872 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
3755 1872 : "Boolean Float32");
3756 :
3757 1872 : poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
3758 : #endif
3759 :
3760 : #ifdef ENABLE_SQL_SQLITE_FORMAT
3761 1872 : poDriver->SetMetadataItem("ENABLE_SQL_SQLITE_FORMAT", "YES");
3762 : #endif
3763 1872 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
3764 :
3765 1872 : poDriver->pfnOpen = MBTilesDataset::Open;
3766 1872 : poDriver->pfnIdentify = MBTilesDataset::Identify;
3767 1872 : poDriver->pfnCreateCopy = MBTilesDataset::CreateCopy;
3768 1872 : poDriver->pfnCreate = MBTilesDataset::Create;
3769 :
3770 1872 : GetGDALDriverManager()->RegisterDriver(poDriver);
3771 : }
|