LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mvt - ogrmvtdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2890 3044 94.9 %
Date: 2026-02-12 23:49:34 Functions: 104 106 98.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  MVT Translator
       4             :  * Purpose:  Mapbox Vector Tile decoder
       5             :  * Author:   Even Rouault, Even Rouault <even dot rouault at spatialys dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot 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 "ogrsf_frmts.h"
      19             : #include "cpl_conv.h"
      20             : #include "cpl_json.h"
      21             : #include "cpl_http.h"
      22             : #include "ogr_p.h"
      23             : #include "gdal_thread_pool.h"
      24             : 
      25             : #include "mvt_tile.h"
      26             : #include "mvtutils.h"
      27             : 
      28             : #include "ogr_geos.h"
      29             : 
      30             : #include "gpb.h"
      31             : 
      32             : #include <algorithm>
      33             : #include <memory>
      34             : #include <vector>
      35             : #include <set>
      36             : 
      37             : const char *SRS_EPSG_3857 =
      38             :     "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS "
      39             :     "84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
      40             :     "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY["
      41             :     "\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
      42             :     "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
      43             :     "AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER["
      44             :     "\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_"
      45             :     "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY["
      46             :     "\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION["
      47             :     "\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 "
      48             :     "+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  "
      49             :     "+no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]";
      50             : 
      51             : // WebMercator related constants
      52             : constexpr double kmSPHERICAL_RADIUS = 6378137.0;
      53             : 
      54             : constexpr int knMAX_FILES_PER_DIR = 10000;
      55             : 
      56             : #ifdef HAVE_MVT_WRITE_SUPPORT
      57             : 
      58             : #include <sqlite3.h>
      59             : #include "../sqlite/ogrsqliteutility.h"
      60             : 
      61             : #include "../sqlite/ogrsqlitevfs.h"
      62             : 
      63             : #include "cpl_worker_thread_pool.h"
      64             : 
      65             : #include <mutex>
      66             : 
      67             : // Limitations from https://github.com/mapbox/mapbox-geostats
      68             : constexpr size_t knMAX_COUNT_LAYERS = 1000;
      69             : constexpr size_t knMAX_REPORT_LAYERS = 100;
      70             : constexpr size_t knMAX_COUNT_FIELDS = 1000;
      71             : constexpr size_t knMAX_REPORT_FIELDS = 100;
      72             : constexpr size_t knMAX_COUNT_VALUES = 1000;
      73             : constexpr size_t knMAX_REPORT_VALUES = 100;
      74             : constexpr size_t knMAX_STRING_VALUE_LENGTH = 256;
      75             : constexpr size_t knMAX_LAYER_NAME_LENGTH = 256;
      76             : constexpr size_t knMAX_FIELD_NAME_LENGTH = 256;
      77             : 
      78             : #undef SQLITE_STATIC
      79             : #define SQLITE_STATIC ((sqlite3_destructor_type) nullptr)
      80             : 
      81             : #endif
      82             : 
      83             : /************************************************************************/
      84             : /*                    InitWebMercatorTilingScheme()                     */
      85             : /************************************************************************/
      86             : 
      87        1255 : static void InitWebMercatorTilingScheme(OGRSpatialReference *poSRS,
      88             :                                         double &dfTopX, double &dfTopY,
      89             :                                         double &dfTileDim0)
      90             : {
      91        1255 :     constexpr double kmMAX_GM =
      92             :         kmSPHERICAL_RADIUS * M_PI;  // 20037508.342789244
      93        1255 :     poSRS->SetFromUserInput(SRS_EPSG_3857);
      94        1255 :     dfTopX = -kmMAX_GM;
      95        1255 :     dfTopY = kmMAX_GM;
      96        1255 :     dfTileDim0 = 2 * kmMAX_GM;
      97        1255 : }
      98             : 
      99             : /************************************************************************/
     100             : /*                              GetCmdId()                              */
     101             : /************************************************************************/
     102             : 
     103             : /* For a drawing instruction combining a command id and a command count,
     104             :  * return the command id */
     105        2036 : static unsigned GetCmdId(unsigned int nCmdCountCombined)
     106             : {
     107        2036 :     return nCmdCountCombined & 0x7;
     108             : }
     109             : 
     110             : /************************************************************************/
     111             : /*                            GetCmdCount()                             */
     112             : /************************************************************************/
     113             : 
     114             : /* For a drawing instruction combining a command id and a command count,
     115             :  * return the command count */
     116        6097 : static unsigned GetCmdCount(unsigned int nCmdCountCombined)
     117             : {
     118        6097 :     return nCmdCountCombined >> 3;
     119             : }
     120             : 
     121             : /************************************************************************/
     122             : /*                           OGRMVTLayerBase                            */
     123             : /************************************************************************/
     124             : 
     125             : class OGRMVTLayerBase CPL_NON_FINAL
     126             :     : public OGRLayer,
     127             :       public OGRGetNextFeatureThroughRaw<OGRMVTLayerBase>
     128             : {
     129             :     virtual OGRFeature *GetNextRawFeature() = 0;
     130             : 
     131             :   protected:
     132             :     OGRFeatureDefn *m_poFeatureDefn = nullptr;
     133             : 
     134             :     void InitFields(const CPLJSONObject &oFields,
     135             :                     const CPLJSONArray &oAttributesFromTileStats);
     136             : 
     137             :   public:
     138             :     ~OGRMVTLayerBase() override;
     139             : 
     140        5821 :     OGRFeatureDefn *GetLayerDefn() const override
     141             :     {
     142        5821 :         return m_poFeatureDefn;
     143             :     }
     144             : 
     145        2971 :     DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRMVTLayerBase)
     146             : 
     147             :     int TestCapability(const char *) const override;
     148             : };
     149             : 
     150             : /************************************************************************/
     151             : /*                             OGRMVTLayer                              */
     152             : /************************************************************************/
     153             : 
     154             : class OGRMVTDataset;
     155             : 
     156             : class OGRMVTLayer final : public OGRMVTLayerBase
     157             : {
     158             :     OGRMVTDataset *m_poDS;
     159             :     const GByte *m_pabyDataStart;
     160             :     const GByte *m_pabyDataEnd;
     161             :     const GByte *m_pabyDataCur = nullptr;
     162             :     const GByte *m_pabyDataFeatureStart = nullptr;
     163             :     bool m_bError = false;
     164             :     unsigned int m_nExtent = knDEFAULT_EXTENT;
     165             :     std::vector<CPLString> m_aosKeys;
     166             : 
     167             :     typedef struct
     168             :     {
     169             :         OGRFieldType eType;
     170             :         OGRFieldSubType eSubType;
     171             :         OGRField sValue;
     172             :     } Value;
     173             : 
     174             :     std::vector<Value> m_asValues;
     175             :     GIntBig m_nFID = 0;
     176             :     GIntBig m_nFeatureCount = -1;
     177             :     OGRPolygon m_oClipPoly;
     178             :     double m_dfTileMinX = 0;
     179             :     double m_dfTileMinY = 0;
     180             :     double m_dfTileMaxX = 0;
     181             :     double m_dfTileMaxY = 0;
     182             :     bool m_bEnforceExternalIsClockwise = false;
     183             : 
     184             :     void Init(const CPLJSONObject &oFields,
     185             :               const CPLJSONArray &oAttributesFromTileStats);
     186             :     bool QuickScanFeature(const GByte *pabyData,
     187             :                           const GByte *pabyDataFeatureEnd, bool bScanFields,
     188             :                           bool bScanGeometries, bool &bGeomTypeSet);
     189             :     void GetXY(int nX, int nY, double &dfX, double &dfY);
     190             :     std::unique_ptr<OGRGeometry>
     191             :     ParseGeometry(unsigned int nGeomType, const GByte *pabyDataGeometryEnd);
     192             :     void SanitizeClippedGeometry(std::unique_ptr<OGRGeometry> &poGeom);
     193             : 
     194             :     OGRFeature *GetNextRawFeature() override;
     195             : 
     196             :   public:
     197             :     OGRMVTLayer(OGRMVTDataset *poDS, const char *pszLayerName,
     198             :                 const GByte *pabyData, int nLayerSize,
     199             :                 const CPLJSONObject &oFields,
     200             :                 const CPLJSONArray &oAttributesFromTileStats,
     201             :                 OGRwkbGeometryType eGeomType);
     202             :     ~OGRMVTLayer() override;
     203             : 
     204             :     void ResetReading() override;
     205             : 
     206             :     GIntBig GetFeatureCount(int bForce) override;
     207             : 
     208             :     GDALDataset *GetDataset() override;
     209             : };
     210             : 
     211             : /************************************************************************/
     212             : /*                         OGRMVTDirectoryLayer                         */
     213             : /************************************************************************/
     214             : 
     215             : class OGRMVTDirectoryLayer final : public OGRMVTLayerBase
     216             : {
     217             :     OGRMVTDataset *m_poDS;
     218             :     int m_nZ = 0;
     219             :     bool m_bUseReadDir = true;
     220             :     CPLString m_osDirName;
     221             :     CPLStringList m_aosDirContent;
     222             :     CPLString m_aosSubDirName;
     223             :     CPLStringList m_aosSubDirContent;
     224             :     bool m_bEOF = false;
     225             :     int m_nXIndex = 0;
     226             :     int m_nYIndex = 0;
     227             :     int m_nTileX = -1;
     228             :     int m_nTileY = -1;
     229             :     GDALDataset *m_poCurrentTile = nullptr;
     230             :     bool m_bJsonField = false;
     231             :     bool m_bAddTileFields = false;
     232             :     GIntBig m_nFIDBase = 0;
     233             :     OGREnvelope m_sExtent;
     234             :     int m_nFilterMinX = 0;
     235             :     int m_nFilterMinY = 0;
     236             :     int m_nFilterMaxX = 0;
     237             :     int m_nFilterMaxY = 0;
     238             : 
     239             :     OGRFeature *GetNextRawFeature() override;
     240             :     OGRFeature *CreateFeatureFrom(OGRFeature *poSrcFeature);
     241             :     void ReadNewSubDir();
     242             :     void OpenTile();
     243             :     void OpenTileIfNeeded();
     244             : 
     245             :   public:
     246             :     OGRMVTDirectoryLayer(OGRMVTDataset *poDS, const char *pszLayerName,
     247             :                          const char *pszDirectoryName,
     248             :                          const CPLJSONObject &oFields,
     249             :                          const CPLJSONArray &oAttributesFromTileStats,
     250             :                          bool bJsonField, bool bAddTileFields,
     251             :                          OGRwkbGeometryType eGeomType,
     252             :                          const OGREnvelope *psExtent);
     253             :     ~OGRMVTDirectoryLayer() override;
     254             : 
     255             :     void ResetReading() override;
     256             : 
     257             :     GIntBig GetFeatureCount(int bForce) override;
     258             :     OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
     259             :                       bool bForce) override;
     260             : 
     261             :     OGRErr ISetSpatialFilter(int iGeomField,
     262             :                              const OGRGeometry *poGeom) override;
     263             : 
     264             :     OGRFeature *GetFeature(GIntBig nFID) override;
     265             : 
     266             :     int TestCapability(const char *) const override;
     267             : 
     268             :     GDALDataset *GetDataset() override;
     269             : };
     270             : 
     271             : /************************************************************************/
     272             : /*                            OGRMVTDataset                             */
     273             : /************************************************************************/
     274             : 
     275             : class OGRMVTDataset final : public GDALDataset
     276             : {
     277             :     friend class OGRMVTLayer;
     278             :     friend class OGRMVTDirectoryLayer;
     279             : 
     280             :     GByte *m_pabyData;
     281             :     std::vector<std::unique_ptr<OGRLayer>> m_apoLayers;
     282             :     bool m_bGeoreferenced = false;
     283             :     double m_dfTileDimX = 0.0;
     284             :     double m_dfTileDimY = 0.0;
     285             :     double m_dfTopX = 0.0;
     286             :     double m_dfTopY = 0.0;
     287             :     CPLString m_osMetadataMemFilename;
     288             :     bool m_bClip = true;
     289             :     CPLString m_osTileExtension{"pbf"};
     290             :     OGRSpatialReference *m_poSRS = nullptr;
     291             :     double m_dfTileDim0 =
     292             :         0.0;  // Extent (in CRS units) of a tile at zoom level 0
     293             :     double m_dfTopXOrigin = 0.0;  // top-left X of tile matrix scheme
     294             :     double m_dfTopYOrigin = 0.0;  // top-left Y of tile matrix scheme
     295             :     int m_nTileMatrixWidth0 =
     296             :         1;  // Number of tiles along X axis at zoom level 0
     297             :     int m_nTileMatrixHeight0 =
     298             :         1;  // Number of tiles along Y axis at zoom level 0
     299             : 
     300             :     static GDALDataset *OpenDirectory(GDALOpenInfo *);
     301             : 
     302             :   public:
     303             :     explicit OGRMVTDataset(GByte *pabyData);
     304             :     ~OGRMVTDataset() override;
     305             : 
     306        3543 :     int GetLayerCount() const override
     307             :     {
     308        3543 :         return static_cast<int>(m_apoLayers.size());
     309             :     }
     310             : 
     311             :     const OGRLayer *GetLayer(int) const override;
     312             : 
     313          12 :     int TestCapability(const char *) const override
     314             :     {
     315          12 :         return FALSE;
     316             :     }
     317             : 
     318             :     static GDALDataset *Open(GDALOpenInfo *);
     319             :     static GDALDataset *Open(GDALOpenInfo *, bool bRecurseAllowed);
     320             : 
     321        1048 :     OGRSpatialReference *GetSRS()
     322             :     {
     323        1048 :         return m_poSRS;
     324             :     }
     325             : 
     326         195 :     inline double GetTileDim0() const
     327             :     {
     328         195 :         return m_dfTileDim0;
     329             :     }
     330             : 
     331          78 :     inline double GetTopXOrigin() const
     332             :     {
     333          78 :         return m_dfTopXOrigin;
     334             :     }
     335             : 
     336          78 :     inline double GetTopYOrigin() const
     337             :     {
     338          78 :         return m_dfTopYOrigin;
     339             :     }
     340             : 
     341          90 :     inline int GetTileMatrixWidth0() const
     342             :     {
     343          90 :         return m_nTileMatrixWidth0;
     344             :     }
     345             : 
     346          90 :     inline int GetTileMatrixHeight0() const
     347             :     {
     348          90 :         return m_nTileMatrixHeight0;
     349             :     }
     350             : };
     351             : 
     352             : /************************************************************************/
     353             : /*                          ~OGRMVTLayerBase()                          */
     354             : /************************************************************************/
     355             : 
     356        1092 : OGRMVTLayerBase::~OGRMVTLayerBase()
     357             : {
     358        1092 :     m_poFeatureDefn->Release();
     359        1092 : }
     360             : 
     361             : /************************************************************************/
     362             : /*                             InitFields()                             */
     363             : /************************************************************************/
     364             : 
     365        1091 : void OGRMVTLayerBase::InitFields(const CPLJSONObject &oFields,
     366             :                                  const CPLJSONArray &oAttributesFromTileStats)
     367             : {
     368        1091 :     OGRMVTInitFields(m_poFeatureDefn, oFields, oAttributesFromTileStats);
     369        1091 : }
     370             : 
     371             : /************************************************************************/
     372             : /*                           TestCapability()                           */
     373             : /************************************************************************/
     374             : 
     375          71 : int OGRMVTLayerBase::TestCapability(const char *pszCap) const
     376             : {
     377          71 :     if (EQUAL(pszCap, OLCStringsAsUTF8) || EQUAL(pszCap, OLCFastSpatialFilter))
     378             :     {
     379          25 :         return TRUE;
     380             :     }
     381          46 :     return FALSE;
     382             : }
     383             : 
     384             : /************************************************************************/
     385             : /*                            OGRMVTLayer()                             */
     386             : /************************************************************************/
     387             : 
     388        1065 : OGRMVTLayer::OGRMVTLayer(OGRMVTDataset *poDS, const char *pszLayerName,
     389             :                          const GByte *pabyData, int nLayerSize,
     390             :                          const CPLJSONObject &oFields,
     391             :                          const CPLJSONArray &oAttributesFromTileStats,
     392        1065 :                          OGRwkbGeometryType eGeomType)
     393             :     : m_poDS(poDS), m_pabyDataStart(pabyData),
     394        1065 :       m_pabyDataEnd(pabyData + nLayerSize)
     395             : {
     396        1065 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
     397        1065 :     SetDescription(m_poFeatureDefn->GetName());
     398        1065 :     m_poFeatureDefn->SetGeomType(eGeomType);
     399        1065 :     m_poFeatureDefn->Reference();
     400             : 
     401        1065 :     if (m_poDS->m_bGeoreferenced)
     402             :     {
     403        1021 :         m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poDS->GetSRS());
     404             :     }
     405             : 
     406        1065 :     Init(oFields, oAttributesFromTileStats);
     407             : 
     408        1065 :     GetXY(0, 0, m_dfTileMinX, m_dfTileMaxY);
     409        1065 :     GetXY(m_nExtent, m_nExtent, m_dfTileMaxX, m_dfTileMinY);
     410        1065 :     OGRLinearRing *poLR = new OGRLinearRing();
     411        1065 :     poLR->addPoint(m_dfTileMinX, m_dfTileMinY);
     412        1065 :     poLR->addPoint(m_dfTileMinX, m_dfTileMaxY);
     413        1065 :     poLR->addPoint(m_dfTileMaxX, m_dfTileMaxY);
     414        1065 :     poLR->addPoint(m_dfTileMaxX, m_dfTileMinY);
     415        1065 :     poLR->addPoint(m_dfTileMinX, m_dfTileMinY);
     416        1065 :     m_oClipPoly.addRingDirectly(poLR);
     417             : 
     418             :     // Config option only for tests for now. When set, it ensures that
     419             :     // the first ring (exterior ring) of a polygon is clockwise oriented,
     420             :     // as per the MVT spec.
     421             :     // By default, we are more tolerant and only use reversal of winding order
     422             :     // to detect inner rings.
     423        1065 :     m_bEnforceExternalIsClockwise = CPLTestBool(
     424             :         CPLGetConfigOption("OGR_MVT_ENFORE_EXTERNAL_RING_IS_CLOCKWISE", "NO"));
     425        1065 : }
     426             : 
     427             : /************************************************************************/
     428             : /*                            ~OGRMVTLayer()                            */
     429             : /************************************************************************/
     430             : 
     431        2130 : OGRMVTLayer::~OGRMVTLayer()
     432             : {
     433       22608 :     for (auto &sValue : m_asValues)
     434             :     {
     435       21543 :         if (sValue.eType == OFTString)
     436             :         {
     437       12216 :             CPLFree(sValue.sValue.String);
     438             :         }
     439             :     }
     440        2130 : }
     441             : 
     442             : /************************************************************************/
     443             : /*                                Init()                                */
     444             : /************************************************************************/
     445             : 
     446        1065 : void OGRMVTLayer::Init(const CPLJSONObject &oFields,
     447             :                        const CPLJSONArray &oAttributesFromTileStats)
     448             : {
     449             :     // First pass to collect keys and values
     450        1065 :     const GByte *pabyData = m_pabyDataStart;
     451        1065 :     const GByte *pabyDataLimit = m_pabyDataEnd;
     452        1065 :     unsigned int nKey = 0;
     453        1065 :     bool bGeomTypeSet = false;
     454        1065 :     const bool bScanFields = !oFields.IsValid();
     455        1065 :     const bool bScanGeometries = m_poFeatureDefn->GetGeomType() == wkbUnknown;
     456        1065 :     const bool bQuickScanFeature = bScanFields || bScanGeometries;
     457             : 
     458             :     try
     459             :     {
     460       45387 :         while (pabyData < pabyDataLimit)
     461             :         {
     462       44322 :             READ_FIELD_KEY(nKey);
     463       44322 :             if (nKey == MAKE_KEY(knLAYER_KEYS, WT_DATA))
     464             :             {
     465       16928 :                 char *pszKey = nullptr;
     466       16928 :                 READ_TEXT(pabyData, pabyDataLimit, pszKey);
     467       16928 :                 m_aosKeys.push_back(pszKey);
     468       16928 :                 CPLFree(pszKey);
     469             :             }
     470       27394 :             else if (nKey == MAKE_KEY(knLAYER_VALUES, WT_DATA))
     471             :             {
     472       21544 :                 unsigned int nValueLength = 0;
     473       21544 :                 READ_SIZE(pabyData, pabyDataLimit, nValueLength);
     474       21544 :                 const GByte *pabyDataValueEnd = pabyData + nValueLength;
     475       21544 :                 READ_VARUINT32(pabyData, pabyDataLimit, nKey);
     476       21544 :                 if (nKey == MAKE_KEY(knVALUE_STRING, WT_DATA))
     477             :                 {
     478       12216 :                     char *pszValue = nullptr;
     479       12216 :                     READ_TEXT(pabyData, pabyDataLimit, pszValue);
     480             :                     Value sValue;
     481       12216 :                     sValue.eType = OFTString;
     482       12216 :                     sValue.eSubType = OFSTNone;
     483       12216 :                     sValue.sValue.String = pszValue;
     484       12216 :                     m_asValues.push_back(sValue);
     485             :                 }
     486        9328 :                 else if (nKey == MAKE_KEY(knVALUE_FLOAT, WT_32BIT))
     487             :                 {
     488             :                     Value sValue;
     489         604 :                     sValue.eType = OFTReal;
     490         604 :                     sValue.eSubType = OFSTFloat32;
     491         604 :                     sValue.sValue.Real = ReadFloat32(&pabyData, pabyDataLimit);
     492         604 :                     m_asValues.push_back(sValue);
     493             :                 }
     494        8724 :                 else if (nKey == MAKE_KEY(knVALUE_DOUBLE, WT_64BIT))
     495             :                 {
     496             :                     Value sValue;
     497         693 :                     sValue.eType = OFTReal;
     498         693 :                     sValue.eSubType = OFSTNone;
     499         693 :                     sValue.sValue.Real = ReadFloat64(&pabyData, pabyDataLimit);
     500         693 :                     m_asValues.push_back(sValue);
     501             :                 }
     502        8031 :                 else if (nKey == MAKE_KEY(knVALUE_INT, WT_VARINT))
     503             :                 {
     504         261 :                     GIntBig nVal = 0;
     505         261 :                     READ_VARINT64(pabyData, pabyDataLimit, nVal);
     506             :                     Value sValue;
     507         203 :                     sValue.eType = (nVal >= INT_MIN && nVal <= INT_MAX)
     508         464 :                                        ? OFTInteger
     509             :                                        : OFTInteger64;
     510         261 :                     sValue.eSubType = OFSTNone;
     511         261 :                     if (sValue.eType == OFTInteger)
     512         140 :                         sValue.sValue.Integer = static_cast<int>(nVal);
     513             :                     else
     514         121 :                         sValue.sValue.Integer64 = nVal;
     515         261 :                     m_asValues.push_back(sValue);
     516             :                 }
     517        7770 :                 else if (nKey == MAKE_KEY(knVALUE_UINT, WT_VARINT))
     518             :                 {
     519        7150 :                     GUIntBig nVal = 0;
     520        7150 :                     READ_VARUINT64(pabyData, pabyDataLimit, nVal);
     521             :                     Value sValue;
     522        7150 :                     sValue.eType =
     523        7150 :                         (nVal <= INT_MAX) ? OFTInteger : OFTInteger64;
     524        7150 :                     sValue.eSubType = OFSTNone;
     525        7150 :                     if (sValue.eType == OFTInteger)
     526        7090 :                         sValue.sValue.Integer = static_cast<int>(nVal);
     527             :                     else
     528          60 :                         sValue.sValue.Integer64 = static_cast<GIntBig>(nVal);
     529        7150 :                     m_asValues.push_back(sValue);
     530             :                 }
     531         620 :                 else if (nKey == MAKE_KEY(knVALUE_SINT, WT_VARINT))
     532             :                 {
     533         490 :                     GIntBig nVal = 0;
     534         490 :                     READ_VARSINT64(pabyData, pabyDataLimit, nVal);
     535             :                     Value sValue;
     536         430 :                     sValue.eType = (nVal >= INT_MIN && nVal <= INT_MAX)
     537         920 :                                        ? OFTInteger
     538             :                                        : OFTInteger64;
     539         490 :                     sValue.eSubType = OFSTNone;
     540         490 :                     if (sValue.eType == OFTInteger)
     541         372 :                         sValue.sValue.Integer = static_cast<int>(nVal);
     542             :                     else
     543         118 :                         sValue.sValue.Integer64 = nVal;
     544         490 :                     m_asValues.push_back(sValue);
     545             :                 }
     546         130 :                 else if (nKey == MAKE_KEY(knVALUE_BOOL, WT_VARINT))
     547             :                 {
     548         129 :                     unsigned nVal = 0;
     549         129 :                     READ_VARUINT32(pabyData, pabyDataLimit, nVal);
     550             :                     Value sValue;
     551         129 :                     sValue.eType = OFTInteger;
     552         129 :                     sValue.eSubType = OFSTBoolean;
     553         129 :                     sValue.sValue.Integer = static_cast<int>(nVal);
     554         129 :                     m_asValues.push_back(sValue);
     555             :                 }
     556             : 
     557       21544 :                 pabyData = pabyDataValueEnd;
     558             :             }
     559        5850 :             else if (nKey == MAKE_KEY(knLAYER_EXTENT, WT_VARINT))
     560             :             {
     561        1052 :                 GUInt32 nExtent = 0;
     562        1052 :                 READ_VARUINT32(pabyData, pabyDataLimit, nExtent);
     563        1052 :                 m_nExtent = std::max(1U, nExtent);  // to avoid divide by zero
     564             :             }
     565             :             else
     566             :             {
     567        4798 :                 SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
     568             :             }
     569             :         }
     570             : 
     571        1065 :         InitFields(oFields, oAttributesFromTileStats);
     572             : 
     573        1065 :         m_nFeatureCount = 0;
     574        1065 :         pabyData = m_pabyDataStart;
     575             :         // Second pass to iterate over features to figure out the geometry type
     576             :         // and attribute schema
     577       45376 :         while (pabyData < pabyDataLimit)
     578             :         {
     579       44314 :             const GByte *pabyDataBefore = pabyData;
     580       44314 :             READ_FIELD_KEY(nKey);
     581       44314 :             if (nKey == MAKE_KEY(knLAYER_FEATURES, WT_DATA))
     582             :             {
     583        2667 :                 if (m_pabyDataFeatureStart == nullptr)
     584             :                 {
     585        1063 :                     m_pabyDataFeatureStart = pabyDataBefore;
     586        1063 :                     m_pabyDataCur = pabyDataBefore;
     587             :                 }
     588             : 
     589        2667 :                 unsigned int nFeatureLength = 0;
     590        2667 :                 READ_SIZE(pabyData, pabyDataLimit, nFeatureLength);
     591        2667 :                 const GByte *pabyDataFeatureEnd = pabyData + nFeatureLength;
     592        2667 :                 if (bQuickScanFeature)
     593             :                 {
     594         526 :                     if (!QuickScanFeature(pabyData, pabyDataFeatureEnd,
     595             :                                           bScanFields, bScanGeometries,
     596             :                                           bGeomTypeSet))
     597             :                     {
     598           3 :                         return;
     599             :                     }
     600             :                 }
     601        2664 :                 pabyData = pabyDataFeatureEnd;
     602             : 
     603        2664 :                 m_nFeatureCount++;
     604             :             }
     605             :             else
     606             :             {
     607       41647 :                 SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
     608             :             }
     609             :         }
     610             :     }
     611           0 :     catch (const GPBException &e)
     612             :     {
     613           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     614             :     }
     615             : }
     616             : 
     617             : /************************************************************************/
     618             : /*                           MergeFieldDefn()                           */
     619             : /************************************************************************/
     620             : 
     621          14 : static void MergeFieldDefn(OGRFieldDefn *poFieldDefn, OGRFieldType eSrcType,
     622             :                            OGRFieldSubType eSrcSubType)
     623             : {
     624          14 :     if (eSrcType == OFTString)
     625             :     {
     626           7 :         poFieldDefn->SetSubType(OFSTNone);
     627           7 :         poFieldDefn->SetType(OFTString);
     628             :     }
     629           7 :     else if (poFieldDefn->GetType() == OFTInteger && eSrcType == OFTInteger64)
     630             :     {
     631           1 :         poFieldDefn->SetSubType(OFSTNone);
     632           1 :         poFieldDefn->SetType(OFTInteger64);
     633             :     }
     634          10 :     else if ((poFieldDefn->GetType() == OFTInteger ||
     635          10 :               poFieldDefn->GetType() == OFTInteger64) &&
     636             :              eSrcType == OFTReal)
     637             :     {
     638           2 :         poFieldDefn->SetSubType(OFSTNone);
     639           2 :         poFieldDefn->SetType(OFTReal);
     640           2 :         poFieldDefn->SetSubType(eSrcSubType);
     641             :     }
     642           4 :     else if (poFieldDefn->GetType() == OFTReal && eSrcType == OFTReal &&
     643             :              eSrcSubType == OFSTNone)
     644             :     {
     645           1 :         poFieldDefn->SetSubType(OFSTNone);
     646             :     }
     647           3 :     else if (poFieldDefn->GetType() == OFTInteger && eSrcType == OFTInteger &&
     648             :              eSrcSubType == OFSTNone)
     649             :     {
     650           1 :         poFieldDefn->SetSubType(OFSTNone);
     651             :     }
     652          14 : }
     653             : 
     654             : /************************************************************************/
     655             : /*                          QuickScanFeature()                          */
     656             : /************************************************************************/
     657             : 
     658         526 : bool OGRMVTLayer::QuickScanFeature(const GByte *pabyData,
     659             :                                    const GByte *pabyDataFeatureEnd,
     660             :                                    bool bScanFields, bool bScanGeometries,
     661             :                                    bool &bGeomTypeSet)
     662             : {
     663         526 :     unsigned int nKey = 0;
     664         526 :     unsigned int nGeomType = 0;
     665             :     try
     666             :     {
     667        2016 :         while (pabyData < pabyDataFeatureEnd)
     668             :         {
     669        1493 :             READ_VARUINT32(pabyData, pabyDataFeatureEnd, nKey);
     670        1493 :             if (nKey == MAKE_KEY(knFEATURE_TYPE, WT_VARINT))
     671             :             {
     672         507 :                 READ_VARUINT32(pabyData, pabyDataFeatureEnd, nGeomType);
     673             :             }
     674         986 :             else if (nKey == MAKE_KEY(knFEATURE_TAGS, WT_DATA) && bScanFields)
     675             :             {
     676          61 :                 unsigned int nTagsSize = 0;
     677          61 :                 READ_SIZE(pabyData, pabyDataFeatureEnd, nTagsSize);
     678          61 :                 const GByte *pabyDataTagsEnd = pabyData + nTagsSize;
     679         240 :                 while (pabyData < pabyDataTagsEnd)
     680             :                 {
     681         182 :                     unsigned int nKeyIdx = 0;
     682         182 :                     unsigned int nValIdx = 0;
     683         182 :                     READ_VARUINT32(pabyData, pabyDataTagsEnd, nKeyIdx);
     684         182 :                     READ_VARUINT32(pabyData, pabyDataTagsEnd, nValIdx);
     685         181 :                     if (nKeyIdx >= m_aosKeys.size())
     686             :                     {
     687           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
     688             :                                  "Invalid tag key index: %u", nKeyIdx);
     689           1 :                         m_bError = true;
     690           1 :                         return false;
     691             :                     }
     692         180 :                     if (nValIdx >= m_asValues.size())
     693             :                     {
     694           1 :                         CPLError(CE_Failure, CPLE_AppDefined,
     695             :                                  "Invalid tag value index: %u", nValIdx);
     696           1 :                         m_bError = true;
     697           1 :                         return false;
     698             :                     }
     699             :                     const int nFieldIdx =
     700         179 :                         m_poFeatureDefn->GetFieldIndex(m_aosKeys[nKeyIdx]);
     701         179 :                     if (nFieldIdx < 0)
     702             :                     {
     703         151 :                         OGRFieldDefn oFieldDefn(m_aosKeys[nKeyIdx],
     704         302 :                                                 m_asValues[nValIdx].eType);
     705         151 :                         oFieldDefn.SetSubType(m_asValues[nValIdx].eSubType);
     706         151 :                         m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
     707             :                     }
     708          56 :                     else if (m_poFeatureDefn->GetFieldDefn(nFieldIdx)
     709          52 :                                      ->GetType() != m_asValues[nValIdx].eType ||
     710          24 :                              m_poFeatureDefn->GetFieldDefn(nFieldIdx)
     711          24 :                                      ->GetSubType() !=
     712          24 :                                  m_asValues[nValIdx].eSubType)
     713             :                     {
     714             :                         OGRFieldDefn *poFieldDefn =
     715           8 :                             m_poFeatureDefn->GetFieldDefn(nFieldIdx);
     716           8 :                         OGRFieldType eSrcType(m_asValues[nValIdx].eType);
     717             :                         OGRFieldSubType eSrcSubType(
     718           8 :                             m_asValues[nValIdx].eSubType);
     719           8 :                         MergeFieldDefn(poFieldDefn, eSrcType, eSrcSubType);
     720             :                     }
     721          58 :                 }
     722             :             }
     723         925 :             else if (nKey == MAKE_KEY(knFEATURE_GEOMETRY, WT_DATA) &&
     724         506 :                      bScanGeometries && nGeomType >= knGEOM_TYPE_POINT &&
     725             :                      nGeomType <= knGEOM_TYPE_POLYGON)
     726             :             {
     727         505 :                 unsigned int nGeometrySize = 0;
     728         505 :                 READ_SIZE(pabyData, pabyDataFeatureEnd, nGeometrySize);
     729         505 :                 const GByte *pabyDataGeometryEnd = pabyData + nGeometrySize;
     730         505 :                 OGRwkbGeometryType eType = wkbUnknown;
     731             : 
     732         505 :                 if (nGeomType == knGEOM_TYPE_POINT)
     733             :                 {
     734          83 :                     eType = wkbPoint;
     735          83 :                     unsigned int nCmdCountCombined = 0;
     736          83 :                     READ_VARUINT32(pabyData, pabyDataGeometryEnd,
     737             :                                    nCmdCountCombined);
     738         166 :                     if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO &&
     739          83 :                         GetCmdCount(nCmdCountCombined) > 1)
     740             :                     {
     741           0 :                         eType = wkbMultiPoint;
     742             :                     }
     743             :                 }
     744         422 :                 else if (nGeomType == knGEOM_TYPE_LINESTRING)
     745             :                 {
     746          16 :                     eType = wkbLineString;
     747          32 :                     for (int iIter = 0; pabyData < pabyDataGeometryEnd; iIter++)
     748             :                     {
     749          17 :                         if (iIter == 1)
     750             :                         {
     751           1 :                             eType = wkbMultiLineString;
     752           1 :                             break;
     753             :                         }
     754          16 :                         unsigned int nCmdCountCombined = 0;
     755             :                         unsigned int nLineToCount;
     756             :                         // Should be a moveto
     757          16 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     758          16 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     759          16 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     760          16 :                         READ_VARUINT32(pabyData, pabyDataGeometryEnd,
     761             :                                        nCmdCountCombined);
     762          16 :                         nLineToCount = GetCmdCount(nCmdCountCombined);
     763          50 :                         for (unsigned i = 0; i < 2 * nLineToCount; i++)
     764             :                         {
     765          34 :                             SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     766             :                         }
     767             :                     }
     768             :                 }
     769             :                 else /* if( nGeomType == knGEOM_TYPE_POLYGON ) */
     770             :                 {
     771         406 :                     eType = wkbPolygon;
     772         812 :                     for (int iIter = 0; pabyData < pabyDataGeometryEnd; iIter++)
     773             :                     {
     774         452 :                         if (iIter == 1)
     775             :                         {
     776          46 :                             eType = wkbMultiPolygon;
     777          46 :                             break;
     778             :                         }
     779         406 :                         unsigned int nCmdCountCombined = 0;
     780             :                         unsigned int nLineToCount;
     781             :                         // Should be a moveto
     782         406 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     783         406 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     784         406 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     785         406 :                         READ_VARUINT32(pabyData, pabyDataGeometryEnd,
     786             :                                        nCmdCountCombined);
     787         406 :                         nLineToCount = GetCmdCount(nCmdCountCombined);
     788        3592 :                         for (unsigned i = 0; i < 2 * nLineToCount; i++)
     789             :                         {
     790        3186 :                             SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     791             :                         }
     792             :                         // Should be a closepath
     793         406 :                         SKIP_VARINT(pabyData, pabyDataGeometryEnd);
     794             :                     }
     795             :                 }
     796             : 
     797         522 :                 if (bGeomTypeSet && m_poFeatureDefn->GetGeomType() ==
     798          17 :                                         OGR_GT_GetCollection(eType))
     799             :                 {
     800             :                     // do nothing
     801             :                 }
     802         512 :                 else if (bGeomTypeSet &&
     803          12 :                          eType == OGR_GT_GetCollection(
     804          12 :                                       m_poFeatureDefn->GetGeomType()))
     805             :                 {
     806           0 :                     m_poFeatureDefn->SetGeomType(eType);
     807             :                 }
     808         512 :                 else if (bGeomTypeSet &&
     809          12 :                          m_poFeatureDefn->GetGeomType() != eType)
     810             :                 {
     811           0 :                     m_poFeatureDefn->SetGeomType(wkbUnknown);
     812             :                 }
     813             :                 else
     814             :                 {
     815         500 :                     m_poFeatureDefn->SetGeomType(eType);
     816             :                 }
     817         505 :                 bGeomTypeSet = true;
     818             : 
     819         505 :                 pabyData = pabyDataGeometryEnd;
     820             :             }
     821             :             else
     822             :             {
     823         420 :                 SKIP_UNKNOWN_FIELD(pabyData, pabyDataFeatureEnd, FALSE);
     824             :             }
     825             :         }
     826         523 :         return true;
     827             :     }
     828           1 :     catch (const GPBException &e)
     829             :     {
     830           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     831           1 :         return false;
     832             :     }
     833             : }
     834             : 
     835             : /************************************************************************/
     836             : /*                          GetFeatureCount()                           */
     837             : /************************************************************************/
     838             : 
     839          51 : GIntBig OGRMVTLayer::GetFeatureCount(int bForce)
     840             : {
     841          51 :     if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr &&
     842          45 :         m_nFeatureCount >= 0)
     843             :     {
     844          45 :         return m_nFeatureCount;
     845             :     }
     846           6 :     return OGRLayer::GetFeatureCount(bForce);
     847             : }
     848             : 
     849             : /************************************************************************/
     850             : /*                            ResetReading()                            */
     851             : /************************************************************************/
     852             : 
     853         133 : void OGRMVTLayer::ResetReading()
     854             : {
     855         133 :     m_nFID = 0;
     856         133 :     m_pabyDataCur = m_pabyDataFeatureStart;
     857         133 : }
     858             : 
     859             : /************************************************************************/
     860             : /*                               GetXY()                                */
     861             : /************************************************************************/
     862             : 
     863      370904 : void OGRMVTLayer::GetXY(int nX, int nY, double &dfX, double &dfY)
     864             : {
     865      370904 :     if (m_poDS->m_bGeoreferenced)
     866             :     {
     867      369341 :         dfX = m_poDS->m_dfTopX + nX * m_poDS->m_dfTileDimX / m_nExtent;
     868      369341 :         dfY = m_poDS->m_dfTopY - nY * m_poDS->m_dfTileDimY / m_nExtent;
     869             :     }
     870             :     else
     871             :     {
     872        1563 :         dfX = nX;
     873        1563 :         dfY = static_cast<double>(m_nExtent) - nY;
     874             :     }
     875      370904 : }
     876             : 
     877             : /************************************************************************/
     878             : /*                      AddWithOverflowAccepted()                       */
     879             : /************************************************************************/
     880             : 
     881             : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
     882      737322 : static int AddWithOverflowAccepted(int a, int b)
     883             : {
     884             :     // In fact in normal situations a+b should not overflow. That can only
     885             :     // happen with corrupted datasets. But we don't really want to add code
     886             :     // to detect that situation, so basically this is just a trick to perform
     887             :     // the addition without the various sanitizers to yell about the overflow.
     888             :     //
     889             :     // Assumes complement-to-two signed integer representation and that
     890             :     // the compiler will safely cast a big unsigned to negative integer.
     891      737322 :     return static_cast<int>(static_cast<unsigned>(a) +
     892      737322 :                             static_cast<unsigned>(b));
     893             : }
     894             : 
     895             : /************************************************************************/
     896             : /*                           ParseGeometry()                            */
     897             : /************************************************************************/
     898             : 
     899             : std::unique_ptr<OGRGeometry>
     900        2112 : OGRMVTLayer::ParseGeometry(unsigned int nGeomType,
     901             :                            const GByte *pabyDataGeometryEnd)
     902             : {
     903             :     try
     904             :     {
     905        2112 :         if (nGeomType == knGEOM_TYPE_POINT)
     906             :         {
     907           1 :             std::unique_ptr<OGRMultiPoint> poMultiPoint;
     908         116 :             unsigned int nCmdCountCombined = 0;
     909             :             unsigned int nCount;
     910         116 :             READ_VARUINT32(m_pabyDataCur, pabyDataGeometryEnd,
     911             :                            nCmdCountCombined);
     912         116 :             nCount = GetCmdCount(nCmdCountCombined);
     913         116 :             if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO && nCount == 1)
     914             :             {
     915         114 :                 int nX = 0;
     916         114 :                 int nY = 0;
     917         114 :                 READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nX);
     918         113 :                 READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nY);
     919             :                 double dfX;
     920             :                 double dfY;
     921         113 :                 GetXY(nX, nY, dfX, dfY);
     922         226 :                 auto poPoint = std::make_unique<OGRPoint>(dfX, dfY);
     923         113 :                 if (m_poFeatureDefn->GetGeomType() == wkbMultiPoint)
     924             :                 {
     925           8 :                     poMultiPoint = std::make_unique<OGRMultiPoint>();
     926           8 :                     poMultiPoint->addGeometry(std::move(poPoint));
     927           8 :                     return poMultiPoint;
     928             :                 }
     929             :                 else
     930             :                 {
     931         105 :                     return poPoint;
     932             :                 }
     933             :             }
     934           2 :             else if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO && nCount > 1)
     935             :             {
     936           2 :                 int nX = 0;
     937           2 :                 int nY = 0;
     938           2 :                 poMultiPoint = std::make_unique<OGRMultiPoint>();
     939           6 :                 for (unsigned i = 0; i < nCount; i++)
     940             :                 {
     941           4 :                     int nDX = 0;
     942           4 :                     int nDY = 0;
     943           4 :                     READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
     944           4 :                     READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
     945             :                     // if( nDX != 0 || nDY != 0 )
     946             :                     {
     947           4 :                         nX = AddWithOverflowAccepted(nX, nDX);
     948           4 :                         nY = AddWithOverflowAccepted(nY, nDY);
     949             :                         double dfX;
     950             :                         double dfY;
     951           4 :                         GetXY(nX, nY, dfX, dfY);
     952           4 :                         auto poPoint = std::make_unique<OGRPoint>(dfX, dfY);
     953           4 :                         if (i == 0 && nCount == 2 &&
     954           2 :                             m_pabyDataCur == pabyDataGeometryEnd)
     955             :                         {
     956             :                             // Current versions of Mapserver at time of writing
     957             :                             // wrongly encode a point with nCount = 2
     958             :                             static bool bWarned = false;
     959           0 :                             if (!bWarned)
     960             :                             {
     961           0 :                                 CPLDebug(
     962             :                                     "MVT",
     963             :                                     "Reading likely a broken point as "
     964             :                                     "produced by some versions of Mapserver");
     965           0 :                                 bWarned = true;
     966             :                             }
     967           0 :                             return poPoint;
     968             :                         }
     969           4 :                         poMultiPoint->addGeometry(std::move(poPoint));
     970             :                     }
     971             :                 }
     972           2 :                 return poMultiPoint;
     973             :             }
     974             :         }
     975        1996 :         else if (nGeomType == knGEOM_TYPE_LINESTRING)
     976             :         {
     977          23 :             std::unique_ptr<OGRMultiLineString> poMultiLS;
     978          23 :             std::unique_ptr<OGRLineString> poLine;
     979          23 :             int nX = 0;
     980          23 :             int nY = 0;
     981          23 :             bool bFirstLine = true;
     982          51 :             while (m_pabyDataCur < pabyDataGeometryEnd)
     983             :             {
     984          28 :                 unsigned int nCmdCountCombined = 0;
     985             :                 unsigned int nLineToCount;
     986             :                 // Should be a moveto
     987          28 :                 SKIP_VARINT(m_pabyDataCur, pabyDataGeometryEnd);
     988          28 :                 int nDX = 0;
     989          28 :                 int nDY = 0;
     990          28 :                 READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
     991          28 :                 READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
     992          28 :                 nX = AddWithOverflowAccepted(nX, nDX);
     993          28 :                 nY = AddWithOverflowAccepted(nY, nDY);
     994             :                 double dfX;
     995             :                 double dfY;
     996          28 :                 GetXY(nX, nY, dfX, dfY);
     997          51 :                 if (!bFirstLine ||
     998          23 :                     m_poFeatureDefn->GetGeomType() == wkbMultiLineString)
     999             :                 {
    1000          22 :                     if (!poMultiLS)
    1001          17 :                         poMultiLS = std::make_unique<OGRMultiLineString>();
    1002          22 :                     if (poLine)
    1003           0 :                         poMultiLS->addGeometry(std::move(poLine));
    1004             :                 }
    1005          28 :                 poLine = std::make_unique<OGRLineString>();
    1006          28 :                 poLine->addPoint(dfX, dfY);
    1007          28 :                 READ_VARUINT32(m_pabyDataCur, pabyDataGeometryEnd,
    1008             :                                nCmdCountCombined);
    1009          28 :                 nLineToCount = GetCmdCount(nCmdCountCombined);
    1010          59 :                 for (unsigned i = 0; i < nLineToCount; i++)
    1011             :                 {
    1012          31 :                     READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
    1013          31 :                     READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
    1014             :                     // if( nDX != 0 || nDY != 0 )
    1015             :                     {
    1016          31 :                         nX = AddWithOverflowAccepted(nX, nDX);
    1017          31 :                         nY = AddWithOverflowAccepted(nY, nDY);
    1018          31 :                         GetXY(nX, nY, dfX, dfY);
    1019          31 :                         poLine->addPoint(dfX, dfY);
    1020             :                     }
    1021             :                 }
    1022          28 :                 if (poMultiLS)
    1023          22 :                     poMultiLS->addGeometry(std::move(poLine));
    1024          28 :                 bFirstLine = false;
    1025             :             }
    1026          23 :             if (poMultiLS)
    1027             :             {
    1028          17 :                 return poMultiLS;
    1029             :             }
    1030             :             else
    1031             :             {
    1032           6 :                 return poLine;
    1033             :             }
    1034             :         }
    1035        1973 :         else if (nGeomType == knGEOM_TYPE_POLYGON)
    1036             :         {
    1037        1973 :             std::unique_ptr<OGRMultiPolygon> poMultiPoly;
    1038        1973 :             std::unique_ptr<OGRPolygon> poPoly;
    1039        1973 :             int externalIsClockwise = 0;
    1040        1973 :             int nX = 0;
    1041        1973 :             int nY = 0;
    1042        1973 :             OGREnvelope sExteriorRingEnvelope;
    1043        5884 :             while (m_pabyDataCur < pabyDataGeometryEnd)
    1044             :             {
    1045        3911 :                 unsigned int nCmdCountCombined = 0;
    1046             :                 unsigned int nLineToCount;
    1047             :                 // Should be a moveto
    1048        3911 :                 SKIP_VARINT(m_pabyDataCur, pabyDataGeometryEnd);
    1049        3911 :                 int nDX = 0;
    1050        3911 :                 int nDY = 0;
    1051        3911 :                 READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
    1052        3911 :                 READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
    1053        3911 :                 nX = AddWithOverflowAccepted(nX, nDX);
    1054        3911 :                 nY = AddWithOverflowAccepted(nY, nDY);
    1055             :                 double dfX;
    1056             :                 double dfY;
    1057        3911 :                 GetXY(nX, nY, dfX, dfY);
    1058        3911 :                 auto poRing = std::make_unique<OGRLinearRing>();
    1059        3911 :                 poRing->addPoint(dfX, dfY);
    1060        3911 :                 READ_VARUINT32(m_pabyDataCur, pabyDataGeometryEnd,
    1061             :                                nCmdCountCombined);
    1062        3911 :                 nLineToCount = GetCmdCount(nCmdCountCombined);
    1063      368598 :                 for (unsigned i = 0; i < nLineToCount; i++)
    1064             :                 {
    1065      364687 :                     READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
    1066      364687 :                     READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
    1067             :                     // if( nDX != 0 || nDY != 0 )
    1068             :                     {
    1069      364687 :                         nX = AddWithOverflowAccepted(nX, nDX);
    1070      364687 :                         nY = AddWithOverflowAccepted(nY, nDY);
    1071      364687 :                         GetXY(nX, nY, dfX, dfY);
    1072      364687 :                         poRing->addPoint(dfX, dfY);
    1073             :                     }
    1074             :                 }
    1075             :                 // Should be a closepath
    1076        3911 :                 SKIP_VARINT(m_pabyDataCur, pabyDataGeometryEnd);
    1077        3911 :                 poRing->closeRings();
    1078        3911 :                 if (!poPoly)
    1079             :                 {
    1080        1973 :                     poPoly = std::make_unique<OGRPolygon>();
    1081        1973 :                     externalIsClockwise = poRing->isClockwise();
    1082        1973 :                     if (!externalIsClockwise)
    1083             :                     {
    1084         743 :                         if (m_bEnforceExternalIsClockwise)
    1085             :                         {
    1086           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    1087             :                                      "Bad ring orientation detected");
    1088           0 :                             return nullptr;
    1089             :                         }
    1090             :                         else
    1091             :                         {
    1092         743 :                             CPLDebugOnce(
    1093             :                                 "MVT",
    1094             :                                 "Bad ring orientation detected. Auto-fixing");
    1095             :                         }
    1096             :                     }
    1097        1973 :                     poPoly->addRing(std::move(poRing));
    1098             :                 }
    1099             :                 else
    1100             :                 {
    1101             :                     // Detect change of winding order to figure out if this is
    1102             :                     // an interior or exterior ring
    1103        1938 :                     if (externalIsClockwise != poRing->isClockwise())
    1104             :                     {
    1105         312 :                         poPoly->addRing(std::move(poRing));
    1106             :                     }
    1107             :                     else
    1108             :                     {
    1109             : #ifdef HAVE_GEOS
    1110             :                         {
    1111             :                             // This block is just to deal with potential bad
    1112             :                             // oriented rings
    1113             :                             // Such as those produced by GDAL < 3.12 in some
    1114             :                             // situations like
    1115             :                             // https://github.com/OSGeo/gdal/issues/13305
    1116        1626 :                             if (!sExteriorRingEnvelope.IsInit())
    1117        1625 :                                 poPoly->getEnvelope(&sExteriorRingEnvelope);
    1118        1626 :                             OGREnvelope sCurRingEnvelope;
    1119        1626 :                             poRing->getEnvelope(&sCurRingEnvelope);
    1120             :                             // Cheap heuristics to detect potentially inner
    1121             :                             // rings
    1122        1626 :                             if (sExteriorRingEnvelope.Contains(
    1123        1626 :                                     sCurRingEnvelope))
    1124             :                             {
    1125             :                                 // Now do the real check
    1126         253 :                                 OGRLineString oLS(*poRing);
    1127         253 :                                 if (poPoly->Contains(&oLS))
    1128             :                                 {
    1129           2 :                                     CPLDebugOnce("MVT",
    1130             :                                                  "Bad ring orientation "
    1131             :                                                  "detected. Auto-fixing");
    1132           2 :                                     poPoly->addRing(std::move(poRing));
    1133           2 :                                     continue;
    1134             :                                 }
    1135             :                             }
    1136             :                         }
    1137             : #endif
    1138             : 
    1139        1624 :                         if (!poMultiPoly)
    1140             :                         {
    1141         750 :                             poMultiPoly = std::make_unique<OGRMultiPolygon>();
    1142             :                         }
    1143        1624 :                         poMultiPoly->addGeometry(std::move(poPoly));
    1144             : 
    1145        1624 :                         poPoly = std::make_unique<OGRPolygon>();
    1146        1624 :                         poPoly->addRing(std::move(poRing));
    1147        1624 :                         sExteriorRingEnvelope = OGREnvelope();
    1148             :                     }
    1149             :                 }
    1150             :             }
    1151        1973 :             if (poMultiPoly)
    1152             :             {
    1153         750 :                 CPLAssert(poPoly);
    1154         750 :                 poMultiPoly->addGeometry(std::move(poPoly));
    1155             :             }
    1156        2446 :             else if (poPoly &&
    1157        2446 :                      m_poFeatureDefn->GetGeomType() == wkbMultiPolygon)
    1158             :             {
    1159         869 :                 poMultiPoly = std::make_unique<OGRMultiPolygon>();
    1160         869 :                 poMultiPoly->addGeometry(std::move(poPoly));
    1161             :             }
    1162        1973 :             if (poMultiPoly)
    1163             :             {
    1164        1619 :                 return poMultiPoly;
    1165             :             }
    1166             :             else
    1167             :             {
    1168         354 :                 return poPoly;
    1169             :             }
    1170             :         }
    1171             :     }
    1172           2 :     catch (const GPBException &e)
    1173             :     {
    1174           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
    1175             :     }
    1176           1 :     return nullptr;
    1177             : }
    1178             : 
    1179             : /************************************************************************/
    1180             : /*                      SanitizeClippedGeometry()                       */
    1181             : /************************************************************************/
    1182             : 
    1183         667 : void OGRMVTLayer::SanitizeClippedGeometry(std::unique_ptr<OGRGeometry> &poGeom)
    1184             : {
    1185         667 :     OGRwkbGeometryType eInGeomType = wkbFlatten(poGeom->getGeometryType());
    1186         667 :     const OGRwkbGeometryType eLayerGeomType = GetGeomType();
    1187         667 :     if (eLayerGeomType == wkbUnknown)
    1188             :     {
    1189           0 :         return;
    1190             :     }
    1191             : 
    1192             :     // GEOS intersection may return a mix of polygon and linestrings when
    1193             :     // intersection a multipolygon and a polygon
    1194         667 :     if (eInGeomType == wkbGeometryCollection)
    1195             :     {
    1196         122 :         std::unique_ptr<OGRGeometryCollection> poTargetGC;
    1197         122 :         const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1198           0 :         std::unique_ptr<OGRGeometry> poTargetSingleGeom;
    1199             :         OGRwkbGeometryType ePartGeom;
    1200         122 :         if (eLayerGeomType == wkbPoint || eLayerGeomType == wkbMultiPoint)
    1201             :         {
    1202           0 :             ePartGeom = wkbPoint;
    1203             :         }
    1204         122 :         else if (eLayerGeomType == wkbLineString ||
    1205             :                  eLayerGeomType == wkbMultiLineString)
    1206             :         {
    1207           0 :             ePartGeom = wkbLineString;
    1208             :         }
    1209             :         else
    1210             :         {
    1211         122 :             ePartGeom = wkbPolygon;
    1212             :         }
    1213         419 :         for (const auto *poSubGeom : poGC)
    1214             :         {
    1215         297 :             if (wkbFlatten(poSubGeom->getGeometryType()) == ePartGeom)
    1216             :             {
    1217         175 :                 if (poTargetSingleGeom)
    1218             :                 {
    1219          53 :                     if (!poTargetGC)
    1220             :                     {
    1221          53 :                         poTargetGC.reset(OGRGeometryFactory::createGeometry(
    1222             :                                              OGR_GT_GetCollection(ePartGeom))
    1223             :                                              ->toGeometryCollection());
    1224             :                         // cppcheck-suppress nullPointerRedundantCheck
    1225          53 :                         poTargetGC->addGeometry(std::move(poTargetSingleGeom));
    1226             :                     }
    1227             : 
    1228          53 :                     poTargetGC->addGeometry(poSubGeom);
    1229             :                 }
    1230             :                 else
    1231             :                 {
    1232         122 :                     poTargetSingleGeom.reset(poSubGeom->clone());
    1233             :                 }
    1234             :             }
    1235             :         }
    1236         122 :         if (poTargetGC)
    1237          53 :             poGeom = std::move(poTargetGC);
    1238          69 :         else if (poTargetSingleGeom)
    1239          69 :             poGeom = std::move(poTargetSingleGeom);
    1240         122 :         eInGeomType = wkbFlatten(poGeom->getGeometryType());
    1241             :     }
    1242             : 
    1243             :     // Wrap single into multi if requested by the layer geometry type
    1244         667 :     if (OGR_GT_GetCollection(eInGeomType) == eLayerGeomType)
    1245             :     {
    1246             :         auto poGC = std::unique_ptr<OGRGeometryCollection>(
    1247             :             OGRGeometryFactory::createGeometry(eLayerGeomType)
    1248         804 :                 ->toGeometryCollection());
    1249         402 :         poGC->addGeometry(std::move(poGeom));
    1250         402 :         poGeom = std::move(poGC);
    1251             :     }
    1252             : }
    1253             : 
    1254             : /************************************************************************/
    1255             : /*                         GetNextRawFeature()                          */
    1256             : /************************************************************************/
    1257             : 
    1258        2897 : OGRFeature *OGRMVTLayer::GetNextRawFeature()
    1259             : {
    1260        2897 :     if (m_pabyDataCur == nullptr || m_pabyDataCur >= m_pabyDataEnd || m_bError)
    1261             :     {
    1262         123 :         return nullptr;
    1263             :     }
    1264             : 
    1265        2774 :     unsigned int nKey = 0;
    1266        2774 :     const GByte *pabyDataLimit = m_pabyDataEnd;
    1267        2774 :     std::unique_ptr<OGRFeature> poFeature;
    1268        2774 :     unsigned int nFeatureLength = 0;
    1269        2774 :     unsigned int nGeomType = 0;
    1270             : 
    1271             :     try
    1272             :     {
    1273             :         while (true)
    1274             :         {
    1275        2779 :             bool bOK = true;
    1276             : 
    1277       34652 :             while (m_pabyDataCur < pabyDataLimit)
    1278             :             {
    1279       33993 :                 READ_VARUINT32(m_pabyDataCur, pabyDataLimit, nKey);
    1280       33993 :                 if (nKey == MAKE_KEY(knLAYER_FEATURES, WT_DATA))
    1281             :                 {
    1282        2120 :                     poFeature = std::make_unique<OGRFeature>(m_poFeatureDefn);
    1283        2120 :                     break;
    1284             :                 }
    1285             :                 else
    1286             :                 {
    1287       31873 :                     SKIP_UNKNOWN_FIELD(m_pabyDataCur, pabyDataLimit, FALSE);
    1288             :                 }
    1289             :             }
    1290             : 
    1291        2779 :             if (poFeature == nullptr)
    1292         659 :                 return nullptr;
    1293             : 
    1294        2120 :             READ_SIZE(m_pabyDataCur, pabyDataLimit, nFeatureLength);
    1295        2120 :             const GByte *pabyDataFeatureEnd = m_pabyDataCur + nFeatureLength;
    1296        8417 :             while (m_pabyDataCur < pabyDataFeatureEnd)
    1297             :             {
    1298        6297 :                 READ_VARUINT32(m_pabyDataCur, pabyDataFeatureEnd, nKey);
    1299        6297 :                 if (nKey == MAKE_KEY(knFEATURE_ID, WT_VARINT))
    1300             :                 {
    1301          14 :                     GUIntBig nID = 0;
    1302          14 :                     READ_VARUINT64(m_pabyDataCur, pabyDataFeatureEnd, nID);
    1303          14 :                     poFeature->SetField("mvt_id", static_cast<GIntBig>(nID));
    1304             :                 }
    1305        6283 :                 else if (nKey == MAKE_KEY(knFEATURE_TYPE, WT_VARINT))
    1306             :                 {
    1307        2114 :                     READ_VARUINT32(m_pabyDataCur, pabyDataFeatureEnd,
    1308             :                                    nGeomType);
    1309             :                 }
    1310        4169 :                 else if (nKey == MAKE_KEY(knFEATURE_TAGS, WT_DATA))
    1311             :                 {
    1312        2055 :                     unsigned int nTagsSize = 0;
    1313        2055 :                     READ_SIZE(m_pabyDataCur, pabyDataFeatureEnd, nTagsSize);
    1314        2055 :                     const GByte *pabyDataTagsEnd = m_pabyDataCur + nTagsSize;
    1315       62992 :                     while (m_pabyDataCur < pabyDataTagsEnd)
    1316             :                     {
    1317       60937 :                         unsigned int nKeyIdx = 0;
    1318       60937 :                         unsigned int nValIdx = 0;
    1319       60937 :                         READ_VARUINT32(m_pabyDataCur, pabyDataTagsEnd, nKeyIdx);
    1320       60937 :                         READ_VARUINT32(m_pabyDataCur, pabyDataTagsEnd, nValIdx);
    1321      121874 :                         if (nKeyIdx < m_aosKeys.size() &&
    1322       60937 :                             nValIdx < m_asValues.size())
    1323             :                         {
    1324             :                             const int nFieldIdx =
    1325       60937 :                                 m_poFeatureDefn->GetFieldIndex(
    1326       60937 :                                     m_aosKeys[nKeyIdx]);
    1327       60937 :                             if (nFieldIdx >= 0)
    1328             :                             {
    1329       60937 :                                 if (m_asValues[nValIdx].eType == OFTString)
    1330             :                                 {
    1331       33105 :                                     poFeature->SetField(
    1332             :                                         nFieldIdx,
    1333       33105 :                                         m_asValues[nValIdx].sValue.String);
    1334             :                                 }
    1335       27832 :                                 else if (m_asValues[nValIdx].eType ==
    1336             :                                          OFTInteger)
    1337             :                                 {
    1338       52608 :                                     poFeature->SetField(
    1339             :                                         nFieldIdx,
    1340       26304 :                                         m_asValues[nValIdx].sValue.Integer);
    1341             :                                 }
    1342        1528 :                                 else if (m_asValues[nValIdx].eType ==
    1343             :                                          OFTInteger64)
    1344             :                                 {
    1345         912 :                                     poFeature->SetField(
    1346             :                                         nFieldIdx,
    1347         456 :                                         m_asValues[nValIdx].sValue.Integer64);
    1348             :                                 }
    1349        1072 :                                 else if (m_asValues[nValIdx].eType == OFTReal)
    1350             :                                 {
    1351        2144 :                                     poFeature->SetField(
    1352             :                                         nFieldIdx,
    1353        1072 :                                         m_asValues[nValIdx].sValue.Real);
    1354             :                                 }
    1355             :                             }
    1356             :                         }
    1357             :                     }
    1358             :                 }
    1359        2114 :                 else if (nKey == MAKE_KEY(knFEATURE_GEOMETRY, WT_DATA) &&
    1360        2112 :                          nGeomType >= 1 && nGeomType <= 3)
    1361             :                 {
    1362        2112 :                     unsigned int nGeometrySize = 0;
    1363        2112 :                     READ_SIZE(m_pabyDataCur, pabyDataFeatureEnd, nGeometrySize);
    1364        2112 :                     const GByte *pabyDataGeometryEnd =
    1365        2112 :                         m_pabyDataCur + nGeometrySize;
    1366        2112 :                     auto poGeom = ParseGeometry(nGeomType, pabyDataGeometryEnd);
    1367        2112 :                     if (poGeom)
    1368             :                     {
    1369             :                         // Clip geometry to tile extent if requested
    1370        2111 :                         if (m_poDS->m_bClip && OGRGeometryFactory::haveGEOS())
    1371             :                         {
    1372        2106 :                             OGREnvelope sEnvelope;
    1373        2106 :                             poGeom->getEnvelope(&sEnvelope);
    1374        2106 :                             if (sEnvelope.MinX >= m_dfTileMinX &&
    1375        1863 :                                 sEnvelope.MinY >= m_dfTileMinY &&
    1376        1701 :                                 sEnvelope.MaxX <= m_dfTileMaxX &&
    1377        1480 :                                 sEnvelope.MaxY <= m_dfTileMaxY)
    1378             :                             {
    1379             :                                 // do nothing
    1380             :                             }
    1381         672 :                             else if (sEnvelope.MinX < m_dfTileMaxX &&
    1382         668 :                                      sEnvelope.MinY < m_dfTileMaxY &&
    1383         668 :                                      sEnvelope.MaxX > m_dfTileMinX &&
    1384         668 :                                      sEnvelope.MaxY > m_dfTileMinY)
    1385             :                             {
    1386             :                                 auto poClipped = std::unique_ptr<OGRGeometry>(
    1387        1334 :                                     poGeom->Intersection(&m_oClipPoly));
    1388         667 :                                 if (poClipped)
    1389             :                                 {
    1390         667 :                                     SanitizeClippedGeometry(poClipped);
    1391         667 :                                     if (poClipped->IsEmpty())
    1392             :                                     {
    1393           0 :                                         bOK = false;
    1394             :                                     }
    1395             :                                     else
    1396             :                                     {
    1397        1334 :                                         poClipped->assignSpatialReference(
    1398         667 :                                             GetSpatialRef());
    1399        1334 :                                         poFeature->SetGeometry(
    1400         667 :                                             std::move(poClipped));
    1401         667 :                                         poGeom.reset();
    1402             :                                     }
    1403         667 :                                 }
    1404             :                             }
    1405             :                             else
    1406             :                             {
    1407           5 :                                 bOK = false;
    1408             :                             }
    1409             :                         }
    1410             : 
    1411        2111 :                         if (poGeom)
    1412             :                         {
    1413        1444 :                             poGeom->assignSpatialReference(GetSpatialRef());
    1414        1444 :                             poFeature->SetGeometry(std::move(poGeom));
    1415             :                         }
    1416             :                     }
    1417             : 
    1418        2112 :                     m_pabyDataCur = pabyDataGeometryEnd;
    1419             :                 }
    1420             :                 else
    1421             :                 {
    1422           2 :                     SKIP_UNKNOWN_FIELD(m_pabyDataCur, pabyDataFeatureEnd,
    1423             :                                        FALSE);
    1424             :                 }
    1425             :             }
    1426        2120 :             m_pabyDataCur = pabyDataFeatureEnd;
    1427             : 
    1428        2120 :             if (bOK)
    1429             :             {
    1430        2115 :                 poFeature->SetFID(m_nFID);
    1431        2115 :                 m_nFID++;
    1432        2115 :                 return poFeature.release();
    1433             :             }
    1434             :             else
    1435             :             {
    1436           5 :                 poFeature.reset();
    1437             :             }
    1438           5 :         }
    1439             :     }
    1440           0 :     catch (const GPBException &e)
    1441             :     {
    1442           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
    1443           0 :         return nullptr;
    1444             :     }
    1445             : }
    1446             : 
    1447             : /************************************************************************/
    1448             : /*                             GetDataset()                             */
    1449             : /************************************************************************/
    1450             : 
    1451           1 : GDALDataset *OGRMVTLayer::GetDataset()
    1452             : {
    1453           1 :     return m_poDS;
    1454             : }
    1455             : 
    1456             : /************************************************************************/
    1457             : /*                         StripDummyEntries()                          */
    1458             : /************************************************************************/
    1459             : 
    1460         122 : static CPLStringList StripDummyEntries(const CPLStringList &aosInput)
    1461             : {
    1462         244 :     CPLStringList aosOutput;
    1463         471 :     for (int i = 0; i < aosInput.Count(); i++)
    1464             :     {
    1465         853 :         if (aosInput[i] != CPLString(".") && aosInput[i] != CPLString("..") &&
    1466         504 :             CPLString(aosInput[i]).find(".properties") == std::string::npos)
    1467             :         {
    1468         155 :             aosOutput.AddString(aosInput[i]);
    1469             :         }
    1470             :     }
    1471         244 :     return aosOutput.Sort();
    1472             : }
    1473             : 
    1474             : /************************************************************************/
    1475             : /*                        OGRMVTDirectoryLayer()                        */
    1476             : /************************************************************************/
    1477             : 
    1478          27 : OGRMVTDirectoryLayer::OGRMVTDirectoryLayer(
    1479             :     OGRMVTDataset *poDS, const char *pszLayerName, const char *pszDirectoryName,
    1480             :     const CPLJSONObject &oFields, const CPLJSONArray &oAttributesFromTileStats,
    1481             :     bool bJsonField, bool bAddTileFields, OGRwkbGeometryType eGeomType,
    1482          27 :     const OGREnvelope *psExtent)
    1483             :     : m_poDS(poDS), m_osDirName(pszDirectoryName), m_bJsonField(bJsonField),
    1484          27 :       m_bAddTileFields(bAddTileFields)
    1485             : {
    1486          27 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
    1487          27 :     SetDescription(m_poFeatureDefn->GetName());
    1488          27 :     m_poFeatureDefn->SetGeomType(eGeomType);
    1489          27 :     m_poFeatureDefn->Reference();
    1490             : 
    1491          27 :     m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
    1492             : 
    1493          27 :     if (m_bAddTileFields)
    1494             :     {
    1495           4 :         OGRFieldDefn oFieldTileZ("tile_z", OFTInteger);
    1496           4 :         OGRFieldDefn oFieldTileX("tile_x", OFTInteger);
    1497           4 :         OGRFieldDefn oFieldTileY("tile_y", OFTInteger);
    1498             : 
    1499           2 :         m_poFeatureDefn->AddFieldDefn(&oFieldTileZ);
    1500           2 :         m_poFeatureDefn->AddFieldDefn(&oFieldTileX);
    1501           2 :         m_poFeatureDefn->AddFieldDefn(&oFieldTileY);
    1502             :     }
    1503             : 
    1504          27 :     if (m_bJsonField)
    1505             :     {
    1506           2 :         OGRFieldDefn oFieldDefnId("mvt_id", OFTInteger64);
    1507           1 :         m_poFeatureDefn->AddFieldDefn(&oFieldDefnId);
    1508             :     }
    1509             :     else
    1510             :     {
    1511          26 :         InitFields(oFields, oAttributesFromTileStats);
    1512             :     }
    1513             : 
    1514          27 :     m_nZ = atoi(CPLGetFilename(m_osDirName));
    1515          27 :     SetMetadataItem("ZOOM_LEVEL", CPLSPrintf("%d", m_nZ));
    1516          27 :     m_bUseReadDir = CPLTestBool(CPLGetConfigOption(
    1517          27 :         "MVT_USE_READDIR", (!STARTS_WITH(m_osDirName, "/vsicurl") &&
    1518          27 :                             !STARTS_WITH(m_osDirName, "http://") &&
    1519          23 :                             !STARTS_WITH(m_osDirName, "https://"))
    1520             :                                ? "YES"
    1521             :                                : "NO"));
    1522          27 :     if (m_bUseReadDir)
    1523             :     {
    1524          22 :         m_aosDirContent = VSIReadDirEx(m_osDirName, knMAX_FILES_PER_DIR);
    1525          22 :         if (m_aosDirContent.Count() >= knMAX_FILES_PER_DIR)
    1526             :         {
    1527           0 :             CPLDebug("MVT", "Disabling readdir");
    1528           0 :             m_aosDirContent.Clear();
    1529           0 :             m_bUseReadDir = false;
    1530             :         }
    1531          22 :         m_aosDirContent = StripDummyEntries(m_aosDirContent);
    1532             :     }
    1533          27 :     OGRMVTDirectoryLayer::ResetReading();
    1534             : 
    1535          27 :     if (psExtent)
    1536             :     {
    1537          20 :         m_sExtent = *psExtent;
    1538             :     }
    1539             : 
    1540          27 :     OGRMVTDirectoryLayer::SetSpatialFilter(nullptr);
    1541             : 
    1542             :     // If the metadata contains an empty fields object, this may be a sign
    1543             :     // that it doesn't know the schema. In that case check if a tile has
    1544             :     // attributes, and in that case create a json field.
    1545          27 :     if (!m_bJsonField && oFields.IsValid() && oFields.GetChildren().empty())
    1546             :     {
    1547          14 :         m_bJsonField = true;
    1548          14 :         OpenTileIfNeeded();
    1549          14 :         m_bJsonField = false;
    1550             : 
    1551          14 :         if (m_poCurrentTile)
    1552             :         {
    1553             :             OGRLayer *poUnderlyingLayer =
    1554          12 :                 m_poCurrentTile->GetLayerByName(GetName());
    1555             :             // There is at least the mvt_id field
    1556          12 :             if (poUnderlyingLayer->GetLayerDefn()->GetFieldCount() > 1)
    1557             :             {
    1558           0 :                 m_bJsonField = true;
    1559             :             }
    1560             :         }
    1561          14 :         OGRMVTDirectoryLayer::ResetReading();
    1562             :     }
    1563             : 
    1564          27 :     if (m_bJsonField)
    1565             :     {
    1566           2 :         OGRFieldDefn oFieldDefn("json", OFTString);
    1567           1 :         m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
    1568             :     }
    1569          27 : }
    1570             : 
    1571             : /************************************************************************/
    1572             : /*                       ~OGRMVTDirectoryLayer()                        */
    1573             : /************************************************************************/
    1574             : 
    1575          54 : OGRMVTDirectoryLayer::~OGRMVTDirectoryLayer()
    1576             : {
    1577          27 :     delete m_poCurrentTile;
    1578          54 : }
    1579             : 
    1580             : /************************************************************************/
    1581             : /*                            ResetReading()                            */
    1582             : /************************************************************************/
    1583             : 
    1584         146 : void OGRMVTDirectoryLayer::ResetReading()
    1585             : {
    1586         146 :     m_bEOF = false;
    1587         146 :     m_nXIndex = -1;
    1588         146 :     m_nYIndex = -1;
    1589         146 :     delete m_poCurrentTile;
    1590         146 :     m_poCurrentTile = nullptr;
    1591         146 : }
    1592             : 
    1593             : /************************************************************************/
    1594             : /*                             IsBetween()                              */
    1595             : /************************************************************************/
    1596             : 
    1597         153 : static bool IsBetween(int nVal, int nMin, int nMax)
    1598             : {
    1599         153 :     return nVal >= nMin && nVal <= nMax;
    1600             : }
    1601             : 
    1602             : /************************************************************************/
    1603             : /*                           ReadNewSubDir()                            */
    1604             : /************************************************************************/
    1605             : 
    1606         123 : void OGRMVTDirectoryLayer::ReadNewSubDir()
    1607             : {
    1608         123 :     delete m_poCurrentTile;
    1609         123 :     m_poCurrentTile = nullptr;
    1610         123 :     if (m_bUseReadDir || !m_aosDirContent.empty())
    1611             :     {
    1612           2 :         while (
    1613         188 :             m_nXIndex < m_aosDirContent.Count() &&
    1614          76 :             (CPLGetValueType(m_aosDirContent[m_nXIndex]) != CPL_VALUE_INTEGER ||
    1615          76 :              !IsBetween(atoi(m_aosDirContent[m_nXIndex]), m_nFilterMinX,
    1616             :                         m_nFilterMaxX)))
    1617             :         {
    1618           2 :             m_nXIndex++;
    1619             :         }
    1620             :     }
    1621             :     else
    1622             :     {
    1623          13 :         if (m_nXIndex < m_nFilterMinX)
    1624           0 :             m_nXIndex = m_nFilterMinX;
    1625          13 :         else if (m_nXIndex > m_nFilterMaxX)
    1626           4 :             m_nXIndex = (1 << m_nZ);
    1627             :     }
    1628         136 :     if (m_nXIndex < ((m_bUseReadDir || !m_aosDirContent.empty())
    1629         123 :                          ? m_aosDirContent.Count()
    1630          13 :                          : (1 << m_nZ)))
    1631             :     {
    1632             :         m_aosSubDirName =
    1633          83 :             CPLFormFilenameSafe(m_osDirName,
    1634          83 :                                 (m_bUseReadDir || !m_aosDirContent.empty())
    1635          74 :                                     ? m_aosDirContent[m_nXIndex]
    1636           9 :                                     : CPLSPrintf("%d", m_nXIndex),
    1637          83 :                                 nullptr);
    1638          83 :         if (m_bUseReadDir)
    1639             :         {
    1640             :             m_aosSubDirContent =
    1641          74 :                 VSIReadDirEx(m_aosSubDirName, knMAX_FILES_PER_DIR);
    1642          74 :             if (m_aosSubDirContent.Count() >= knMAX_FILES_PER_DIR)
    1643             :             {
    1644           0 :                 CPLDebug("MVT", "Disabling readdir");
    1645           0 :                 m_aosSubDirContent.Clear();
    1646           0 :                 m_bUseReadDir = false;
    1647             :             }
    1648          74 :             m_aosSubDirContent = StripDummyEntries(m_aosSubDirContent);
    1649             :         }
    1650          83 :         m_nYIndex = -1;
    1651          83 :         OpenTileIfNeeded();
    1652             :     }
    1653             :     else
    1654             :     {
    1655          40 :         m_bEOF = true;
    1656             :     }
    1657         123 : }
    1658             : 
    1659             : /************************************************************************/
    1660             : /*                              OpenTile()                              */
    1661             : /************************************************************************/
    1662             : 
    1663          86 : void OGRMVTDirectoryLayer::OpenTile()
    1664             : {
    1665          86 :     delete m_poCurrentTile;
    1666          86 :     m_poCurrentTile = nullptr;
    1667          86 :     if (m_nYIndex < (m_bUseReadDir ? m_aosSubDirContent.Count() : (1 << m_nZ)))
    1668             :     {
    1669          86 :         CPLString osFilename = CPLFormFilenameSafe(
    1670             :             m_aosSubDirName,
    1671          86 :             m_bUseReadDir ? m_aosSubDirContent[m_nYIndex]
    1672           9 :                           : CPLSPrintf("%d.%s", m_nYIndex,
    1673           9 :                                        m_poDS->m_osTileExtension.c_str()),
    1674         172 :             nullptr);
    1675          86 :         GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(), GA_ReadOnly);
    1676          86 :         oOpenInfo.papszOpenOptions = CSLSetNameValue(
    1677             :             nullptr, "METADATA_FILE",
    1678          86 :             m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
    1679          86 :         oOpenInfo.papszOpenOptions = CSLSetNameValue(
    1680             :             oOpenInfo.papszOpenOptions, "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
    1681          86 :         m_poCurrentTile =
    1682          86 :             OGRMVTDataset::Open(&oOpenInfo, /* bRecurseAllowed = */ false);
    1683          86 :         CSLDestroy(oOpenInfo.papszOpenOptions);
    1684             : 
    1685           9 :         m_nTileX = (m_bUseReadDir || !m_aosDirContent.empty())
    1686          95 :                        ? atoi(m_aosDirContent[m_nXIndex])
    1687             :                        : m_nXIndex;
    1688          86 :         m_nTileY =
    1689          86 :             m_bUseReadDir ? atoi(m_aosSubDirContent[m_nYIndex]) : m_nYIndex;
    1690          86 :         m_nFIDBase = (static_cast<GIntBig>(m_nTileX) << m_nZ) | m_nTileY;
    1691             :     }
    1692          86 : }
    1693             : 
    1694             : /************************************************************************/
    1695             : /*                          OpenTileIfNeeded()                          */
    1696             : /************************************************************************/
    1697             : 
    1698         244 : void OGRMVTDirectoryLayer::OpenTileIfNeeded()
    1699             : {
    1700         244 :     if (m_nXIndex < 0)
    1701             :     {
    1702          81 :         m_nXIndex = 0;
    1703          81 :         ReadNewSubDir();
    1704             :     }
    1705         616 :     while ((m_poCurrentTile == nullptr && !m_bEOF) ||
    1706         244 :            (m_poCurrentTile != nullptr &&
    1707         197 :             m_poCurrentTile->GetLayerByName(GetName()) == nullptr))
    1708             :     {
    1709         128 :         m_nYIndex++;
    1710         128 :         if (m_bUseReadDir)
    1711             :         {
    1712         192 :             while (m_nYIndex < m_aosSubDirContent.Count() &&
    1713          77 :                    (CPLGetValueType(
    1714         192 :                         CPLGetBasenameSafe(m_aosSubDirContent[m_nYIndex])
    1715          77 :                             .c_str()) != CPL_VALUE_INTEGER ||
    1716          77 :                     !IsBetween(atoi(m_aosSubDirContent[m_nYIndex]),
    1717             :                                m_nFilterMinY, m_nFilterMaxY)))
    1718             :             {
    1719           0 :                 m_nYIndex++;
    1720             :             }
    1721             :         }
    1722             :         else
    1723             :         {
    1724          13 :             if (m_nYIndex < m_nFilterMinY)
    1725           0 :                 m_nYIndex = m_nFilterMinY;
    1726          13 :             else if (m_nYIndex > m_nFilterMaxY)
    1727           4 :                 m_nYIndex = (1 << m_nZ);
    1728             :         }
    1729         128 :         if (m_nYIndex ==
    1730         128 :             (m_bUseReadDir ? m_aosSubDirContent.Count() : (1 << m_nZ)))
    1731             :         {
    1732          42 :             m_nXIndex++;
    1733          42 :             ReadNewSubDir();
    1734             :         }
    1735             :         else
    1736             :         {
    1737          86 :             OpenTile();
    1738             :         }
    1739             :     }
    1740         244 : }
    1741             : 
    1742             : /************************************************************************/
    1743             : /*                          GetFeatureCount()                           */
    1744             : /************************************************************************/
    1745             : 
    1746          16 : GIntBig OGRMVTDirectoryLayer::GetFeatureCount(int bForce)
    1747             : {
    1748          16 :     if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
    1749             :     {
    1750          10 :         GIntBig nFeatureCount = 0;
    1751          10 :         ResetReading();
    1752             :         while (true)
    1753             :         {
    1754          21 :             OpenTileIfNeeded();
    1755          21 :             if (m_poCurrentTile == nullptr)
    1756          10 :                 break;
    1757             :             OGRLayer *poUnderlyingLayer =
    1758          11 :                 m_poCurrentTile->GetLayerByName(GetName());
    1759          11 :             nFeatureCount += poUnderlyingLayer->GetFeatureCount(bForce);
    1760          11 :             delete m_poCurrentTile;
    1761          11 :             m_poCurrentTile = nullptr;
    1762          11 :         }
    1763          10 :         ResetReading();
    1764          10 :         return nFeatureCount;
    1765             :     }
    1766           6 :     return OGRLayer::GetFeatureCount(bForce);
    1767             : }
    1768             : 
    1769             : /************************************************************************/
    1770             : /*                         ISetSpatialFilter()                          */
    1771             : /************************************************************************/
    1772             : 
    1773          51 : OGRErr OGRMVTDirectoryLayer::ISetSpatialFilter(int iGeomField,
    1774             :                                                const OGRGeometry *poGeomIn)
    1775             : {
    1776          51 :     OGRLayer::ISetSpatialFilter(iGeomField, poGeomIn);
    1777             : 
    1778          51 :     OGREnvelope sEnvelope;
    1779          51 :     if (m_poFilterGeom != nullptr)
    1780           7 :         sEnvelope = m_sFilterEnvelope;
    1781          51 :     if (m_sExtent.IsInit())
    1782             :     {
    1783          44 :         if (sEnvelope.IsInit())
    1784           7 :             sEnvelope.Intersect(m_sExtent);
    1785             :         else
    1786          37 :             sEnvelope = m_sExtent;
    1787             :     }
    1788             : 
    1789          90 :     if (sEnvelope.IsInit() && sEnvelope.MinX >= -10 * m_poDS->GetTileDim0() &&
    1790          39 :         sEnvelope.MinY >= -10 * m_poDS->GetTileDim0() &&
    1791          39 :         sEnvelope.MaxX <=
    1792         129 :             10 * m_poDS->GetTileDim0() * m_poDS->GetTileMatrixWidth0() &&
    1793          39 :         sEnvelope.MaxY <=
    1794          39 :             10 * m_poDS->GetTileDim0() * m_poDS->GetTileMatrixHeight0())
    1795             :     {
    1796          39 :         const double dfTileDim = m_poDS->GetTileDim0() / (1 << m_nZ);
    1797          39 :         m_nFilterMinX = std::max(
    1798          78 :             0, static_cast<int>(floor(
    1799          39 :                    (sEnvelope.MinX - m_poDS->GetTopXOrigin()) / dfTileDim)));
    1800          39 :         m_nFilterMinY = std::max(
    1801          78 :             0, static_cast<int>(floor(
    1802          39 :                    (m_poDS->GetTopYOrigin() - sEnvelope.MaxY) / dfTileDim)));
    1803          39 :         m_nFilterMaxX = std::min(
    1804          78 :             static_cast<int>(
    1805          78 :                 ceil((sEnvelope.MaxX - m_poDS->GetTopXOrigin()) / dfTileDim)),
    1806          39 :             static_cast<int>(std::min<int64_t>(
    1807          78 :                 INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
    1808          39 :                                  m_poDS->GetTileMatrixWidth0() -
    1809          78 :                              1)));
    1810          39 :         m_nFilterMaxY = std::min(
    1811          78 :             static_cast<int>(
    1812          78 :                 ceil((m_poDS->GetTopYOrigin() - sEnvelope.MinY) / dfTileDim)),
    1813          39 :             static_cast<int>(std::min<int64_t>(
    1814          78 :                 INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
    1815          78 :                                  m_poDS->GetTileMatrixHeight0() -
    1816          78 :                              1)));
    1817             :     }
    1818             :     else
    1819             :     {
    1820          12 :         m_nFilterMinX = 0;
    1821          12 :         m_nFilterMinY = 0;
    1822          12 :         m_nFilterMaxX = static_cast<int>(
    1823          24 :             std::min<int64_t>(INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
    1824          12 :                                                m_poDS->GetTileMatrixWidth0() -
    1825          12 :                                            1));
    1826          12 :         m_nFilterMaxY = static_cast<int>(
    1827          24 :             std::min<int64_t>(INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
    1828          24 :                                                m_poDS->GetTileMatrixHeight0() -
    1829          12 :                                            1));
    1830             :     }
    1831             : 
    1832          51 :     return OGRERR_NONE;
    1833             : }
    1834             : 
    1835             : /************************************************************************/
    1836             : /*                           TestCapability()                           */
    1837             : /************************************************************************/
    1838             : 
    1839          36 : int OGRMVTDirectoryLayer::TestCapability(const char *pszCap) const
    1840             : {
    1841          36 :     if (EQUAL(pszCap, OLCFastGetExtent))
    1842             :     {
    1843           2 :         return TRUE;
    1844             :     }
    1845          34 :     return OGRMVTLayerBase::TestCapability(pszCap);
    1846             : }
    1847             : 
    1848             : /************************************************************************/
    1849             : /*                             IGetExtent()                             */
    1850             : /************************************************************************/
    1851             : 
    1852           4 : OGRErr OGRMVTDirectoryLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
    1853             :                                         bool bForce)
    1854             : {
    1855           4 :     if (m_sExtent.IsInit())
    1856             :     {
    1857           4 :         *psExtent = m_sExtent;
    1858           4 :         return OGRERR_NONE;
    1859             :     }
    1860           0 :     return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
    1861             : }
    1862             : 
    1863             : /************************************************************************/
    1864             : /*                         CreateFeatureFrom()                          */
    1865             : /************************************************************************/
    1866             : 
    1867          67 : OGRFeature *OGRMVTDirectoryLayer::CreateFeatureFrom(OGRFeature *poSrcFeature)
    1868             : {
    1869             : 
    1870          67 :     return OGRMVTCreateFeatureFrom(poSrcFeature, m_poFeatureDefn, m_bJsonField,
    1871         134 :                                    GetSpatialRef());
    1872             : }
    1873             : 
    1874             : /************************************************************************/
    1875             : /*                         GetNextRawFeature()                          */
    1876             : /************************************************************************/
    1877             : 
    1878         126 : OGRFeature *OGRMVTDirectoryLayer::GetNextRawFeature()
    1879             : {
    1880             :     while (true)
    1881             :     {
    1882         126 :         OpenTileIfNeeded();
    1883         126 :         if (m_poCurrentTile == nullptr)
    1884          31 :             return nullptr;
    1885             :         OGRLayer *poUnderlyingLayer =
    1886          95 :             m_poCurrentTile->GetLayerByName(GetName());
    1887          95 :         OGRFeature *poUnderlyingFeature = poUnderlyingLayer->GetNextFeature();
    1888          95 :         if (poUnderlyingFeature != nullptr)
    1889             :         {
    1890          65 :             OGRFeature *poFeature = CreateFeatureFrom(poUnderlyingFeature);
    1891         130 :             poFeature->SetFID(m_nFIDBase +
    1892          65 :                               (poUnderlyingFeature->GetFID() << (2 * m_nZ)));
    1893          65 :             if (m_bAddTileFields)
    1894             :             {
    1895           4 :                 poFeature->SetField("tile_z", m_nZ);
    1896           4 :                 poFeature->SetField("tile_x", m_nTileX);
    1897           4 :                 poFeature->SetField("tile_y", m_nTileY);
    1898             :             }
    1899          65 :             delete poUnderlyingFeature;
    1900          65 :             return poFeature;
    1901             :         }
    1902             :         else
    1903             :         {
    1904          30 :             delete m_poCurrentTile;
    1905          30 :             m_poCurrentTile = nullptr;
    1906             :         }
    1907          30 :     }
    1908             : }
    1909             : 
    1910             : /************************************************************************/
    1911             : /*                             GetFeature()                             */
    1912             : /************************************************************************/
    1913             : 
    1914           5 : OGRFeature *OGRMVTDirectoryLayer::GetFeature(GIntBig nFID)
    1915             : {
    1916           5 :     const int nX = static_cast<int>(nFID & ((1 << m_nZ) - 1));
    1917           5 :     const int nY = static_cast<int>((nFID >> m_nZ) & ((1 << m_nZ) - 1));
    1918           5 :     const GIntBig nTileFID = nFID >> (2 * m_nZ);
    1919          15 :     const CPLString osFilename = CPLFormFilenameSafe(
    1920           5 :         CPLFormFilenameSafe(m_osDirName, CPLSPrintf("%d", nX), nullptr).c_str(),
    1921          15 :         CPLSPrintf("%d.%s", nY, m_poDS->m_osTileExtension.c_str()), nullptr);
    1922           5 :     GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(), GA_ReadOnly);
    1923           5 :     oOpenInfo.papszOpenOptions = CSLSetNameValue(
    1924             :         nullptr, "METADATA_FILE",
    1925           5 :         m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
    1926           5 :     oOpenInfo.papszOpenOptions = CSLSetNameValue(
    1927             :         oOpenInfo.papszOpenOptions, "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
    1928             :     GDALDataset *poTile =
    1929           5 :         OGRMVTDataset::Open(&oOpenInfo, /* bRecurseAllowed = */ false);
    1930           5 :     CSLDestroy(oOpenInfo.papszOpenOptions);
    1931           5 :     OGRFeature *poFeature = nullptr;
    1932           5 :     if (poTile)
    1933             :     {
    1934           5 :         OGRLayer *poLayer = poTile->GetLayerByName(GetName());
    1935           5 :         if (poLayer)
    1936             :         {
    1937           5 :             OGRFeature *poUnderlyingFeature = poLayer->GetFeature(nTileFID);
    1938           5 :             if (poUnderlyingFeature)
    1939             :             {
    1940           2 :                 poFeature = CreateFeatureFrom(poUnderlyingFeature);
    1941           2 :                 poFeature->SetFID(nFID);
    1942             :             }
    1943           5 :             delete poUnderlyingFeature;
    1944             :         }
    1945             :     }
    1946           5 :     delete poTile;
    1947          10 :     return poFeature;
    1948             : }
    1949             : 
    1950             : /************************************************************************/
    1951             : /*                             GetDataset()                             */
    1952             : /************************************************************************/
    1953             : 
    1954           1 : GDALDataset *OGRMVTDirectoryLayer::GetDataset()
    1955             : {
    1956           1 :     return m_poDS;
    1957             : }
    1958             : 
    1959             : /************************************************************************/
    1960             : /*                           OGRMVTDataset()                            */
    1961             : /************************************************************************/
    1962             : 
    1963        1001 : OGRMVTDataset::OGRMVTDataset(GByte *pabyData)
    1964        1001 :     : m_pabyData(pabyData), m_poSRS(new OGRSpatialReference())
    1965             : {
    1966        1001 :     m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1967             : 
    1968        1001 :     m_bClip = CPLTestBool(CPLGetConfigOption("OGR_MVT_CLIP", "YES"));
    1969             : 
    1970             :     // Default WebMercator tiling scheme
    1971        1001 :     InitWebMercatorTilingScheme(m_poSRS, m_dfTopXOrigin, m_dfTopYOrigin,
    1972        1001 :                                 m_dfTileDim0);
    1973        1001 : }
    1974             : 
    1975             : /************************************************************************/
    1976             : /*                           ~OGRMVTDataset()                           */
    1977             : /************************************************************************/
    1978             : 
    1979        2002 : OGRMVTDataset::~OGRMVTDataset()
    1980             : {
    1981        1001 :     VSIFree(m_pabyData);
    1982        1001 :     if (!m_osMetadataMemFilename.empty())
    1983          18 :         VSIUnlink(m_osMetadataMemFilename);
    1984        1001 :     if (m_poSRS)
    1985         998 :         m_poSRS->Release();
    1986        2002 : }
    1987             : 
    1988             : /************************************************************************/
    1989             : /*                              GetLayer()                              */
    1990             : /************************************************************************/
    1991             : 
    1992        1764 : const OGRLayer *OGRMVTDataset::GetLayer(int iLayer) const
    1993             : 
    1994             : {
    1995        1764 :     if (iLayer < 0 || iLayer >= GetLayerCount())
    1996           4 :         return nullptr;
    1997        1760 :     return m_apoLayers[iLayer].get();
    1998             : }
    1999             : 
    2000             : /************************************************************************/
    2001             : /*                              Identify()                              */
    2002             : /************************************************************************/
    2003             : 
    2004       56265 : static int OGRMVTDriverIdentify(GDALOpenInfo *poOpenInfo)
    2005             : 
    2006             : {
    2007       56265 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
    2008        1833 :         return TRUE;
    2009             : 
    2010       54432 :     if (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl"))
    2011             :     {
    2012           1 :         if (CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
    2013             :             CPL_VALUE_INTEGER)
    2014             :         {
    2015           0 :             return TRUE;
    2016             :         }
    2017             :     }
    2018             : 
    2019       54432 :     if (poOpenInfo->bIsDirectory)
    2020             :     {
    2021         733 :         if (CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
    2022             :             CPL_VALUE_INTEGER)
    2023             :         {
    2024             :             VSIStatBufL sStat;
    2025          72 :             CPLString osMetadataFile(CPLFormFilenameSafe(
    2026          36 :                 CPLGetPathSafe(poOpenInfo->pszFilename).c_str(),
    2027          36 :                 "metadata.json", nullptr));
    2028          72 :             const char *pszMetadataFile = CSLFetchNameValue(
    2029          36 :                 poOpenInfo->papszOpenOptions, "METADATA_FILE");
    2030          36 :             if (pszMetadataFile)
    2031             :             {
    2032           6 :                 osMetadataFile = pszMetadataFile;
    2033             :             }
    2034          66 :             if (!osMetadataFile.empty() &&
    2035          30 :                 (STARTS_WITH(osMetadataFile, "http://") ||
    2036          30 :                  STARTS_WITH(osMetadataFile, "https://") ||
    2037          30 :                  VSIStatL(osMetadataFile, &sStat) == 0))
    2038             :             {
    2039          26 :                 return TRUE;
    2040             :             }
    2041          10 :             if (pszMetadataFile == nullptr)
    2042             :             {
    2043             :                 // tileserver-gl metadata file:
    2044             :                 // If opening /path/to/foo/0, try looking for /path/to/foo.json
    2045           4 :                 CPLString osParentDir(CPLGetPathSafe(poOpenInfo->pszFilename));
    2046             :                 osMetadataFile =
    2047           8 :                     CPLFormFilenameSafe(CPLGetPathSafe(osParentDir).c_str(),
    2048           4 :                                         CPLGetFilename(osParentDir), "json");
    2049           4 :                 if (VSIStatL(osMetadataFile, &sStat) == 0)
    2050             :                 {
    2051           2 :                     return TRUE;
    2052             :                 }
    2053             :             }
    2054             : 
    2055             :             // At least 3 files, to include the dummy . and ..
    2056             :             const CPLStringList aosDirContent = StripDummyEntries(
    2057           8 :                 CPLStringList(VSIReadDirEx(poOpenInfo->pszFilename, 3)));
    2058          16 :             if (!aosDirContent.empty() &&
    2059           8 :                 CPLGetValueType(aosDirContent[0]) == CPL_VALUE_INTEGER)
    2060             :             {
    2061             :                 const std::string osSubDir = CPLFormFilenameSafe(
    2062           8 :                     poOpenInfo->pszFilename, aosDirContent[0], nullptr);
    2063             :                 // At least 3 files, to include the dummy . and ..
    2064             :                 const CPLStringList aosSubDirContent = StripDummyEntries(
    2065           8 :                     CPLStringList(VSIReadDirEx(osSubDir.c_str(), 10)));
    2066             :                 const std::string osTileExtension(CSLFetchNameValueDef(
    2067           8 :                     poOpenInfo->papszOpenOptions, "TILE_EXTENSION", "pbf"));
    2068           8 :                 for (int i = 0; i < aosSubDirContent.Count(); i++)
    2069             :                 {
    2070           8 :                     if (CPLGetValueType(
    2071          16 :                             CPLGetBasenameSafe(aosSubDirContent[i]).c_str()) ==
    2072             :                         CPL_VALUE_INTEGER)
    2073             :                     {
    2074             :                         const std::string osExtension(
    2075           8 :                             CPLGetExtensionSafe(aosSubDirContent[i]));
    2076           8 :                         if (EQUAL(osExtension.c_str(),
    2077           8 :                                   osTileExtension.c_str()) ||
    2078           0 :                             EQUAL(osExtension.c_str(), "mvt"))
    2079             :                         {
    2080           8 :                             return TRUE;
    2081             :                         }
    2082             :                     }
    2083             :                 }
    2084             :             }
    2085             :         }
    2086         697 :         return FALSE;
    2087             :     }
    2088             : 
    2089       53699 :     if (poOpenInfo->nHeaderBytes <= 2)
    2090       50823 :         return FALSE;
    2091             : 
    2092             :     // GZip header ?
    2093        2876 :     if (poOpenInfo->pabyHeader[0] == 0x1F && poOpenInfo->pabyHeader[1] == 0x8B)
    2094             :     {
    2095             :         // Prevent recursion
    2096          36 :         if (STARTS_WITH(poOpenInfo->pszFilename, "/vsigzip/"))
    2097             :         {
    2098           0 :             return FALSE;
    2099             :         }
    2100             :         CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
    2101          72 :                                       false);
    2102             :         GDALOpenInfo oOpenInfo(
    2103          72 :             (CPLString("/vsigzip/") + poOpenInfo->pszFilename).c_str(),
    2104          72 :             GA_ReadOnly);
    2105          36 :         return OGRMVTDriverIdentify(&oOpenInfo);
    2106             :     }
    2107             : 
    2108             :     // The GPB macros assume that the buffer is nul terminated,
    2109             :     // which is the case
    2110        2840 :     const GByte *pabyData = reinterpret_cast<GByte *>(poOpenInfo->pabyHeader);
    2111        2840 :     const GByte *const pabyDataStart = pabyData;
    2112             :     const GByte *pabyLayerStart;
    2113        2840 :     const GByte *const pabyDataLimit = pabyData + poOpenInfo->nHeaderBytes;
    2114        2840 :     const GByte *pabyLayerEnd = pabyDataLimit;
    2115        2840 :     int nKey = 0;
    2116        2840 :     unsigned int nLayerLength = 0;
    2117        2840 :     bool bLayerNameFound = false;
    2118        2840 :     bool bKeyFound = false;
    2119        2840 :     bool bFeatureFound = false;
    2120        2840 :     bool bVersionFound = false;
    2121             : 
    2122             :     try
    2123             :     {
    2124        2840 :         READ_FIELD_KEY(nKey);
    2125        2838 :         if (nKey != MAKE_KEY(knLAYER, WT_DATA))
    2126        2789 :             return FALSE;
    2127          49 :         READ_VARUINT32(pabyData, pabyDataLimit, nLayerLength);
    2128          49 :         pabyLayerStart = pabyData;
    2129             : 
    2130             :         // Sanity check on layer length
    2131          49 :         if (nLayerLength < static_cast<unsigned>(poOpenInfo->nHeaderBytes -
    2132          49 :                                                  (pabyData - pabyDataStart)))
    2133             :         {
    2134           7 :             if (pabyData[nLayerLength] != MAKE_KEY(knLAYER, WT_DATA))
    2135           1 :                 return FALSE;
    2136           6 :             pabyLayerEnd = pabyData + nLayerLength;
    2137             :         }
    2138          42 :         else if (nLayerLength > 10 * 1024 * 1024)
    2139             :         {
    2140           0 :             return FALSE;
    2141             :         }
    2142             : 
    2143             :         // Quick scan on partial layer content to see if it seems to conform to
    2144             :         // the proto
    2145         544 :         while (pabyData < pabyLayerEnd)
    2146             :         {
    2147         498 :             READ_VARUINT32(pabyData, pabyLayerEnd, nKey);
    2148         498 :             auto nFieldNumber = GET_FIELDNUMBER(nKey);
    2149         498 :             auto nWireType = GET_WIRETYPE(nKey);
    2150         498 :             if (nFieldNumber == knLAYER_NAME)
    2151             :             {
    2152          48 :                 if (nWireType != WT_DATA)
    2153             :                 {
    2154           0 :                     CPLDebug("MVT", "Invalid wire type for layer_name field");
    2155             :                 }
    2156          48 :                 char *pszLayerName = nullptr;
    2157          48 :                 unsigned int nTextSize = 0;
    2158          48 :                 READ_TEXT_WITH_SIZE(pabyData, pabyLayerEnd, pszLayerName,
    2159             :                                     nTextSize);
    2160          48 :                 if (nTextSize == 0 || !CPLIsUTF8(pszLayerName, nTextSize))
    2161             :                 {
    2162           0 :                     CPLFree(pszLayerName);
    2163           0 :                     CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
    2164           0 :                     return FALSE;
    2165             :                 }
    2166          48 :                 CPLFree(pszLayerName);
    2167          48 :                 bLayerNameFound = true;
    2168             :             }
    2169         450 :             else if (nFieldNumber == knLAYER_FEATURES)
    2170             :             {
    2171          52 :                 if (nWireType != WT_DATA)
    2172             :                 {
    2173           0 :                     CPLDebug("MVT",
    2174             :                              "Invalid wire type for layer_features field");
    2175             :                 }
    2176          52 :                 unsigned int nFeatureLength = 0;
    2177          52 :                 unsigned int nGeomType = 0;
    2178          52 :                 READ_VARUINT32(pabyData, pabyLayerEnd, nFeatureLength);
    2179          52 :                 if (nFeatureLength > nLayerLength - (pabyData - pabyLayerStart))
    2180             :                 {
    2181           0 :                     CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
    2182           0 :                     return FALSE;
    2183             :                 }
    2184          52 :                 bFeatureFound = true;
    2185             : 
    2186          52 :                 const GByte *const pabyDataFeatureStart = pabyData;
    2187             :                 const GByte *const pabyDataFeatureEnd =
    2188             :                     pabyDataStart +
    2189         104 :                     std::min(static_cast<int>(pabyData + nFeatureLength -
    2190             :                                               pabyDataStart),
    2191          52 :                              poOpenInfo->nHeaderBytes);
    2192         168 :                 while (pabyData < pabyDataFeatureEnd)
    2193             :                 {
    2194         118 :                     READ_VARUINT32(pabyData, pabyDataFeatureEnd, nKey);
    2195         118 :                     nFieldNumber = GET_FIELDNUMBER(nKey);
    2196         118 :                     nWireType = GET_WIRETYPE(nKey);
    2197         118 :                     if (nFieldNumber == knFEATURE_TYPE)
    2198             :                     {
    2199          48 :                         if (nWireType != WT_VARINT)
    2200             :                         {
    2201           0 :                             CPLDebug(
    2202             :                                 "MVT",
    2203             :                                 "Invalid wire type for feature_type field");
    2204           0 :                             return FALSE;
    2205             :                         }
    2206          48 :                         READ_VARUINT32(pabyData, pabyDataFeatureEnd, nGeomType);
    2207          48 :                         if (nGeomType > knGEOM_TYPE_POLYGON)
    2208             :                         {
    2209           0 :                             CPLDebug("MVT", "Protobuf error: line %d",
    2210             :                                      __LINE__);
    2211           0 :                             return FALSE;
    2212             :                         }
    2213             :                     }
    2214          70 :                     else if (nFieldNumber == knFEATURE_TAGS)
    2215             :                     {
    2216          18 :                         if (nWireType != WT_DATA)
    2217             :                         {
    2218           0 :                             CPLDebug(
    2219             :                                 "MVT",
    2220             :                                 "Invalid wire type for feature_tags field");
    2221           0 :                             return FALSE;
    2222             :                         }
    2223          18 :                         unsigned int nTagsSize = 0;
    2224          18 :                         READ_VARUINT32(pabyData, pabyDataFeatureEnd, nTagsSize);
    2225          18 :                         if (nTagsSize == 0 ||
    2226          18 :                             nTagsSize > nFeatureLength -
    2227          18 :                                             (pabyData - pabyDataFeatureStart))
    2228             :                         {
    2229           0 :                             CPLDebug("MVT", "Protobuf error: line %d",
    2230             :                                      __LINE__);
    2231           0 :                             return FALSE;
    2232             :                         }
    2233             :                         const GByte *const pabyDataTagsEnd =
    2234             :                             pabyDataStart +
    2235          36 :                             std::min(static_cast<int>(pabyData + nTagsSize -
    2236             :                                                       pabyDataStart),
    2237          18 :                                      poOpenInfo->nHeaderBytes);
    2238         400 :                         while (pabyData < pabyDataTagsEnd)
    2239             :                         {
    2240         382 :                             unsigned int nKeyIdx = 0;
    2241         382 :                             unsigned int nValIdx = 0;
    2242         382 :                             READ_VARUINT32(pabyData, pabyDataTagsEnd, nKeyIdx);
    2243         382 :                             READ_VARUINT32(pabyData, pabyDataTagsEnd, nValIdx);
    2244         382 :                             if (nKeyIdx > 10 * 1024 * 1024 ||
    2245             :                                 nValIdx > 10 * 1024 * 1024)
    2246             :                             {
    2247           0 :                                 CPLDebug("MVT", "Protobuf error: line %d",
    2248             :                                          __LINE__);
    2249           0 :                                 return FALSE;
    2250             :                             }
    2251             :                         }
    2252             :                     }
    2253          52 :                     else if (nFieldNumber == knFEATURE_GEOMETRY &&
    2254             :                              nWireType != WT_DATA)
    2255             :                     {
    2256           0 :                         CPLDebug(
    2257             :                             "MVT",
    2258             :                             "Invalid wire type for feature_geometry field");
    2259           0 :                         return FALSE;
    2260             :                     }
    2261          52 :                     else if (nKey == MAKE_KEY(knFEATURE_GEOMETRY, WT_DATA) &&
    2262          48 :                              nGeomType >= knGEOM_TYPE_POINT &&
    2263             :                              nGeomType <= knGEOM_TYPE_POLYGON)
    2264             :                     {
    2265          48 :                         unsigned int nGeometrySize = 0;
    2266          48 :                         READ_VARUINT32(pabyData, pabyDataFeatureEnd,
    2267             :                                        nGeometrySize);
    2268          48 :                         if (nGeometrySize == 0 ||
    2269          48 :                             nGeometrySize >
    2270          48 :                                 nFeatureLength -
    2271          48 :                                     (pabyData - pabyDataFeatureStart))
    2272             :                         {
    2273           0 :                             CPLDebug("MVT", "Protobuf error: line %d",
    2274             :                                      __LINE__);
    2275           0 :                             return FALSE;
    2276             :                         }
    2277             :                         const GByte *const pabyDataGeometryEnd =
    2278             :                             pabyDataStart +
    2279          96 :                             std::min(static_cast<int>(pabyData + nGeometrySize -
    2280             :                                                       pabyDataStart),
    2281          48 :                                      poOpenInfo->nHeaderBytes);
    2282             : 
    2283          48 :                         if (nGeomType == knGEOM_TYPE_POINT)
    2284             :                         {
    2285          14 :                             unsigned int nCmdCountCombined = 0;
    2286             :                             unsigned int nCount;
    2287          14 :                             READ_VARUINT32(pabyData, pabyDataGeometryEnd,
    2288             :                                            nCmdCountCombined);
    2289          14 :                             nCount = GetCmdCount(nCmdCountCombined);
    2290          28 :                             if (GetCmdId(nCmdCountCombined) != knCMD_MOVETO ||
    2291          28 :                                 nCount == 0 || nCount > 10 * 1024 * 1024)
    2292             :                             {
    2293           0 :                                 CPLDebug("MVT", "Protobuf error: line %d",
    2294             :                                          __LINE__);
    2295           0 :                                 return FALSE;
    2296             :                             }
    2297          46 :                             for (unsigned i = 0; i < 2 * nCount; i++)
    2298             :                             {
    2299          32 :                                 SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2300             :                             }
    2301             :                         }
    2302          34 :                         else if (nGeomType == knGEOM_TYPE_LINESTRING)
    2303             :                         {
    2304          56 :                             while (pabyData < pabyDataGeometryEnd)
    2305             :                             {
    2306          32 :                                 unsigned int nCmdCountCombined = 0;
    2307             :                                 unsigned int nLineToCount;
    2308             :                                 // Should be a moveto
    2309          32 :                                 READ_VARUINT32(pabyData, pabyDataGeometryEnd,
    2310             :                                                nCmdCountCombined);
    2311          32 :                                 if (GetCmdId(nCmdCountCombined) !=
    2312          64 :                                         knCMD_MOVETO ||
    2313          32 :                                     GetCmdCount(nCmdCountCombined) != 1)
    2314             :                                 {
    2315           0 :                                     CPLDebug("MVT", "Protobuf error: line %d",
    2316             :                                              __LINE__);
    2317           0 :                                     return FALSE;
    2318             :                                 }
    2319          32 :                                 SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2320          32 :                                 SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2321          32 :                                 READ_VARUINT32(pabyData, pabyDataGeometryEnd,
    2322             :                                                nCmdCountCombined);
    2323          32 :                                 if (GetCmdId(nCmdCountCombined) != knCMD_LINETO)
    2324             :                                 {
    2325           0 :                                     CPLDebug("MVT", "Protobuf error: line %d",
    2326             :                                              __LINE__);
    2327           0 :                                     return FALSE;
    2328             :                                 }
    2329          32 :                                 nLineToCount = GetCmdCount(nCmdCountCombined);
    2330          96 :                                 for (unsigned i = 0; i < 2 * nLineToCount; i++)
    2331             :                                 {
    2332          64 :                                     SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2333             :                                 }
    2334             :                             }
    2335             :                         }
    2336             :                         else /* if( nGeomType == knGEOM_TYPE_POLYGON ) */
    2337             :                         {
    2338         382 :                             while (pabyData < pabyDataGeometryEnd)
    2339             :                             {
    2340         374 :                                 unsigned int nCmdCountCombined = 0;
    2341             :                                 unsigned int nLineToCount;
    2342             :                                 // Should be a moveto
    2343         374 :                                 READ_VARUINT32(pabyData, pabyDataGeometryEnd,
    2344             :                                                nCmdCountCombined);
    2345         374 :                                 if (GetCmdId(nCmdCountCombined) !=
    2346         748 :                                         knCMD_MOVETO ||
    2347         374 :                                     GetCmdCount(nCmdCountCombined) != 1)
    2348             :                                 {
    2349           0 :                                     CPLDebug("MVT", "Protobuf error: line %d",
    2350             :                                              __LINE__);
    2351           0 :                                     return FALSE;
    2352             :                                 }
    2353         374 :                                 SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2354         374 :                                 SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2355         374 :                                 READ_VARUINT32(pabyData, pabyDataGeometryEnd,
    2356             :                                                nCmdCountCombined);
    2357         374 :                                 if (GetCmdId(nCmdCountCombined) != knCMD_LINETO)
    2358             :                                 {
    2359           0 :                                     CPLDebug("MVT", "Protobuf error: line %d",
    2360             :                                              __LINE__);
    2361           0 :                                     return FALSE;
    2362             :                                 }
    2363         374 :                                 nLineToCount = GetCmdCount(nCmdCountCombined);
    2364        7020 :                                 for (unsigned i = 0; i < 2 * nLineToCount; i++)
    2365             :                                 {
    2366        6648 :                                     SKIP_VARINT(pabyData, pabyDataGeometryEnd);
    2367             :                                 }
    2368             :                                 // Should be a closepath
    2369         372 :                                 READ_VARUINT32(pabyData, pabyDataGeometryEnd,
    2370             :                                                nCmdCountCombined);
    2371         372 :                                 if (GetCmdId(nCmdCountCombined) !=
    2372         744 :                                         knCMD_CLOSEPATH ||
    2373         372 :                                     GetCmdCount(nCmdCountCombined) != 1)
    2374             :                                 {
    2375           0 :                                     CPLDebug("MVT", "Protobuf error: line %d",
    2376             :                                              __LINE__);
    2377           0 :                                     return FALSE;
    2378             :                                 }
    2379             :                             }
    2380             :                         }
    2381             : 
    2382          46 :                         pabyData = pabyDataGeometryEnd;
    2383             :                     }
    2384             :                     else
    2385             :                     {
    2386           4 :                         SKIP_UNKNOWN_FIELD(pabyData, pabyDataFeatureEnd, FALSE);
    2387             :                     }
    2388             :                 }
    2389             : 
    2390          50 :                 pabyData = pabyDataFeatureEnd;
    2391             :             }
    2392         398 :             else if (nFieldNumber == knLAYER_KEYS)
    2393             :             {
    2394         152 :                 if (nWireType != WT_DATA)
    2395             :                 {
    2396           0 :                     CPLDebug("MVT", "Invalid wire type for keys field");
    2397           0 :                     return FALSE;
    2398             :                 }
    2399         152 :                 char *pszKey = nullptr;
    2400         152 :                 unsigned int nTextSize = 0;
    2401         152 :                 READ_TEXT_WITH_SIZE(pabyData, pabyLayerEnd, pszKey, nTextSize);
    2402         152 :                 if (!CPLIsUTF8(pszKey, nTextSize))
    2403             :                 {
    2404           0 :                     CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
    2405           0 :                     CPLFree(pszKey);
    2406           0 :                     return FALSE;
    2407             :                 }
    2408         152 :                 CPLFree(pszKey);
    2409         152 :                 bKeyFound = true;
    2410             :             }
    2411         246 :             else if (nFieldNumber == knLAYER_VALUES)
    2412             :             {
    2413         156 :                 if (nWireType != WT_DATA)
    2414             :                 {
    2415           0 :                     CPLDebug("MVT", "Invalid wire type for values field");
    2416           0 :                     return FALSE;
    2417             :                 }
    2418         156 :                 unsigned int nValueLength = 0;
    2419         156 :                 READ_VARUINT32(pabyData, pabyLayerEnd, nValueLength);
    2420         156 :                 if (nValueLength == 0 ||
    2421         156 :                     nValueLength > nLayerLength - (pabyData - pabyLayerStart))
    2422             :                 {
    2423           0 :                     CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
    2424           0 :                     return FALSE;
    2425             :                 }
    2426         156 :                 pabyData += nValueLength;
    2427             :             }
    2428          90 :             else if (GET_FIELDNUMBER(nKey) == knLAYER_EXTENT &&
    2429          42 :                      GET_WIRETYPE(nKey) != WT_VARINT)
    2430             :             {
    2431           0 :                 CPLDebug("MVT", "Invalid wire type for extent field");
    2432           0 :                 return FALSE;
    2433             :             }
    2434             : #if 0
    2435             :             // The check on extent is too fragile. Values of 65536 can be found
    2436             :             else if( nKey == MAKE_KEY(knLAYER_EXTENT, WT_VARINT) )
    2437             :             {
    2438             :                 unsigned int nExtent = 0;
    2439             :                 READ_VARUINT32(pabyData, pabyLayerEnd, nExtent);
    2440             :                 if( nExtent < 128 || nExtent > 16834 )
    2441             :                 {
    2442             :                     CPLDebug("MVT", "Invalid extent: %u", nExtent);
    2443             :                     return FALSE;
    2444             :                 }
    2445             :             }
    2446             : #endif
    2447          90 :             else if (nFieldNumber == knLAYER_VERSION)
    2448             :             {
    2449          46 :                 if (nWireType != WT_VARINT)
    2450             :                 {
    2451           0 :                     CPLDebug("MVT", "Invalid wire type for version field");
    2452           0 :                     return FALSE;
    2453             :                 }
    2454          46 :                 unsigned int nVersion = 0;
    2455          46 :                 READ_VARUINT32(pabyData, pabyLayerEnd, nVersion);
    2456          46 :                 if (nVersion != 1 && nVersion != 2)
    2457             :                 {
    2458           0 :                     CPLDebug("MVT", "Invalid version: %u", nVersion);
    2459           0 :                     return FALSE;
    2460             :                 }
    2461          46 :                 bVersionFound = true;
    2462             :             }
    2463             :             else
    2464             :             {
    2465          44 :                 SKIP_UNKNOWN_FIELD(pabyData, pabyLayerEnd, FALSE);
    2466             :             }
    2467             :         }
    2468             :     }
    2469           4 :     catch (const GPBException &)
    2470             :     {
    2471             :     }
    2472             : 
    2473          50 :     return bLayerNameFound && (bKeyFound || bFeatureFound || bVersionFound);
    2474             : }
    2475             : 
    2476             : /************************************************************************/
    2477             : /*                     LongLatToSphericalMercator()                     */
    2478             : /************************************************************************/
    2479             : 
    2480          30 : static void LongLatToSphericalMercator(double *x, double *y)
    2481             : {
    2482          30 :     double X = kmSPHERICAL_RADIUS * (*x) / 180 * M_PI;
    2483             :     double Y =
    2484          30 :         kmSPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
    2485          30 :     *x = X;
    2486          30 :     *y = Y;
    2487          30 : }
    2488             : 
    2489             : /************************************************************************/
    2490             : /*                            LoadMetadata()                            */
    2491             : /************************************************************************/
    2492             : 
    2493         930 : static bool LoadMetadata(const CPLString &osMetadataFile,
    2494             :                          const CPLString &osMetadataContent,
    2495             :                          CPLJSONArray &oVectorLayers,
    2496             :                          CPLJSONArray &oTileStatLayers, CPLJSONObject &oBounds,
    2497             :                          OGRSpatialReference *poSRS, double &dfTopX,
    2498             :                          double &dfTopY, double &dfTileDim0,
    2499             :                          int &nTileMatrixWidth0, int &nTileMatrixHeight0,
    2500             :                          const CPLString &osMetadataMemFilename)
    2501             : 
    2502             : {
    2503        1860 :     CPLJSONDocument oDoc;
    2504             : 
    2505             :     bool bLoadOK;
    2506         930 :     if (!osMetadataContent.empty())
    2507             :     {
    2508           3 :         bLoadOK = oDoc.LoadMemory(osMetadataContent);
    2509             :     }
    2510        1854 :     else if (STARTS_WITH(osMetadataFile, "http://") ||
    2511         927 :              STARTS_WITH(osMetadataFile, "https://"))
    2512             :     {
    2513           0 :         bLoadOK = oDoc.LoadUrl(osMetadataFile, nullptr);
    2514             :     }
    2515             :     else
    2516             :     {
    2517         927 :         bLoadOK = oDoc.Load(osMetadataFile);
    2518             :     }
    2519         930 :     if (!bLoadOK)
    2520           2 :         return false;
    2521             : 
    2522        2784 :     const CPLJSONObject oCrs(oDoc.GetRoot().GetObj("crs"));
    2523             :     const CPLJSONObject oTopX(
    2524        2784 :         oDoc.GetRoot().GetObj("tile_origin_upper_left_x"));
    2525             :     const CPLJSONObject oTopY(
    2526        2784 :         oDoc.GetRoot().GetObj("tile_origin_upper_left_y"));
    2527             :     const CPLJSONObject oTileDim0(
    2528        2784 :         oDoc.GetRoot().GetObj("tile_dimension_zoom_0"));
    2529         928 :     nTileMatrixWidth0 = 1;
    2530         928 :     nTileMatrixHeight0 = 1;
    2531         934 :     if (oCrs.IsValid() && oTopX.IsValid() && oTopY.IsValid() &&
    2532           6 :         oTileDim0.IsValid())
    2533             :     {
    2534           6 :         poSRS->SetFromUserInput(oCrs.ToString().c_str());
    2535           6 :         dfTopX = oTopX.ToDouble();
    2536           6 :         dfTopY = oTopY.ToDouble();
    2537           6 :         dfTileDim0 = oTileDim0.ToDouble();
    2538             :         const CPLJSONObject oTMWidth0(
    2539          18 :             oDoc.GetRoot().GetObj("tile_matrix_width_zoom_0"));
    2540           6 :         if (oTMWidth0.GetType() == CPLJSONObject::Type::Integer)
    2541           6 :             nTileMatrixWidth0 = std::max(1, oTMWidth0.ToInteger());
    2542             : 
    2543             :         const CPLJSONObject oTMHeight0(
    2544          18 :             oDoc.GetRoot().GetObj("tile_matrix_height_zoom_0"));
    2545           6 :         if (oTMHeight0.GetType() == CPLJSONObject::Type::Integer)
    2546           6 :             nTileMatrixHeight0 = std::max(1, oTMHeight0.ToInteger());
    2547             : 
    2548             :         // Assumes WorldCRS84Quad with 2 tiles in width
    2549             :         // cf https://github.com/OSGeo/gdal/issues/11749
    2550           6 :         if (!oTMWidth0.IsValid() && dfTopX == -180 && dfTileDim0 == 180)
    2551           0 :             nTileMatrixWidth0 = 2;
    2552             :     }
    2553             : 
    2554         928 :     oVectorLayers.Deinit();
    2555         928 :     oTileStatLayers.Deinit();
    2556             : 
    2557        2784 :     CPLJSONObject oJson = oDoc.GetRoot().GetObj("json");
    2558         928 :     if (!(oJson.IsValid() && oJson.GetType() == CPLJSONObject::Type::String))
    2559             :     {
    2560         534 :         oVectorLayers = oDoc.GetRoot().GetArray("vector_layers");
    2561             : 
    2562         534 :         oTileStatLayers = oDoc.GetRoot().GetArray("tilestats/layers");
    2563             :     }
    2564             :     else
    2565             :     {
    2566         394 :         CPLJSONDocument oJsonDoc;
    2567         394 :         if (!oJsonDoc.LoadMemory(oJson.ToString()))
    2568             :         {
    2569           1 :             return false;
    2570             :         }
    2571             : 
    2572         393 :         oVectorLayers = oJsonDoc.GetRoot().GetArray("vector_layers");
    2573             : 
    2574         393 :         oTileStatLayers = oJsonDoc.GetRoot().GetArray("tilestats/layers");
    2575             :     }
    2576             : 
    2577         927 :     oBounds = oDoc.GetRoot().GetObj("bounds");
    2578             : 
    2579         927 :     if (!osMetadataMemFilename.empty())
    2580             :     {
    2581          18 :         oDoc.Save(osMetadataMemFilename);
    2582             :     }
    2583             : 
    2584         927 :     return oVectorLayers.IsValid();
    2585             : }
    2586             : 
    2587             : /************************************************************************/
    2588             : /*                          ConvertFromWGS84()                          */
    2589             : /************************************************************************/
    2590             : 
    2591          18 : static void ConvertFromWGS84(OGRSpatialReference *poTargetSRS, double &dfX0,
    2592             :                              double &dfY0, double &dfX1, double &dfY1)
    2593             : {
    2594          36 :     OGRSpatialReference oSRS_EPSG3857;
    2595          18 :     oSRS_EPSG3857.SetFromUserInput(SRS_EPSG_3857);
    2596             : 
    2597          18 :     if (poTargetSRS->IsSame(&oSRS_EPSG3857))
    2598             :     {
    2599          15 :         LongLatToSphericalMercator(&dfX0, &dfY0);
    2600          15 :         LongLatToSphericalMercator(&dfX1, &dfY1);
    2601             :     }
    2602             :     else
    2603             :     {
    2604           6 :         OGRSpatialReference oSRS_EPSG4326;
    2605           3 :         oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
    2606           3 :         oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2607             :         OGRCoordinateTransformation *poCT =
    2608           3 :             OGRCreateCoordinateTransformation(&oSRS_EPSG4326, poTargetSRS);
    2609           3 :         if (poCT)
    2610             :         {
    2611           3 :             poCT->Transform(1, &dfX0, &dfY0);
    2612           3 :             poCT->Transform(1, &dfX1, &dfY1);
    2613           3 :             delete poCT;
    2614             :         }
    2615             :     }
    2616          18 : }
    2617             : 
    2618             : /************************************************************************/
    2619             : /*                           OpenDirectory()                            */
    2620             : /************************************************************************/
    2621             : 
    2622          26 : GDALDataset *OGRMVTDataset::OpenDirectory(GDALOpenInfo *poOpenInfo)
    2623             : 
    2624             : {
    2625          52 :     const CPLString osZ(CPLGetFilename(poOpenInfo->pszFilename));
    2626          26 :     if (CPLGetValueType(osZ) != CPL_VALUE_INTEGER)
    2627           1 :         return nullptr;
    2628             : 
    2629          25 :     const int nZ = atoi(osZ);
    2630          25 :     if (nZ < 0 || nZ > 30)
    2631           1 :         return nullptr;
    2632             : 
    2633             :     CPLString osMetadataFile(
    2634          48 :         CPLFormFilenameSafe(CPLGetPathSafe(poOpenInfo->pszFilename).c_str(),
    2635          48 :                             "metadata.json", nullptr));
    2636             :     const char *pszMetadataFile =
    2637          24 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
    2638          24 :     if (pszMetadataFile)
    2639             :     {
    2640           3 :         osMetadataFile = pszMetadataFile;
    2641             :     }
    2642             : 
    2643          24 :     CPLString osTileExtension(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
    2644          48 :                                                    "TILE_EXTENSION", "pbf"));
    2645             :     bool bJsonField =
    2646          24 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "JSON_FIELD", false);
    2647             : 
    2648             :     bool bAddTileFields =
    2649          24 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "ADD_TILE_FIELDS", false);
    2650             : 
    2651             :     VSIStatBufL sStat;
    2652             : 
    2653          24 :     bool bMetadataFileExists = false;
    2654          48 :     CPLString osMetadataContent;
    2655          43 :     if (STARTS_WITH(osMetadataFile, "http://") ||
    2656          19 :         STARTS_WITH(osMetadataFile, "https://"))
    2657             :     {
    2658           8 :         for (int i = 0; i < 2; i++)
    2659             :         {
    2660           8 :             if (pszMetadataFile == nullptr)
    2661           8 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    2662           8 :             CPLHTTPResult *psResult = CPLHTTPFetch(osMetadataFile, nullptr);
    2663           8 :             if (pszMetadataFile == nullptr)
    2664           8 :                 CPLPopErrorHandler();
    2665           8 :             if (psResult == nullptr)
    2666             :             {
    2667           0 :                 osMetadataFile.clear();
    2668             :             }
    2669           8 :             else if (psResult->pszErrBuf != nullptr ||
    2670           3 :                      psResult->pabyData == nullptr)
    2671             :             {
    2672           5 :                 CPLHTTPDestroyResult(psResult);
    2673           5 :                 osMetadataFile.clear();
    2674             : 
    2675           5 :                 if (i == 0 && pszMetadataFile == nullptr)
    2676             :                 {
    2677             :                     // tileserver-gl metadata file:
    2678             :                     // If opening /path/to/foo/0, try looking for
    2679             :                     // /path/to/foo.json
    2680             :                     CPLString osParentDir(
    2681           6 :                         CPLGetPathSafe(poOpenInfo->pszFilename));
    2682           6 :                     osMetadataFile = CPLFormFilenameSafe(
    2683           6 :                         CPLGetPathSafe(osParentDir).c_str(),
    2684           3 :                         CPLGetFilename(osParentDir), "json");
    2685           3 :                     continue;
    2686           2 :                 }
    2687             :             }
    2688             :             else
    2689             :             {
    2690           3 :                 bMetadataFileExists = true;
    2691             :                 osMetadataContent =
    2692           3 :                     reinterpret_cast<const char *>(psResult->pabyData);
    2693           3 :                 CPLHTTPDestroyResult(psResult);
    2694             :             }
    2695           5 :             break;
    2696             :         }
    2697             :     }
    2698          19 :     else if (!osMetadataFile.empty())
    2699             :     {
    2700          16 :         bMetadataFileExists = (VSIStatL(osMetadataFile, &sStat) == 0);
    2701          16 :         if (!bMetadataFileExists && pszMetadataFile == nullptr)
    2702             :         {
    2703             :             // tileserver-gl metadata file:
    2704             :             // If opening /path/to/foo/0, try looking for /path/to/foo.json
    2705           2 :             CPLString osParentDir(CPLGetPathSafe(poOpenInfo->pszFilename));
    2706             :             osMetadataFile =
    2707           4 :                 CPLFormFilenameSafe(CPLGetPathSafe(osParentDir).c_str(),
    2708           2 :                                     CPLGetFilename(osParentDir), "json");
    2709           2 :             bMetadataFileExists = (VSIStatL(osMetadataFile, &sStat) == 0);
    2710             :         }
    2711             :     }
    2712             : 
    2713          24 :     if (!bMetadataFileExists)
    2714             :     {
    2715             :         // If we don't have a metadata file, iterate through all tiles to
    2716             :         // establish the layer definitions.
    2717           6 :         OGRMVTDataset *poDS = nullptr;
    2718           6 :         bool bTryToListDir =
    2719          12 :             !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl/") &&
    2720           6 :             !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl_streaming/") &&
    2721           6 :             !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl?") &&
    2722          16 :             !STARTS_WITH(poOpenInfo->pszFilename, "http://") &&
    2723           4 :             !STARTS_WITH(poOpenInfo->pszFilename, "https://");
    2724           6 :         CPLStringList aosDirContent;
    2725           6 :         if (bTryToListDir)
    2726             :         {
    2727           4 :             aosDirContent = VSIReadDir(poOpenInfo->pszFilename);
    2728           4 :             aosDirContent = StripDummyEntries(aosDirContent);
    2729             :         }
    2730          12 :         const int nMaxTiles = atoi(CSLFetchNameValueDef(
    2731           6 :             poOpenInfo->papszOpenOptions,
    2732             :             "TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN", "1000"));
    2733           6 :         int nCountTiles = 0;
    2734           6 :         int nFailedAttempts = 0;
    2735          14 :         for (int i = 0; i < (bTryToListDir ? aosDirContent.Count() : (1 << nZ));
    2736             :              i++)
    2737             :         {
    2738           8 :             if (bTryToListDir)
    2739             :             {
    2740           6 :                 if (CPLGetValueType(aosDirContent[i]) != CPL_VALUE_INTEGER)
    2741             :                 {
    2742           0 :                     continue;
    2743             :                 }
    2744             :             }
    2745           8 :             CPLString osSubDir = CPLFormFilenameSafe(
    2746           8 :                 poOpenInfo->pszFilename,
    2747           8 :                 bTryToListDir ? aosDirContent[i] : CPLSPrintf("%d", i),
    2748           8 :                 nullptr);
    2749           8 :             CPLStringList aosSubDirContent;
    2750           8 :             if (bTryToListDir)
    2751             :             {
    2752           6 :                 aosSubDirContent = VSIReadDir(osSubDir);
    2753           6 :                 aosSubDirContent = StripDummyEntries(aosSubDirContent);
    2754             :             }
    2755          20 :             for (int j = 0;
    2756          20 :                  j < (bTryToListDir ? aosSubDirContent.Count() : (1 << nZ));
    2757             :                  j++)
    2758             :             {
    2759          12 :                 if (bTryToListDir)
    2760             :                 {
    2761          10 :                     if (CPLGetValueType(
    2762          20 :                             CPLGetBasenameSafe(aosSubDirContent[j]).c_str()) !=
    2763             :                         CPL_VALUE_INTEGER)
    2764             :                     {
    2765           0 :                         continue;
    2766             :                     }
    2767             :                 }
    2768             :                 const std::string osFilename(CPLFormFilenameSafe(
    2769             :                     osSubDir,
    2770             :                     bTryToListDir
    2771          10 :                         ? aosSubDirContent[j]
    2772           2 :                         : CPLSPrintf("%d.%s", j, osTileExtension.c_str()),
    2773          24 :                     nullptr));
    2774          12 :                 GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(),
    2775          12 :                                        GA_ReadOnly);
    2776          12 :                 oOpenInfo.papszOpenOptions =
    2777          12 :                     CSLSetNameValue(nullptr, "METADATA_FILE", "");
    2778          12 :                 oOpenInfo.papszOpenOptions =
    2779          12 :                     CSLSetNameValue(oOpenInfo.papszOpenOptions,
    2780             :                                     "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
    2781          12 :                 auto poTileDS = OGRMVTDataset::Open(
    2782             :                     &oOpenInfo, /* bRecurseAllowed = */ false);
    2783          12 :                 if (poTileDS)
    2784             :                 {
    2785          11 :                     if (poDS == nullptr)
    2786             :                     {
    2787           5 :                         poDS = new OGRMVTDataset(nullptr);
    2788           5 :                         poDS->m_osTileExtension = osTileExtension;
    2789           5 :                         poDS->SetDescription(poOpenInfo->pszFilename);
    2790          10 :                         poDS->m_bClip =
    2791           5 :                             CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP",
    2792           5 :                                          poDS->m_bClip);
    2793             :                     }
    2794             : 
    2795          26 :                     for (int k = 0; k < poTileDS->GetLayerCount(); k++)
    2796             :                     {
    2797          15 :                         OGRLayer *poTileLayer = poTileDS->GetLayer(k);
    2798             :                         OGRFeatureDefn *poTileLDefn =
    2799          15 :                             poTileLayer->GetLayerDefn();
    2800             :                         OGRwkbGeometryType eTileGeomType =
    2801          15 :                             poTileLDefn->GetGeomType();
    2802             :                         OGRwkbGeometryType eTileGeomTypeColl =
    2803          15 :                             OGR_GT_GetCollection(eTileGeomType);
    2804          15 :                         if (eTileGeomTypeColl != wkbUnknown &&
    2805             :                             eTileGeomTypeColl != eTileGeomType)
    2806             :                         {
    2807           6 :                             eTileGeomType = eTileGeomTypeColl;
    2808             :                         }
    2809             : 
    2810             :                         OGRLayer *poLayer =
    2811          15 :                             poDS->GetLayerByName(poTileLayer->GetName());
    2812             :                         OGRFeatureDefn *poLDefn;
    2813          15 :                         if (poLayer == nullptr)
    2814             :                         {
    2815          14 :                             CPLJSONObject oFields;
    2816           7 :                             oFields.Deinit();
    2817           7 :                             poDS->m_apoLayers.push_back(
    2818           7 :                                 std::make_unique<OGRMVTDirectoryLayer>(
    2819           7 :                                     poDS, poTileLayer->GetName(),
    2820           7 :                                     poOpenInfo->pszFilename, oFields,
    2821           7 :                                     CPLJSONArray(), bJsonField, bAddTileFields,
    2822           7 :                                     wkbUnknown, nullptr));
    2823           7 :                             poLayer = poDS->m_apoLayers.back().get();
    2824           7 :                             poLDefn = poLayer->GetLayerDefn();
    2825           7 :                             poLDefn->SetGeomType(eTileGeomType);
    2826             :                         }
    2827             :                         else
    2828             :                         {
    2829           8 :                             poLDefn = poLayer->GetLayerDefn();
    2830           8 :                             if (poLayer->GetGeomType() != eTileGeomType)
    2831             :                             {
    2832           0 :                                 poLDefn->SetGeomType(wkbUnknown);
    2833             :                             }
    2834             :                         }
    2835             : 
    2836          15 :                         if (!bJsonField)
    2837             :                         {
    2838          22 :                             for (int l = 1; l < poTileLDefn->GetFieldCount();
    2839             :                                  l++)
    2840             :                             {
    2841             :                                 OGRFieldDefn *poTileFDefn =
    2842           8 :                                     poTileLDefn->GetFieldDefn(l);
    2843           8 :                                 int nFieldIdx = poLDefn->GetFieldIndex(
    2844           8 :                                     poTileFDefn->GetNameRef());
    2845           8 :                                 if (nFieldIdx < 0)
    2846             :                                 {
    2847           2 :                                     poLDefn->AddFieldDefn(poTileFDefn);
    2848             :                                 }
    2849             :                                 else
    2850             :                                 {
    2851          12 :                                     MergeFieldDefn(
    2852           6 :                                         poLDefn->GetFieldDefn(nFieldIdx),
    2853             :                                         poTileFDefn->GetType(),
    2854             :                                         poTileFDefn->GetSubType());
    2855             :                                 }
    2856             :                             }
    2857             :                         }
    2858             :                     }
    2859          11 :                     nCountTiles++;
    2860             :                 }
    2861           1 :                 else if (!bTryToListDir)
    2862             :                 {
    2863           1 :                     nFailedAttempts++;
    2864             :                 }
    2865          12 :                 delete poTileDS;
    2866          12 :                 CSLDestroy(oOpenInfo.papszOpenOptions);
    2867             : 
    2868          12 :                 if (nFailedAttempts == 10)
    2869           0 :                     break;
    2870          12 :                 if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
    2871           0 :                     break;
    2872             :             }
    2873             : 
    2874           8 :             if (nFailedAttempts == 10)
    2875           0 :                 break;
    2876           8 :             if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
    2877           0 :                 break;
    2878             :         }
    2879           6 :         return poDS;
    2880             :     }
    2881             : 
    2882          36 :     CPLJSONArray oVectorLayers;
    2883          36 :     CPLJSONArray oTileStatLayers;
    2884          36 :     CPLJSONObject oBounds;
    2885             : 
    2886          18 :     OGRMVTDataset *poDS = new OGRMVTDataset(nullptr);
    2887             : 
    2888             :     CPLString osMetadataMemFilename =
    2889          36 :         VSIMemGenerateHiddenFilename("mvt_metadata.json");
    2890          18 :     if (!LoadMetadata(osMetadataFile, osMetadataContent, oVectorLayers,
    2891             :                       oTileStatLayers, oBounds, poDS->m_poSRS,
    2892          18 :                       poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
    2893          18 :                       poDS->m_dfTileDim0, poDS->m_nTileMatrixWidth0,
    2894          18 :                       poDS->m_nTileMatrixHeight0, osMetadataMemFilename))
    2895             :     {
    2896           0 :         delete poDS;
    2897           0 :         return nullptr;
    2898             :     }
    2899             : 
    2900          18 :     OGREnvelope sExtent;
    2901          18 :     bool bExtentValid = false;
    2902          18 :     if (oBounds.IsValid() && oBounds.GetType() == CPLJSONObject::Type::String)
    2903             :     {
    2904             :         CPLStringList aosTokens(
    2905          51 :             CSLTokenizeString2(oBounds.ToString().c_str(), ",", 0));
    2906          17 :         if (aosTokens.Count() == 4)
    2907             :         {
    2908          17 :             double dfX0 = CPLAtof(aosTokens[0]);
    2909          17 :             double dfY0 = CPLAtof(aosTokens[1]);
    2910          17 :             double dfX1 = CPLAtof(aosTokens[2]);
    2911          17 :             double dfY1 = CPLAtof(aosTokens[3]);
    2912          17 :             ConvertFromWGS84(poDS->m_poSRS, dfX0, dfY0, dfX1, dfY1);
    2913          17 :             bExtentValid = true;
    2914          17 :             sExtent.MinX = dfX0;
    2915          17 :             sExtent.MinY = dfY0;
    2916          17 :             sExtent.MaxX = dfX1;
    2917          17 :             sExtent.MaxY = dfY1;
    2918             :         }
    2919             :     }
    2920           2 :     else if (oBounds.IsValid() &&
    2921           1 :              oBounds.GetType() == CPLJSONObject::Type::Array)
    2922             :     {
    2923             :         // Cf https://free.tilehosting.com/data/v3.json?key=THE_KEY
    2924           2 :         CPLJSONArray oBoundArray = oBounds.ToArray();
    2925           1 :         if (oBoundArray.Size() == 4)
    2926             :         {
    2927           1 :             bExtentValid = true;
    2928           1 :             sExtent.MinX = oBoundArray[0].ToDouble();
    2929           1 :             sExtent.MinY = oBoundArray[1].ToDouble();
    2930           1 :             sExtent.MaxX = oBoundArray[2].ToDouble();
    2931           1 :             sExtent.MaxY = oBoundArray[3].ToDouble();
    2932           1 :             ConvertFromWGS84(poDS->m_poSRS, sExtent.MinX, sExtent.MinY,
    2933             :                              sExtent.MaxX, sExtent.MaxY);
    2934             :         }
    2935             :     }
    2936             : 
    2937          18 :     poDS->SetDescription(poOpenInfo->pszFilename);
    2938          36 :     poDS->m_bClip =
    2939          18 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
    2940          18 :     poDS->m_osTileExtension = std::move(osTileExtension);
    2941          18 :     poDS->m_osMetadataMemFilename = std::move(osMetadataMemFilename);
    2942          38 :     for (int i = 0; i < oVectorLayers.Size(); i++)
    2943             :     {
    2944          60 :         CPLJSONObject oId = oVectorLayers[i].GetObj("id");
    2945          20 :         if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
    2946             :         {
    2947          20 :             OGRwkbGeometryType eGeomType = wkbUnknown;
    2948          20 :             if (oTileStatLayers.IsValid())
    2949             :             {
    2950          19 :                 eGeomType = OGRMVTFindGeomTypeFromTileStat(
    2951          38 :                     oTileStatLayers, oId.ToString().c_str());
    2952             :             }
    2953             : 
    2954          60 :             CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
    2955             :             CPLJSONArray oAttributesFromTileStats =
    2956             :                 OGRMVTFindAttributesFromTileStat(oTileStatLayers,
    2957          40 :                                                  oId.ToString().c_str());
    2958             : 
    2959          20 :             poDS->m_apoLayers.push_back(std::make_unique<OGRMVTDirectoryLayer>(
    2960          40 :                 poDS, oId.ToString().c_str(), poOpenInfo->pszFilename, oFields,
    2961             :                 oAttributesFromTileStats, bJsonField, bAddTileFields, eGeomType,
    2962          40 :                 (bExtentValid) ? &sExtent : nullptr));
    2963             :         }
    2964             :     }
    2965             : 
    2966          18 :     return poDS;
    2967             : }
    2968             : 
    2969             : /************************************************************************/
    2970             : /*                                Open()                                */
    2971             : /************************************************************************/
    2972             : 
    2973         907 : GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo)
    2974             : {
    2975         907 :     return Open(poOpenInfo, true);
    2976             : }
    2977             : 
    2978        1010 : GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo, bool bRecurseAllowed)
    2979             : 
    2980             : {
    2981        1010 :     if (!OGRMVTDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
    2982           0 :         return nullptr;
    2983             : 
    2984        1010 :     VSILFILE *fp = poOpenInfo->fpL;
    2985        2020 :     CPLString osFilename(poOpenInfo->pszFilename);
    2986        1010 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
    2987             :     {
    2988         968 :         osFilename = poOpenInfo->pszFilename + strlen("MVT:");
    2989        1936 :         if (STARTS_WITH(osFilename, "/vsigzip/http://") ||
    2990         968 :             STARTS_WITH(osFilename, "/vsigzip/https://"))
    2991             :         {
    2992           0 :             osFilename = osFilename.substr(strlen("/vsigzip/"));
    2993             :         }
    2994             : 
    2995             :         // If the filename has no extension and is a directory, consider
    2996             :         // we open a directory
    2997             :         VSIStatBufL sStat;
    2998         865 :         if (bRecurseAllowed && !STARTS_WITH(osFilename, "/vsigzip/") &&
    2999         864 :             strchr((CPLGetFilename(osFilename)), '.') == nullptr &&
    3000        1833 :             VSIStatL(osFilename, &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
    3001             :         {
    3002           3 :             GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
    3003           3 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
    3004           3 :             GDALDataset *poDS = OpenDirectory(&oOpenInfo);
    3005           3 :             if (poDS)
    3006           1 :                 poDS->SetDescription(poOpenInfo->pszFilename);
    3007           3 :             return poDS;
    3008             :         }
    3009             : 
    3010             :         // For a network resource, if the filename is an integer, consider it
    3011             :         // is a directory and open as such
    3012        1827 :         if (bRecurseAllowed &&
    3013         862 :             (STARTS_WITH(osFilename, "/vsicurl") ||
    3014         862 :              STARTS_WITH(osFilename, "http://") ||
    3015        1827 :              STARTS_WITH(osFilename, "https://")) &&
    3016           6 :             CPLGetValueType(CPLGetFilename(osFilename)) == CPL_VALUE_INTEGER)
    3017             :         {
    3018           5 :             GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
    3019           5 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
    3020           5 :             GDALDataset *poDS = OpenDirectory(&oOpenInfo);
    3021           5 :             if (poDS)
    3022           4 :                 poDS->SetDescription(poOpenInfo->pszFilename);
    3023           5 :             return poDS;
    3024             :         }
    3025             : 
    3026        1910 :         if (!STARTS_WITH(osFilename, "http://") &&
    3027         950 :             !STARTS_WITH(osFilename, "https://"))
    3028             :         {
    3029             :             CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES",
    3030        1900 :                                           "NO", false);
    3031             :             CPLConfigOptionSetter oSetter2("CPL_VSIL_GZIP_SAVE_INFO", "NO",
    3032        1900 :                                            false);
    3033         950 :             fp = VSIFOpenL(osFilename, "rb");
    3034             :             // Is it a gzipped file ?
    3035         950 :             if (fp && !STARTS_WITH(osFilename, "/vsigzip/"))
    3036             :             {
    3037         948 :                 GByte abyHeaderBytes[2] = {0, 0};
    3038         948 :                 VSIFReadL(abyHeaderBytes, 2, 1, fp);
    3039         948 :                 if (abyHeaderBytes[0] == 0x1F && abyHeaderBytes[1] == 0x8B)
    3040             :                 {
    3041         412 :                     VSIFCloseL(fp);
    3042         412 :                     fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
    3043             :                 }
    3044             :             }
    3045             :         }
    3046             :     }
    3047          84 :     else if (bRecurseAllowed &&
    3048          42 :              (poOpenInfo->bIsDirectory ||
    3049          24 :               (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl") &&
    3050           0 :                CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
    3051             :                    CPL_VALUE_INTEGER)))
    3052             :     {
    3053          18 :         return OpenDirectory(poOpenInfo);
    3054             :     }
    3055             :     // Is it a gzipped file ?
    3056          24 :     else if (poOpenInfo->nHeaderBytes >= 2 &&
    3057          24 :              poOpenInfo->pabyHeader[0] == 0x1F &&
    3058          18 :              poOpenInfo->pabyHeader[1] == 0x8B)
    3059             :     {
    3060             :         CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
    3061          18 :                                       false);
    3062          18 :         fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
    3063             :     }
    3064             :     else
    3065             :     {
    3066           6 :         poOpenInfo->fpL = nullptr;
    3067             :     }
    3068         985 :     if (fp == nullptr && !STARTS_WITH(osFilename, "http://") &&
    3069           1 :         !STARTS_WITH(osFilename, "https://"))
    3070             :     {
    3071           1 :         return nullptr;
    3072             :     }
    3073             : 
    3074        1966 :     CPLString osY = CPLGetBasenameSafe(osFilename);
    3075        2949 :     CPLString osX = CPLGetBasenameSafe(CPLGetPathSafe(osFilename).c_str());
    3076        1966 :     CPLString osZ = CPLGetBasenameSafe(
    3077        3932 :         CPLGetPathSafe(CPLGetPathSafe(osFilename).c_str()).c_str());
    3078         983 :     size_t nPos = osY.find('.');
    3079         983 :     if (nPos != std::string::npos)
    3080           0 :         osY.resize(nPos);
    3081             : 
    3082        1966 :     CPLString osMetadataFile;
    3083         983 :     if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE"))
    3084             :     {
    3085             :         osMetadataFile =
    3086         961 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
    3087             :     }
    3088          22 :     else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
    3089          34 :              CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
    3090          12 :              CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
    3091             :     {
    3092          12 :         osMetadataFile = CPLFormFilenameSafe(
    3093          24 :             CPLGetPathSafe(
    3094          24 :                 CPLGetPathSafe(CPLGetPathSafe(osFilename).c_str()).c_str())
    3095             :                 .c_str(),
    3096          12 :             "metadata.json", nullptr);
    3097          12 :         if (osMetadataFile.find("/vsigzip/") == 0)
    3098             :         {
    3099           2 :             osMetadataFile = osMetadataFile.substr(strlen("/vsigzip/"));
    3100             :         }
    3101             :         VSIStatBufL sStat;
    3102          12 :         if (osMetadataFile.empty() || VSIStatL(osMetadataFile, &sStat) != 0)
    3103             :         {
    3104           1 :             osMetadataFile.clear();
    3105             :         }
    3106             :     }
    3107             : 
    3108         983 :     if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X") &&
    3109        1812 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y") &&
    3110         829 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z"))
    3111             :     {
    3112         829 :         osX = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X");
    3113         829 :         osY = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y");
    3114         829 :         osZ = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z");
    3115             :     }
    3116             : 
    3117             :     GByte *pabyDataMod;
    3118             :     size_t nFileSize;
    3119             : 
    3120         983 :     if (fp == nullptr)
    3121             :     {
    3122             :         bool bSilenceErrors =
    3123          10 :             CPLFetchBool(poOpenInfo->papszOpenOptions,
    3124             :                          "DO_NOT_ERROR_ON_MISSING_TILE", false);
    3125          10 :         if (bSilenceErrors)
    3126           9 :             CPLPushErrorHandler(CPLQuietErrorHandler);
    3127          10 :         CPLHTTPResult *psResult = CPLHTTPFetch(osFilename, nullptr);
    3128          10 :         if (bSilenceErrors)
    3129           9 :             CPLPopErrorHandler();
    3130          10 :         if (psResult == nullptr)
    3131           0 :             return nullptr;
    3132          10 :         if (psResult->pszErrBuf != nullptr)
    3133             :         {
    3134           5 :             CPLHTTPDestroyResult(psResult);
    3135           5 :             return nullptr;
    3136             :         }
    3137           5 :         pabyDataMod = psResult->pabyData;
    3138           5 :         if (pabyDataMod == nullptr)
    3139             :         {
    3140           0 :             CPLHTTPDestroyResult(psResult);
    3141           0 :             return nullptr;
    3142             :         }
    3143           5 :         nFileSize = psResult->nDataLen;
    3144           5 :         psResult->pabyData = nullptr;
    3145           5 :         CPLHTTPDestroyResult(psResult);
    3146             : 
    3147             :         // zlib decompress if needed
    3148           5 :         if (nFileSize > 2 && pabyDataMod[0] == 0x1F && pabyDataMod[1] == 0x8B)
    3149             :         {
    3150           5 :             size_t nOutBytes = 0;
    3151             :             void *pUncompressed =
    3152           5 :                 CPLZLibInflate(pabyDataMod, nFileSize, nullptr, 0, &nOutBytes);
    3153           5 :             CPLFree(pabyDataMod);
    3154           5 :             if (pUncompressed == nullptr)
    3155             :             {
    3156           0 :                 return nullptr;
    3157             :             }
    3158           5 :             pabyDataMod = static_cast<GByte *>(pUncompressed);
    3159           5 :             nFileSize = nOutBytes;
    3160             :         }
    3161             :     }
    3162             :     else
    3163             :     {
    3164             :         // Check file size and ingest into memory
    3165         973 :         VSIFSeekL(fp, 0, SEEK_END);
    3166         973 :         vsi_l_offset nFileSizeL = VSIFTellL(fp);
    3167         973 :         if (nFileSizeL > 10 * 1024 * 1024)
    3168             :         {
    3169           0 :             VSIFCloseL(fp);
    3170           0 :             return nullptr;
    3171             :         }
    3172         973 :         nFileSize = static_cast<size_t>(nFileSizeL);
    3173         973 :         pabyDataMod = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nFileSize + 1));
    3174         973 :         if (pabyDataMod == nullptr)
    3175             :         {
    3176           0 :             VSIFCloseL(fp);
    3177           0 :             return nullptr;
    3178             :         }
    3179         973 :         VSIFSeekL(fp, 0, SEEK_SET);
    3180         973 :         VSIFReadL(pabyDataMod, 1, nFileSize, fp);
    3181         973 :         pabyDataMod[nFileSize] = 0;
    3182         973 :         VSIFCloseL(fp);
    3183             :     }
    3184             : 
    3185         978 :     const GByte *pabyData = pabyDataMod;
    3186             : 
    3187             :     // First scan to browse through layers
    3188         978 :     const GByte *pabyDataLimit = pabyData + nFileSize;
    3189         978 :     int nKey = 0;
    3190         978 :     OGRMVTDataset *poDS = new OGRMVTDataset(pabyDataMod);
    3191         978 :     poDS->SetDescription(poOpenInfo->pszFilename);
    3192        1956 :     poDS->m_bClip =
    3193         978 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
    3194             : 
    3195        1949 :     if (!(CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
    3196         971 :           CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
    3197         946 :           CPLGetValueType(osZ) == CPL_VALUE_INTEGER))
    3198             :     {
    3199             :         // See
    3200             :         // https://github.com/mapbox/mvt-fixtures/tree/master/real-world/compressed
    3201          32 :         int nX = 0;
    3202          32 :         int nY = 0;
    3203          32 :         int nZ = 0;
    3204             :         CPLString osBasename(
    3205          96 :             CPLGetBasenameSafe(CPLGetBasenameSafe(osFilename).c_str()));
    3206          63 :         if (sscanf(osBasename, "%d-%d-%d", &nZ, &nX, &nY) == 3 ||
    3207          31 :             sscanf(osBasename, "%d_%d_%d", &nZ, &nX, &nY) == 3)
    3208             :         {
    3209           1 :             osX = CPLSPrintf("%d", nX);
    3210           1 :             osY = CPLSPrintf("%d", nY);
    3211           1 :             osZ = CPLSPrintf("%d", nZ);
    3212             :         }
    3213             :     }
    3214             : 
    3215        1956 :     CPLJSONArray oVectorLayers;
    3216         978 :     oVectorLayers.Deinit();
    3217             : 
    3218        1956 :     CPLJSONArray oTileStatLayers;
    3219         978 :     oTileStatLayers.Deinit();
    3220             : 
    3221         978 :     if (!osMetadataFile.empty())
    3222             :     {
    3223         912 :         CPLJSONObject oBounds;
    3224         912 :         LoadMetadata(osMetadataFile, CPLString(), oVectorLayers,
    3225             :                      oTileStatLayers, oBounds, poDS->m_poSRS,
    3226         912 :                      poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
    3227         912 :                      poDS->m_dfTileDim0, poDS->m_nTileMatrixWidth0,
    3228        1824 :                      poDS->m_nTileMatrixHeight0, CPLString());
    3229             :     }
    3230             : 
    3231             :     const char *pszGeorefTopX =
    3232         978 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPX");
    3233             :     const char *pszGeorefTopY =
    3234         978 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPY");
    3235             :     const char *pszGeorefTileDimX =
    3236         978 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMX");
    3237             :     const char *pszGeorefTileDimY =
    3238         978 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMY");
    3239         978 :     if (pszGeorefTopX && pszGeorefTopY && pszGeorefTileDimX &&
    3240             :         pszGeorefTileDimY)
    3241             :     {
    3242           3 :         poDS->m_bGeoreferenced = true;
    3243           3 :         poDS->m_dfTileDimX = CPLAtof(pszGeorefTileDimX);
    3244           3 :         poDS->m_dfTileDimY = CPLAtof(pszGeorefTileDimY);
    3245           3 :         poDS->m_dfTopX = CPLAtof(pszGeorefTopX);
    3246           3 :         poDS->m_dfTopY = CPLAtof(pszGeorefTopY);
    3247           3 :         poDS->m_poSRS->Release();
    3248           3 :         poDS->m_poSRS = nullptr;
    3249             :     }
    3250         975 :     else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
    3251        1922 :              CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
    3252         947 :              CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
    3253             :     {
    3254         947 :         int nX = atoi(osX);
    3255         947 :         int nY = atoi(osY);
    3256         947 :         int nZ = atoi(osZ);
    3257         947 :         if (nZ >= 0 && nZ < 30 && nX >= 0 &&
    3258         947 :             nX < (static_cast<int64_t>(1) << nZ) * poDS->m_nTileMatrixWidth0 &&
    3259         945 :             nY >= 0 &&
    3260         945 :             nY < (static_cast<int64_t>(1) << nZ) * poDS->m_nTileMatrixHeight0)
    3261             :         {
    3262         945 :             poDS->m_bGeoreferenced = true;
    3263         945 :             poDS->m_dfTileDimX = poDS->m_dfTileDim0 / (1 << nZ);
    3264         945 :             poDS->m_dfTileDimY = poDS->m_dfTileDimX;
    3265         945 :             poDS->m_dfTopX = poDS->m_dfTopXOrigin + nX * poDS->m_dfTileDimX;
    3266         945 :             poDS->m_dfTopY = poDS->m_dfTopYOrigin - nY * poDS->m_dfTileDimY;
    3267             :         }
    3268             :     }
    3269             : 
    3270             :     try
    3271             :     {
    3272        2043 :         while (pabyData < pabyDataLimit)
    3273             :         {
    3274        1066 :             READ_FIELD_KEY(nKey);
    3275        1066 :             if (nKey == MAKE_KEY(knLAYER, WT_DATA))
    3276             :             {
    3277        1065 :                 unsigned int nLayerSize = 0;
    3278        1065 :                 READ_SIZE(pabyData, pabyDataLimit, nLayerSize);
    3279        1065 :                 const GByte *pabyDataLayer = pabyData;
    3280        1065 :                 const GByte *pabyDataLimitLayer = pabyData + nLayerSize;
    3281        1325 :                 while (pabyData < pabyDataLimitLayer)
    3282             :                 {
    3283        1325 :                     READ_VARINT32(pabyData, pabyDataLimitLayer, nKey);
    3284        1325 :                     if (nKey == MAKE_KEY(knLAYER_NAME, WT_DATA))
    3285             :                     {
    3286        1065 :                         char *pszLayerName = nullptr;
    3287        1065 :                         READ_TEXT(pabyData, pabyDataLimitLayer, pszLayerName);
    3288             : 
    3289        2130 :                         CPLJSONObject oFields;
    3290        1065 :                         oFields.Deinit();
    3291        1065 :                         if (oVectorLayers.IsValid())
    3292             :                         {
    3293        1098 :                             for (int i = 0; i < oVectorLayers.Size(); i++)
    3294             :                             {
    3295             :                                 CPLJSONObject oId =
    3296        2196 :                                     oVectorLayers[i].GetObj("id");
    3297        2196 :                                 if (oId.IsValid() &&
    3298        1098 :                                     oId.GetType() ==
    3299             :                                         CPLJSONObject::Type::String)
    3300             :                                 {
    3301        1098 :                                     if (oId.ToString() == pszLayerName)
    3302             :                                     {
    3303             :                                         oFields =
    3304         969 :                                             oVectorLayers[i].GetObj("fields");
    3305         969 :                                         break;
    3306             :                                     }
    3307             :                                 }
    3308             :                             }
    3309             :                         }
    3310             : 
    3311        1065 :                         OGRwkbGeometryType eGeomType = wkbUnknown;
    3312        1065 :                         if (oTileStatLayers.IsValid())
    3313             :                         {
    3314         566 :                             eGeomType = OGRMVTFindGeomTypeFromTileStat(
    3315             :                                 oTileStatLayers, pszLayerName);
    3316             :                         }
    3317             :                         CPLJSONArray oAttributesFromTileStats =
    3318             :                             OGRMVTFindAttributesFromTileStat(oTileStatLayers,
    3319        2130 :                                                              pszLayerName);
    3320             : 
    3321        1065 :                         poDS->m_apoLayers.push_back(
    3322        2130 :                             std::make_unique<OGRMVTLayer>(
    3323             :                                 poDS, pszLayerName, pabyDataLayer, nLayerSize,
    3324             :                                 oFields, oAttributesFromTileStats, eGeomType));
    3325        1065 :                         CPLFree(pszLayerName);
    3326        1065 :                         break;
    3327             :                     }
    3328             :                     else
    3329             :                     {
    3330         260 :                         SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimitLayer, FALSE);
    3331             :                     }
    3332             :                 }
    3333        1065 :                 pabyData = pabyDataLimitLayer;
    3334             :             }
    3335             :             else
    3336             :             {
    3337           1 :                 if (nKey == 0 && !poDS->m_apoLayers.empty())
    3338             :                 {
    3339             :                     // File attached to https://github.com/OSGeo/gdal/issues/13268
    3340             :                     // has 0-byte padding after the layer definition.
    3341           1 :                     break;
    3342             :                 }
    3343           0 :                 SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
    3344             :             }
    3345             :         }
    3346             : 
    3347         978 :         return poDS;
    3348             :     }
    3349           0 :     catch (const GPBException &e)
    3350             :     {
    3351           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
    3352           0 :         delete poDS;
    3353           0 :         return nullptr;
    3354             :     }
    3355             : }
    3356             : 
    3357             : #ifdef HAVE_MVT_WRITE_SUPPORT
    3358             : 
    3359             : /************************************************************************/
    3360             : /*                         OGRMVTWriterDataset                          */
    3361             : /************************************************************************/
    3362             : 
    3363             : class OGRMVTWriterLayer;
    3364             : 
    3365             : struct OGRMVTFeatureContent
    3366             : {
    3367             :     std::vector<std::pair<std::string, MVTTileLayerValue>> oValues;
    3368             :     GIntBig nFID;
    3369             : };
    3370             : 
    3371             : class OGRMVTWriterDataset final : public GDALDataset
    3372             : {
    3373             :     class MVTFieldProperties
    3374             :     {
    3375             :       public:
    3376             :         CPLString m_osName;
    3377             :         std::set<MVTTileLayerValue> m_oSetValues;
    3378             :         std::set<MVTTileLayerValue> m_oSetAllValues;
    3379             :         double m_dfMinVal = 0;
    3380             :         double m_dfMaxVal = 0;
    3381             :         bool m_bAllInt = false;
    3382             :         MVTTileLayerValue::ValueType m_eType =
    3383             :             MVTTileLayerValue::ValueType::NONE;
    3384             :     };
    3385             : 
    3386             :     class MVTLayerProperties
    3387             :     {
    3388             :       public:
    3389             :         int m_nMinZoom = 0;
    3390             :         int m_nMaxZoom = 0;
    3391             :         std::map<MVTTileLayerFeature::GeomType, GIntBig> m_oCountGeomType;
    3392             :         std::map<CPLString, size_t> m_oMapFieldNameToIdx;
    3393             :         std::vector<MVTFieldProperties> m_aoFields;
    3394             :         std::set<CPLString> m_oSetFields;
    3395             :     };
    3396             : 
    3397             :     std::vector<std::unique_ptr<OGRMVTWriterLayer>> m_apoLayers;
    3398             :     CPLString m_osTempDB;
    3399             :     mutable std::mutex m_oDBMutex;
    3400             :     mutable bool m_bWriteFeatureError = false;
    3401             :     sqlite3_vfs *m_pMyVFS = nullptr;
    3402             :     sqlite3 *m_hDB = nullptr;
    3403             :     sqlite3_stmt *m_hInsertStmt = nullptr;
    3404             :     int m_nMinZoom = 0;
    3405             :     int m_nMaxZoom = 5;
    3406             :     double m_dfSimplification = 0.0;
    3407             :     double m_dfSimplificationMaxZoom = 0.0;
    3408             :     CPLJSONDocument m_oConf;
    3409             :     unsigned m_nExtent = knDEFAULT_EXTENT;
    3410             :     int m_nMetadataVersion = 2;
    3411             :     int m_nMVTVersion = 2;
    3412             :     int m_nBuffer = 5 * knDEFAULT_EXTENT / 256;
    3413             :     bool m_bGZip = true;
    3414             :     mutable CPLWorkerThreadPool m_oThreadPool;
    3415             :     bool m_bThreadPoolOK = false;
    3416             :     mutable GIntBig m_nTempTiles = 0;
    3417             :     CPLString m_osName;
    3418             :     CPLString m_osDescription;
    3419             :     CPLString m_osType{"overlay"};
    3420             :     sqlite3 *m_hDBMBTILES = nullptr;
    3421             :     OGREnvelope m_oEnvelope;
    3422             :     bool m_bMaxTileSizeOptSpecified = false;
    3423             :     bool m_bMaxFeaturesOptSpecified = false;
    3424             :     unsigned m_nMaxTileSize = 500000;
    3425             :     unsigned m_nMaxFeatures = 200000;
    3426             :     std::map<std::string, std::string> m_oMapLayerNameToDesc;
    3427             :     std::map<std::string, GIntBig> m_oMapLayerNameToFeatureCount;
    3428             :     CPLString m_osBounds;
    3429             :     CPLString m_osCenter;
    3430             :     CPLString m_osExtension{"pbf"};
    3431             :     OGRSpatialReference *m_poSRS = nullptr;
    3432             :     double m_dfTopX = 0.0;
    3433             :     double m_dfTopY = 0.0;
    3434             :     double m_dfTileDim0 = 0.0;
    3435             :     int m_nTileMatrixWidth0 =
    3436             :         1;  // Number of tiles along X axis at zoom level 0
    3437             :     int m_nTileMatrixHeight0 =
    3438             :         1;  // Number of tiles along Y axis at zoom level 0
    3439             :     bool m_bReuseTempFile = false;  // debug only
    3440             : 
    3441             :     OGRErr PreGenerateForTile(
    3442             :         int nZ, int nX, int nY, const CPLString &osTargetName,
    3443             :         bool bIsMaxZoomForLayer,
    3444             :         const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
    3445             :         GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
    3446             :         const OGREnvelope &sEnvelope) const;
    3447             : 
    3448             :     static void WriterTaskFunc(void *pParam);
    3449             : 
    3450             :     OGRErr PreGenerateForTileReal(int nZ, int nX, int nY,
    3451             :                                   const CPLString &osTargetName,
    3452             :                                   bool bIsMaxZoomForLayer,
    3453             :                                   const OGRMVTFeatureContent *poFeatureContent,
    3454             :                                   GIntBig nSerial, const OGRGeometry *poGeom,
    3455             :                                   const OGREnvelope &sEnvelope) const;
    3456             : 
    3457             :     void ConvertToTileCoords(double dfX, double dfY, int &nX, int &nY,
    3458             :                              double dfTopX, double dfTopY,
    3459             :                              double dfTileDim) const;
    3460             : 
    3461             :     enum class ExpectedWindingOrder
    3462             :     {
    3463             :         NONE,
    3464             :         CLOCKWISE,
    3465             :         COUNTERCLOCKWISE,
    3466             :     };
    3467             :     bool EncodeLineString(MVTTileLayerFeature *poGPBFeature,
    3468             :                           const OGRLineString *poLS, OGRLineString *poOutLS,
    3469             :                           bool bWriteLastPoint,
    3470             :                           ExpectedWindingOrder eExpectedWindingOrder,
    3471             :                           GUInt32 nMinLineTo, double dfTopX, double dfTopY,
    3472             :                           double dfTileDim, int &nLastX, int &nLastY) const;
    3473             :     bool EncodePolygon(MVTTileLayerFeature *poGPBFeature,
    3474             :                        const OGRPolygon *poPoly, OGRPolygon *poOutPoly,
    3475             :                        double dfTopX, double dfTopY, double dfTileDim,
    3476             :                        int &nLastX, int &nLastY, double &dfArea) const;
    3477             : #ifdef notdef
    3478             :     bool EncodeRepairedOuterRing(MVTTileLayerFeature *poGPBFeature,
    3479             :                                  OGRPolygon &oOutPoly, int &nLastX,
    3480             :                                  int &nLastY) const;
    3481             : #endif
    3482             : 
    3483             :     static void UpdateLayerProperties(MVTLayerProperties *poLayerProperties,
    3484             :                                       const std::string &osKey,
    3485             :                                       const MVTTileLayerValue &oValue);
    3486             : 
    3487             :     void EncodeFeature(const void *pabyBlob, int nBlobSize,
    3488             :                        std::shared_ptr<MVTTileLayer> &poTargetLayer,
    3489             :                        std::map<CPLString, GUInt32> &oMapKeyToIdx,
    3490             :                        std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
    3491             :                        MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
    3492             :                        unsigned &nFeaturesInTile);
    3493             : 
    3494             :     std::string
    3495             :     EncodeTile(int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer,
    3496             :                sqlite3_stmt *hStmtRows,
    3497             :                std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
    3498             :                std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead);
    3499             : 
    3500             :     std::string RecodeTileLowerResolution(int nZ, int nX, int nY, int nExtent,
    3501             :                                           sqlite3_stmt *hStmtLayer,
    3502             :                                           sqlite3_stmt *hStmtRows);
    3503             : 
    3504             :     bool CreateOutput();
    3505             : 
    3506             :     bool GenerateMetadata(size_t nLayers,
    3507             :                           const std::map<CPLString, MVTLayerProperties> &oMap);
    3508             : 
    3509             :   public:
    3510             :     OGRMVTWriterDataset();
    3511             :     ~OGRMVTWriterDataset() override;
    3512             : 
    3513             :     CPLErr Close(GDALProgressFunc = nullptr, void * = nullptr) override;
    3514             : 
    3515           0 :     bool CanReopenWithCurrentDescription() const override
    3516             :     {
    3517           0 :         return false;
    3518             :     }
    3519             : 
    3520             :     OGRLayer *ICreateLayer(const char *pszName,
    3521             :                            const OGRGeomFieldDefn *poGeomFieldDefn,
    3522             :                            CSLConstList papszOptions) override;
    3523             : 
    3524             :     int TestCapability(const char *) const override;
    3525             : 
    3526             :     OGRErr WriteFeature(OGRMVTWriterLayer *poLayer, OGRFeature *poFeature,
    3527             :                         GIntBig nSerial, OGRGeometry *poGeom);
    3528             : 
    3529             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
    3530             :                                int nBandsIn, GDALDataType eDT,
    3531             :                                CSLConstList papszOptions);
    3532             : 
    3533         182 :     OGRSpatialReference *GetSRS()
    3534             :     {
    3535         182 :         return m_poSRS;
    3536             :     }
    3537             : };
    3538             : 
    3539             : /************************************************************************/
    3540             : /*                          OGRMVTWriterLayer                           */
    3541             : /************************************************************************/
    3542             : 
    3543             : class OGRMVTWriterLayer final : public OGRLayer
    3544             : {
    3545             :     friend class OGRMVTWriterDataset;
    3546             : 
    3547             :     OGRMVTWriterDataset *m_poDS = nullptr;
    3548             :     OGRFeatureDefn *m_poFeatureDefn = nullptr;
    3549             :     OGRCoordinateTransformation *m_poCT = nullptr;
    3550             :     GIntBig m_nSerial = 0;
    3551             :     int m_nMinZoom = 0;
    3552             :     int m_nMaxZoom = 5;
    3553             :     CPLString m_osTargetName;
    3554             : 
    3555             :   public:
    3556             :     OGRMVTWriterLayer(OGRMVTWriterDataset *poDS, const char *pszLayerName,
    3557             :                       OGRSpatialReference *poSRS);
    3558             :     ~OGRMVTWriterLayer() override;
    3559             : 
    3560          48 :     void ResetReading() override
    3561             :     {
    3562          48 :     }
    3563             : 
    3564          48 :     OGRFeature *GetNextFeature() override
    3565             :     {
    3566          48 :         return nullptr;
    3567             :     }
    3568             : 
    3569        1441 :     const OGRFeatureDefn *GetLayerDefn() const override
    3570             :     {
    3571        1441 :         return m_poFeatureDefn;
    3572             :     }
    3573             : 
    3574             :     int TestCapability(const char *) const override;
    3575             :     OGRErr ICreateFeature(OGRFeature *) override;
    3576             :     OGRErr CreateField(const OGRFieldDefn *, int) override;
    3577             : 
    3578          49 :     GDALDataset *GetDataset() override
    3579             :     {
    3580          49 :         return m_poDS;
    3581             :     }
    3582             : };
    3583             : 
    3584             : /************************************************************************/
    3585             : /*                         OGRMVTWriterLayer()                          */
    3586             : /************************************************************************/
    3587             : 
    3588         171 : OGRMVTWriterLayer::OGRMVTWriterLayer(OGRMVTWriterDataset *poDS,
    3589             :                                      const char *pszLayerName,
    3590         171 :                                      OGRSpatialReference *poSRSIn)
    3591             : {
    3592         171 :     m_poDS = poDS;
    3593         171 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
    3594         171 :     SetDescription(m_poFeatureDefn->GetName());
    3595         171 :     m_poFeatureDefn->Reference();
    3596             : 
    3597         171 :     m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
    3598             : 
    3599         171 :     if (poSRSIn != nullptr && !poDS->GetSRS()->IsSame(poSRSIn))
    3600             :     {
    3601           3 :         m_poCT = OGRCreateCoordinateTransformation(poSRSIn, poDS->GetSRS());
    3602           3 :         if (m_poCT == nullptr)
    3603             :         {
    3604             :             // If we can't create a transformation, issue a warning - but
    3605             :             // continue the transformation.
    3606           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    3607             :                      "Failed to create coordinate transformation between the "
    3608             :                      "input and target coordinate systems.");
    3609             :         }
    3610             :     }
    3611         171 : }
    3612             : 
    3613             : /************************************************************************/
    3614             : /*                         ~OGRMVTWriterLayer()                         */
    3615             : /************************************************************************/
    3616             : 
    3617         342 : OGRMVTWriterLayer::~OGRMVTWriterLayer()
    3618             : {
    3619         171 :     m_poFeatureDefn->Release();
    3620         171 :     delete m_poCT;
    3621         342 : }
    3622             : 
    3623             : /************************************************************************/
    3624             : /*                           TestCapability()                           */
    3625             : /************************************************************************/
    3626             : 
    3627         359 : int OGRMVTWriterLayer::TestCapability(const char *pszCap) const
    3628             : {
    3629             : 
    3630         359 :     if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCCreateField))
    3631          96 :         return true;
    3632         263 :     return false;
    3633             : }
    3634             : 
    3635             : /************************************************************************/
    3636             : /*                            CreateField()                             */
    3637             : /************************************************************************/
    3638             : 
    3639         313 : OGRErr OGRMVTWriterLayer::CreateField(const OGRFieldDefn *poFieldDefn, int)
    3640             : {
    3641         313 :     m_poFeatureDefn->AddFieldDefn(poFieldDefn);
    3642         313 :     return OGRERR_NONE;
    3643             : }
    3644             : 
    3645             : /************************************************************************/
    3646             : /*                           ICreateFeature()                           */
    3647             : /************************************************************************/
    3648             : 
    3649         295 : OGRErr OGRMVTWriterLayer::ICreateFeature(OGRFeature *poFeature)
    3650             : {
    3651         295 :     OGRGeometry *poGeom = poFeature->GetGeometryRef();
    3652         295 :     if (poGeom == nullptr || poGeom->IsEmpty())
    3653         102 :         return OGRERR_NONE;
    3654         193 :     if (m_poCT)
    3655             :     {
    3656           2 :         poGeom->transform(m_poCT);
    3657             :     }
    3658         193 :     m_nSerial++;
    3659         193 :     return m_poDS->WriteFeature(this, poFeature, m_nSerial, poGeom);
    3660             : }
    3661             : 
    3662             : /************************************************************************/
    3663             : /*                        OGRMVTWriterDataset()                         */
    3664             : /************************************************************************/
    3665             : 
    3666         133 : OGRMVTWriterDataset::OGRMVTWriterDataset()
    3667             : {
    3668             :     // Default WebMercator tiling scheme
    3669         133 :     m_poSRS = new OGRSpatialReference();
    3670         133 :     m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3671             : 
    3672         133 :     InitWebMercatorTilingScheme(m_poSRS, m_dfTopX, m_dfTopY, m_dfTileDim0);
    3673         133 : }
    3674             : 
    3675             : /************************************************************************/
    3676             : /*                        ~OGRMVTWriterDataset()                        */
    3677             : /************************************************************************/
    3678             : 
    3679         266 : OGRMVTWriterDataset::~OGRMVTWriterDataset()
    3680             : {
    3681         133 :     OGRMVTWriterDataset::Close();
    3682             : 
    3683         133 :     if (m_pMyVFS)
    3684             :     {
    3685         133 :         sqlite3_vfs_unregister(m_pMyVFS);
    3686         133 :         CPLFree(m_pMyVFS->pAppData);
    3687         133 :         CPLFree(m_pMyVFS);
    3688             :     }
    3689             : 
    3690         133 :     m_poSRS->Release();
    3691         266 : }
    3692             : 
    3693             : /************************************************************************/
    3694             : /*                               Close()                                */
    3695             : /************************************************************************/
    3696             : 
    3697         256 : CPLErr OGRMVTWriterDataset::Close(GDALProgressFunc, void *)
    3698             : {
    3699         256 :     CPLErr eErr = CE_None;
    3700         256 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    3701             :     {
    3702         133 :         if (GetDescription()[0] != '\0')
    3703             :         {
    3704         123 :             if (!CreateOutput())
    3705           3 :                 eErr = CE_Failure;
    3706             :         }
    3707         133 :         if (m_hInsertStmt != nullptr)
    3708             :         {
    3709         130 :             sqlite3_finalize(m_hInsertStmt);
    3710             :         }
    3711         133 :         if (m_hDB)
    3712             :         {
    3713         130 :             sqlite3_close(m_hDB);
    3714             :         }
    3715         133 :         if (m_hDBMBTILES)
    3716             :         {
    3717          76 :             sqlite3_close(m_hDBMBTILES);
    3718             :         }
    3719         262 :         if (!m_osTempDB.empty() && !m_bReuseTempFile &&
    3720         129 :             CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
    3721             :         {
    3722         128 :             VSIUnlink(m_osTempDB);
    3723             :         }
    3724             : 
    3725         133 :         if (GDALDataset::Close() != CE_None)
    3726           0 :             eErr = CE_Failure;
    3727             :     }
    3728         256 :     return eErr;
    3729             : }
    3730             : 
    3731             : /************************************************************************/
    3732             : /*                        ConvertToTileCoords()                         */
    3733             : /************************************************************************/
    3734             : 
    3735       20126 : void OGRMVTWriterDataset::ConvertToTileCoords(double dfX, double dfY, int &nX,
    3736             :                                               int &nY, double dfTopX,
    3737             :                                               double dfTopY,
    3738             :                                               double dfTileDim) const
    3739             : {
    3740       20126 :     if (dfTileDim == 0)
    3741             :     {
    3742        3022 :         nX = static_cast<int>(dfX);
    3743        3022 :         nY = static_cast<int>(dfY);
    3744             :     }
    3745             :     else
    3746             :     {
    3747       17104 :         nX = static_cast<int>(
    3748       17104 :             std::round((dfX - dfTopX) * m_nExtent / dfTileDim));
    3749       17104 :         nY = static_cast<int>(
    3750       17104 :             std::round((dfTopY - dfY) * m_nExtent / dfTileDim));
    3751             :     }
    3752       20126 : }
    3753             : 
    3754             : /************************************************************************/
    3755             : /*                        GetCmdCountCombined()                         */
    3756             : /************************************************************************/
    3757             : 
    3758        4110 : static unsigned GetCmdCountCombined(unsigned int nCmdId, unsigned int nCmdCount)
    3759             : {
    3760        4110 :     return (nCmdId | (nCmdCount << 3));
    3761             : }
    3762             : 
    3763             : /************************************************************************/
    3764             : /*                          EncodeLineString()                          */
    3765             : /************************************************************************/
    3766             : 
    3767        3016 : bool OGRMVTWriterDataset::EncodeLineString(
    3768             :     MVTTileLayerFeature *poGPBFeature, const OGRLineString *poLS,
    3769             :     OGRLineString *poOutLS, bool bWriteLastPoint,
    3770             :     ExpectedWindingOrder eExpectedWindingOrder, GUInt32 nMinLineTo,
    3771             :     double dfTopX, double dfTopY, double dfTileDim, int &nLastX,
    3772             :     int &nLastY) const
    3773             : {
    3774        3016 :     const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    3775        3016 :     const int nLastXOri = nLastX;
    3776        3016 :     const int nLastYOri = nLastY;
    3777        3016 :     GUInt32 nLineToCount = 0;
    3778        3016 :     const int nPoints = poLS->getNumPoints() - (bWriteLastPoint ? 0 : 1);
    3779        3016 :     bool bReverseOrder = false;
    3780             : 
    3781        4700 :     if (eExpectedWindingOrder != ExpectedWindingOrder::NONE &&
    3782        1684 :         poLS->getNumPoints() >= 4)
    3783             :     {
    3784             :         // Do the check on winding order in integer coordinates, since very flat
    3785             :         // rings in non rounded coordinates can change orientation after going
    3786             :         // to integer coordinates! In that case, let's remove them if they are
    3787             :         // inner rings.
    3788        1684 :         int nLastXTmp = nLastX;
    3789        1684 :         int nLastYTmp = nLastY;
    3790        1684 :         OGRLinearRing oRingInteger;
    3791       12774 :         for (int i = 0; i < nPoints; i++)
    3792             :         {
    3793             :             int nX, nY;
    3794       11090 :             const double dfX = poLS->getX(i);
    3795       11090 :             const double dfY = poLS->getY(i);
    3796       11090 :             ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
    3797       11090 :             const int nDiffX = nX - nLastXTmp;
    3798       11090 :             const int nDiffY = nY - nLastYTmp;
    3799       11090 :             if (i == 0 || nDiffX != 0 || nDiffY != 0)
    3800             :             {
    3801             :                 // The minus sign is because the Y axis is positive-downward
    3802             :                 // in vector tile coordinates!
    3803             :                 // Cf https://docs.mapbox.com/data/tilesets/guides/vector-tiles-standards/#winding-order
    3804        5031 :                 oRingInteger.addPoint(nX, -nY);
    3805        5031 :                 nLastXTmp = nX;
    3806        5031 :                 nLastYTmp = nY;
    3807             :             }
    3808             :         }
    3809        1684 :         oRingInteger.closeRings();
    3810        1684 :         if (oRingInteger.getNumPoints() < 4)
    3811        1226 :             return false;
    3812         458 :         const auto bIsClockWise = oRingInteger.isClockwise();
    3813         458 :         if (eExpectedWindingOrder == ExpectedWindingOrder::COUNTERCLOCKWISE)
    3814             :         {
    3815         387 :             if ((dfTileDim != 0 && bIsClockWise != poLS->isClockwise()) ||
    3816         124 :                 (dfTileDim == 0 && bIsClockWise == poLS->isClockwise()))
    3817             :             {
    3818           2 :                 return false;
    3819             :             }
    3820             :         }
    3821         456 :         bReverseOrder =
    3822         195 :             (eExpectedWindingOrder == ExpectedWindingOrder::CLOCKWISE &&
    3823         912 :              !bIsClockWise) ||
    3824         261 :             (eExpectedWindingOrder == ExpectedWindingOrder::COUNTERCLOCKWISE &&
    3825             :              bIsClockWise);
    3826             :     }
    3827             : 
    3828        1788 :     int nFirstX = 0;
    3829        1788 :     int nFirstY = 0;
    3830        1788 :     int nLastXValid = nLastX;
    3831        1788 :     int nLastYValid = nLastY;
    3832        1788 :     if (poOutLS)
    3833        1788 :         poOutLS->setNumPoints(nPoints);
    3834             : 
    3835        9430 :     for (int i = 0; i < nPoints; i++)
    3836             :     {
    3837             :         int nX, nY;
    3838        7642 :         int nSrcIdx = bReverseOrder ? poLS->getNumPoints() - 1 - i : i;
    3839        7642 :         double dfX = poLS->getX(nSrcIdx);
    3840        7642 :         double dfY = poLS->getY(nSrcIdx);
    3841        7642 :         ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
    3842        7642 :         int nDiffX = nX - nLastX;
    3843        7642 :         int nDiffY = nY - nLastY;
    3844        7642 :         if (i == 0 || nDiffX != 0 || nDiffY != 0)
    3845             :         {
    3846        5150 :             if (i > 0)
    3847             :             {
    3848        3362 :                 nLineToCount++;
    3849        3362 :                 if (nLineToCount == 1)
    3850             :                 {
    3851         528 :                     poGPBFeature->addGeometry(
    3852             :                         GetCmdCountCombined(knCMD_MOVETO, 1));
    3853         528 :                     const int nLastDiffX = nLastX - nLastXOri;
    3854         528 :                     const int nLastDiffY = nLastY - nLastYOri;
    3855         528 :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
    3856         528 :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
    3857         528 :                     if (poOutLS)
    3858         528 :                         poOutLS->setPoint(0, nLastX, nLastY);
    3859             : 
    3860             :                     // To be modified later
    3861         528 :                     poGPBFeature->addGeometry(
    3862             :                         GetCmdCountCombined(knCMD_LINETO, 0));
    3863             :                 }
    3864             : 
    3865        3362 :                 poGPBFeature->addGeometry(EncodeSInt(nDiffX));
    3866        3362 :                 poGPBFeature->addGeometry(EncodeSInt(nDiffY));
    3867        3362 :                 if (poOutLS)
    3868        3362 :                     poOutLS->setPoint(nLineToCount, nX, nY);
    3869             :             }
    3870             :             else
    3871             :             {
    3872        1788 :                 nFirstX = nX;
    3873        1788 :                 nFirstY = nY;
    3874             :             }
    3875        5150 :             nLastXValid = nLastX;
    3876        5150 :             nLastYValid = nLastY;
    3877        5150 :             nLastX = nX;
    3878        5150 :             nLastY = nY;
    3879             :         }
    3880             :     }
    3881             : 
    3882             :     // If last point of ring is identical to first one, discard it
    3883        1788 :     if (nMinLineTo == 2 && nLineToCount > 0 && nFirstX == nLastX &&
    3884          81 :         nFirstY == nLastY)
    3885             :     {
    3886          41 :         poGPBFeature->resizeGeometryArray(poGPBFeature->getGeometryCount() - 2);
    3887          41 :         nLineToCount--;
    3888          41 :         nLastX = nLastXValid;
    3889          41 :         nLastY = nLastYValid;
    3890             :     }
    3891             : 
    3892        1788 :     if (nLineToCount >= nMinLineTo)
    3893             :     {
    3894         528 :         if (poOutLS)
    3895         528 :             poOutLS->setNumPoints(1 + nLineToCount);
    3896             :         // Patch actual number of points in LINETO command
    3897         528 :         poGPBFeature->setGeometry(
    3898             :             nInitialSize + 3, GetCmdCountCombined(knCMD_LINETO, nLineToCount));
    3899         528 :         return true;
    3900             :     }
    3901             :     else
    3902             :     {
    3903        1260 :         poGPBFeature->resizeGeometryArray(nInitialSize);
    3904        1260 :         nLastX = nLastXOri;
    3905        1260 :         nLastY = nLastYOri;
    3906        1260 :         return false;
    3907             :     }
    3908             : }
    3909             : 
    3910             : #ifdef notdef
    3911             : /************************************************************************/
    3912             : /*                      EncodeRepairedOuterRing()                       */
    3913             : /************************************************************************/
    3914             : 
    3915             : bool OGRMVTWriterDataset::EncodeRepairedOuterRing(
    3916             :     MVTTileLayerFeature *poGPBFeature, OGRPolygon &oInPoly, int &nLastX,
    3917             :     int &nLastY) const
    3918             : {
    3919             :     std::unique_ptr<OGRGeometry> poFixedGeom(oInPoly.Buffer(0));
    3920             :     if (!poFixedGeom.get() || poFixedGeom->IsEmpty())
    3921             :     {
    3922             :         return false;
    3923             :     }
    3924             : 
    3925             :     OGRPolygon *poPoly = nullptr;
    3926             :     if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbMultiPolygon)
    3927             :     {
    3928             :         OGRMultiPolygon *poMP = poFixedGeom.get()->toMultiPolygon();
    3929             :         poPoly = poMP->getGeometryRef(0)->toPolygon();
    3930             :     }
    3931             :     else if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbPolygon)
    3932             :     {
    3933             :         poPoly = poFixedGeom.get()->toPolygon();
    3934             :     }
    3935             :     if (!poPoly)
    3936             :         return false;
    3937             : 
    3938             :     OGRLinearRing *poRing = poPoly->getExteriorRing();
    3939             :     const bool bReverseOrder = !poRing->isClockwise();
    3940             : 
    3941             :     const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    3942             :     const int nLastXOri = nLastX;
    3943             :     const int nLastYOri = nLastY;
    3944             :     GUInt32 nLineToCount = 0;
    3945             :     const int nPoints = poRing->getNumPoints() - 1;
    3946             :     auto poOutLinearRing = std::make_unique<OGRLinearRing>();
    3947             :     poOutLinearRing->setNumPoints(nPoints);
    3948             :     for (int i = 0; i < nPoints; i++)
    3949             :     {
    3950             :         int nSrcIdx = bReverseOrder ? poRing->getNumPoints() - 1 - i : i;
    3951             :         double dfX = poRing->getX(nSrcIdx);
    3952             :         double dfY = poRing->getY(nSrcIdx);
    3953             :         int nX = static_cast<int>(std::round(dfX));
    3954             :         int nY = static_cast<int>(std::round(dfY));
    3955             :         if (nX != dfX || nY != dfY)
    3956             :             continue;
    3957             :         int nDiffX = nX - nLastX;
    3958             :         int nDiffY = nY - nLastY;
    3959             :         if (i == 0 || nDiffX != 0 || nDiffY != 0)
    3960             :         {
    3961             :             if (i > 0)
    3962             :             {
    3963             :                 nLineToCount++;
    3964             :                 if (nLineToCount == 1)
    3965             :                 {
    3966             :                     poGPBFeature->addGeometry(
    3967             :                         GetCmdCountCombined(knCMD_MOVETO, 1));
    3968             :                     const int nLastDiffX = nLastX - nLastXOri;
    3969             :                     const int nLastDiffY = nLastY - nLastYOri;
    3970             :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
    3971             :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
    3972             :                     poOutLinearRing->setPoint(0, nLastX, nLastY);
    3973             : 
    3974             :                     // To be modified later
    3975             :                     poGPBFeature->addGeometry(
    3976             :                         GetCmdCountCombined(knCMD_LINETO, 0));
    3977             :                 }
    3978             : 
    3979             :                 poGPBFeature->addGeometry(EncodeSInt(nDiffX));
    3980             :                 poGPBFeature->addGeometry(EncodeSInt(nDiffY));
    3981             :                 poOutLinearRing->setPoint(nLineToCount, nX, nY);
    3982             :             }
    3983             :             nLastX = nX;
    3984             :             nLastY = nY;
    3985             :         }
    3986             :     }
    3987             :     if (nLineToCount >= 2)
    3988             :     {
    3989             :         poOutLinearRing->setNumPoints(1 + nLineToCount);
    3990             :         OGRPolygon oOutPoly;
    3991             :         oOutPoly.addRingDirectly(poOutLinearRing.release());
    3992             :         int bIsValid;
    3993             :         {
    3994             :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    3995             :             bIsValid = oOutPoly.IsValid();
    3996             :         }
    3997             :         if (bIsValid)
    3998             :         {
    3999             :             // Patch actual number of points in LINETO command
    4000             :             poGPBFeature->setGeometry(
    4001             :                 nInitialSize + 3,
    4002             :                 GetCmdCountCombined(knCMD_LINETO, nLineToCount));
    4003             :             poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
    4004             :             return true;
    4005             :         }
    4006             :     }
    4007             : 
    4008             :     poGPBFeature->resizeGeometryArray(nInitialSize);
    4009             :     nLastX = nLastXOri;
    4010             :     nLastY = nLastYOri;
    4011             :     return false;
    4012             : }
    4013             : #endif
    4014             : 
    4015             : /************************************************************************/
    4016             : /*                           EncodePolygon()                            */
    4017             : /************************************************************************/
    4018             : 
    4019        1414 : bool OGRMVTWriterDataset::EncodePolygon(MVTTileLayerFeature *poGPBFeature,
    4020             :                                         const OGRPolygon *poPoly,
    4021             :                                         OGRPolygon *poOutPoly, double dfTopX,
    4022             :                                         double dfTopY, double dfTileDim,
    4023             :                                         int &nLastX, int &nLastY,
    4024             :                                         double &dfArea) const
    4025             : {
    4026        1414 :     dfArea = 0;
    4027        2828 :     auto poOutOuterRing = std::make_unique<OGRLinearRing>();
    4028        1879 :     for (int i = 0; i < 1 + poPoly->getNumInteriorRings(); i++)
    4029             :     {
    4030        1684 :         const OGRLinearRing *poRing = (i == 0) ? poPoly->getExteriorRing()
    4031         270 :                                                : poPoly->getInteriorRing(i - 1);
    4032        3368 :         if (poRing->getNumPoints() < 4 ||
    4033        3368 :             poRing->getX(0) != poRing->getX(poRing->getNumPoints() - 1) ||
    4034        1684 :             poRing->getY(0) != poRing->getY(poRing->getNumPoints() - 1))
    4035             :         {
    4036           0 :             if (i == 0)
    4037        1219 :                 return false;
    4038         189 :             continue;
    4039             :         }
    4040        1684 :         const bool bWriteLastPoint = false;
    4041        1684 :         const auto eExpectedWindingOrder =
    4042        1684 :             ((i == 0) ? ExpectedWindingOrder::CLOCKWISE
    4043             :                       : ExpectedWindingOrder::COUNTERCLOCKWISE);
    4044        1684 :         const GUInt32 nMinLineTo = 2;
    4045           0 :         std::unique_ptr<OGRLinearRing> poOutInnerRing;
    4046        1684 :         if (i > 0)
    4047         270 :             poOutInnerRing = std::make_unique<OGRLinearRing>();
    4048             :         OGRLinearRing *poOutRing =
    4049        1684 :             poOutInnerRing.get() ? poOutInnerRing.get() : poOutOuterRing.get();
    4050             : 
    4051             :         bool bSuccess =
    4052        1684 :             EncodeLineString(poGPBFeature, poRing, poOutRing, bWriteLastPoint,
    4053             :                              eExpectedWindingOrder, nMinLineTo, dfTopX, dfTopY,
    4054             :                              dfTileDim, nLastX, nLastY);
    4055        1684 :         if (!bSuccess)
    4056             :         {
    4057        1228 :             if (i == 0)
    4058        1219 :                 return false;
    4059           9 :             continue;
    4060             :         }
    4061             : 
    4062         456 :         if (poOutPoly == nullptr)
    4063             :         {
    4064         180 :             poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
    4065         180 :             continue;
    4066             :         }
    4067             : 
    4068         276 :         poOutRing->closeRings();
    4069             : 
    4070         276 :         poOutPoly->addRing(poOutRing);
    4071         276 :         if (i > 0)
    4072         139 :             dfArea -= poOutRing->get_Area();
    4073             :         else
    4074         137 :             dfArea = poOutRing->get_Area();
    4075             : 
    4076         276 :         poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
    4077             :     }
    4078             : 
    4079         195 :     return true;
    4080             : }
    4081             : 
    4082             : /************************************************************************/
    4083             : /*                         PreGenerateForTile()                         */
    4084             : /************************************************************************/
    4085             : 
    4086        4053 : OGRErr OGRMVTWriterDataset::PreGenerateForTileReal(
    4087             :     int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
    4088             :     bool bIsMaxZoomForLayer, const OGRMVTFeatureContent *poFeatureContent,
    4089             :     GIntBig nSerial, const OGRGeometry *poGeom,
    4090             :     const OGREnvelope &sEnvelope) const
    4091             : {
    4092        4053 :     double dfTileDim = m_dfTileDim0 / (1 << nZ);
    4093        4053 :     double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
    4094        4053 :     double dfTopX = m_dfTopX + nTileX * dfTileDim;
    4095        4053 :     double dfTopY = m_dfTopY - nTileY * dfTileDim;
    4096        4053 :     double dfBottomRightX = dfTopX + dfTileDim;
    4097        4053 :     double dfBottomRightY = dfTopY - dfTileDim;
    4098        4053 :     double dfIntersectTopX = dfTopX - dfBuffer;
    4099        4053 :     double dfIntersectTopY = dfTopY + dfBuffer;
    4100        4053 :     double dfIntersectBottomRightX = dfBottomRightX + dfBuffer;
    4101        4053 :     double dfIntersectBottomRightY = dfBottomRightY - dfBuffer;
    4102             : 
    4103             :     const OGRGeometry *poIntersection;
    4104        4053 :     std::unique_ptr<OGRGeometry> poIntersectionHolder;  // keep in that scope
    4105        4053 :     if (sEnvelope.MinX >= dfIntersectTopX &&
    4106        4027 :         sEnvelope.MinY >= dfIntersectBottomRightY &&
    4107        4021 :         sEnvelope.MaxX <= dfIntersectBottomRightX &&
    4108        4003 :         sEnvelope.MaxY <= dfIntersectTopY)
    4109             :     {
    4110        3999 :         poIntersection = poGeom;
    4111             :     }
    4112             :     else
    4113             :     {
    4114          54 :         OGRLinearRing *poLR = new OGRLinearRing();
    4115          54 :         poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
    4116          54 :         poLR->addPoint(dfIntersectTopX, dfIntersectBottomRightY);
    4117          54 :         poLR->addPoint(dfIntersectBottomRightX, dfIntersectBottomRightY);
    4118          54 :         poLR->addPoint(dfIntersectBottomRightX, dfIntersectTopY);
    4119          54 :         poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
    4120          54 :         OGRPolygon oPoly;
    4121          54 :         oPoly.addRingDirectly(poLR);
    4122             : 
    4123          54 :         CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4124          54 :         auto poTmp = poGeom->Intersection(&oPoly);
    4125          54 :         poIntersection = poTmp;
    4126          54 :         poIntersectionHolder.reset(poTmp);
    4127          54 :         if (poIntersection == nullptr || poIntersection->IsEmpty())
    4128             :         {
    4129           3 :             return OGRERR_NONE;
    4130             :         }
    4131             :     }
    4132             : 
    4133             :     // Create a layer with a single feature in it
    4134        8100 :     auto poLayer = std::make_shared<MVTTileLayer>();
    4135        8100 :     auto poGPBFeature = std::make_shared<MVTTileLayerFeature>();
    4136        4050 :     poLayer->addFeature(poGPBFeature);
    4137             : 
    4138        4050 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    4139        4050 :     if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
    4140        1388 :         poGPBFeature->setType(MVTTileLayerFeature::GeomType::POINT);
    4141        2662 :     else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
    4142        1323 :         poGPBFeature->setType(MVTTileLayerFeature::GeomType::LINESTRING);
    4143        1339 :     else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
    4144        1339 :         poGPBFeature->setType(MVTTileLayerFeature::GeomType::POLYGON);
    4145             :     else
    4146             :     {
    4147           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
    4148           0 :         return OGRERR_NONE;
    4149             :     }
    4150             : 
    4151             :     OGRwkbGeometryType eGeomToEncodeType =
    4152        4050 :         wkbFlatten(poIntersection->getGeometryType());
    4153             : 
    4154             :     // Simplify contour if requested by user
    4155        4050 :     const OGRGeometry *poGeomToEncode = poIntersection;
    4156        4050 :     std::unique_ptr<OGRGeometry> poGeomSimplified;
    4157        4050 :     const double dfSimplification =
    4158        4050 :         bIsMaxZoomForLayer ? m_dfSimplificationMaxZoom : m_dfSimplification;
    4159        4050 :     if (dfSimplification > 0 &&
    4160          12 :         (eGeomType == wkbLineString || eGeomType == wkbMultiLineString ||
    4161          12 :          eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon))
    4162             :     {
    4163          12 :         const double dfTol = dfTileDim / m_nExtent;
    4164          24 :         poGeomSimplified = std::unique_ptr<OGRGeometry>(
    4165          12 :             poIntersection->SimplifyPreserveTopology(dfTol * dfSimplification));
    4166          12 :         if (poGeomSimplified.get())
    4167             :         {
    4168          12 :             poGeomToEncode = poGeomSimplified.get();
    4169          12 :             eGeomToEncodeType = wkbFlatten(poGeomSimplified->getGeometryType());
    4170             :         }
    4171             :     }
    4172             : 
    4173        4050 :     bool bGeomOK = false;
    4174        4050 :     double dfAreaOrLength = 0.0;
    4175             : 
    4176             :     const auto EmitValidPolygon =
    4177          58 :         [this, &bGeomOK, &dfAreaOrLength,
    4178         186 :          &poGPBFeature](const OGRGeometry *poValidGeom)
    4179             :     {
    4180          58 :         bGeomOK = false;
    4181          58 :         dfAreaOrLength = 0;
    4182          58 :         int nLastX = 0;
    4183          58 :         int nLastY = 0;
    4184             : 
    4185          58 :         if (wkbFlatten(poValidGeom->getGeometryType()) == wkbPolygon)
    4186             :         {
    4187          12 :             const OGRPolygon *poPoly = poValidGeom->toPolygon();
    4188          12 :             double dfPartArea = 0.0;
    4189          12 :             bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
    4190             :                                     0, nLastX, nLastY, dfPartArea);
    4191          12 :             dfAreaOrLength = dfPartArea;
    4192             :         }
    4193          46 :         else if (OGR_GT_IsSubClassOf(poValidGeom->getGeometryType(),
    4194          46 :                                      wkbGeometryCollection))
    4195             :         {
    4196         130 :             for (auto &&poSubGeom : poValidGeom->toGeometryCollection())
    4197             :             {
    4198          88 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
    4199             :                 {
    4200          36 :                     const OGRPolygon *poPoly = poSubGeom->toPolygon();
    4201          36 :                     double dfPartArea = 0.0;
    4202          36 :                     bGeomOK |=
    4203          36 :                         EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
    4204          36 :                                       0, nLastX, nLastY, dfPartArea);
    4205          36 :                     dfAreaOrLength += dfPartArea;
    4206             :                 }
    4207          52 :                 else if (wkbFlatten(poSubGeom->getGeometryType()) ==
    4208             :                          wkbMultiPolygon)
    4209             :                 {
    4210             :                     const OGRMultiPolygon *poMPoly =
    4211           5 :                         poSubGeom->toMultiPolygon();
    4212          15 :                     for (const auto *poPoly : poMPoly)
    4213             :                     {
    4214          10 :                         double dfPartArea = 0.0;
    4215          10 :                         bGeomOK |=
    4216          10 :                             EncodePolygon(poGPBFeature.get(), poPoly, nullptr,
    4217          10 :                                           0, 0, 0, nLastX, nLastY, dfPartArea);
    4218          10 :                         dfAreaOrLength += dfPartArea;
    4219             :                     }
    4220             :                 }
    4221             :             }
    4222             :         }
    4223          58 :     };
    4224             : 
    4225        4050 :     if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
    4226             :     {
    4227        1388 :         if (eGeomToEncodeType == wkbPoint)
    4228             :         {
    4229        1004 :             const OGRPoint *poPoint = poIntersection->toPoint();
    4230             :             int nX, nY;
    4231        1004 :             double dfX = poPoint->getX();
    4232        1004 :             double dfY = poPoint->getY();
    4233        1004 :             bGeomOK = true;
    4234        1004 :             ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
    4235        1004 :             poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_MOVETO, 1));
    4236        1004 :             poGPBFeature->addGeometry(EncodeSInt(nX));
    4237        1004 :             poGPBFeature->addGeometry(EncodeSInt(nY));
    4238             :         }
    4239         384 :         else if (eGeomToEncodeType == wkbMultiPoint ||
    4240             :                  eGeomToEncodeType == wkbGeometryCollection)
    4241             :         {
    4242             :             const OGRGeometryCollection *poGC =
    4243         384 :                 poIntersection->toGeometryCollection();
    4244         768 :             std::set<std::pair<int, int>> oSetUniqueCoords;
    4245         384 :             poGPBFeature->addGeometry(
    4246             :                 GetCmdCountCombined(knCMD_MOVETO, 0));  // To be modified later
    4247         384 :             int nLastX = 0;
    4248         384 :             int nLastY = 0;
    4249         774 :             for (auto &&poSubGeom : poGC)
    4250             :             {
    4251         390 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPoint)
    4252             :                 {
    4253         390 :                     const OGRPoint *poPoint = poSubGeom->toPoint();
    4254             :                     int nX, nY;
    4255         390 :                     double dfX = poPoint->getX();
    4256         390 :                     double dfY = poPoint->getY();
    4257         390 :                     ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY,
    4258             :                                         dfTileDim);
    4259         390 :                     if (oSetUniqueCoords.find(std::pair<int, int>(nX, nY)) ==
    4260         780 :                         oSetUniqueCoords.end())
    4261             :                     {
    4262         390 :                         oSetUniqueCoords.insert(std::pair<int, int>(nX, nY));
    4263             : 
    4264         390 :                         int nDiffX = nX - nLastX;
    4265         390 :                         int nDiffY = nY - nLastY;
    4266         390 :                         poGPBFeature->addGeometry(EncodeSInt(nDiffX));
    4267         390 :                         poGPBFeature->addGeometry(EncodeSInt(nDiffY));
    4268         390 :                         nLastX = nX;
    4269         390 :                         nLastY = nY;
    4270             :                     }
    4271             :                 }
    4272             :             }
    4273         384 :             GUInt32 nPoints = static_cast<GUInt32>(oSetUniqueCoords.size());
    4274         384 :             bGeomOK = nPoints > 0;
    4275         384 :             poGPBFeature->setGeometry(
    4276             :                 0, GetCmdCountCombined(knCMD_MOVETO, nPoints));
    4277        1388 :         }
    4278             :     }
    4279        2662 :     else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
    4280             :     {
    4281        1323 :         const bool bWriteLastPoint = true;
    4282        1323 :         const GUInt32 nMinLineTo = 1;
    4283             : 
    4284        1323 :         if (eGeomToEncodeType == wkbLineString)
    4285             :         {
    4286         936 :             const OGRLineString *poLS = poGeomToEncode->toLineString();
    4287         936 :             int nLastX = 0;
    4288         936 :             int nLastY = 0;
    4289         936 :             OGRLineString oOutLS;
    4290         936 :             bGeomOK = EncodeLineString(
    4291             :                 poGPBFeature.get(), poLS, &oOutLS, bWriteLastPoint,
    4292             :                 ExpectedWindingOrder::NONE, nMinLineTo, dfTopX, dfTopY,
    4293             :                 dfTileDim, nLastX, nLastY);
    4294         936 :             dfAreaOrLength = oOutLS.get_Length();
    4295             :         }
    4296         387 :         else if (eGeomToEncodeType == wkbMultiLineString ||
    4297             :                  eGeomToEncodeType == wkbGeometryCollection)
    4298             :         {
    4299             :             const OGRGeometryCollection *poGC =
    4300         387 :                 poGeomToEncode->toGeometryCollection();
    4301         387 :             int nLastX = 0;
    4302         387 :             int nLastY = 0;
    4303         783 :             for (auto &&poSubGeom : poGC)
    4304             :             {
    4305         396 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbLineString)
    4306             :                 {
    4307         396 :                     const OGRLineString *poLS = poSubGeom->toLineString();
    4308         396 :                     OGRLineString oOutLS;
    4309         396 :                     bool bSubGeomOK = EncodeLineString(
    4310             :                         poGPBFeature.get(), poLS, &oOutLS, bWriteLastPoint,
    4311             :                         ExpectedWindingOrder::NONE, nMinLineTo, dfTopX, dfTopY,
    4312             :                         dfTileDim, nLastX, nLastY);
    4313         396 :                     if (bSubGeomOK)
    4314          18 :                         dfAreaOrLength += oOutLS.get_Length();
    4315         396 :                     bGeomOK |= bSubGeomOK;
    4316             :                 }
    4317             :             }
    4318        1323 :         }
    4319             :     }
    4320        1339 :     else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
    4321             :     {
    4322        1339 :         if (eGeomToEncodeType == wkbPolygon)
    4323             :         {
    4324         955 :             const OGRPolygon *poPoly = poGeomToEncode->toPolygon();
    4325         955 :             int nLastX = 0;
    4326         955 :             int nLastY = 0;
    4327        1910 :             OGRPolygon oOutPoly;
    4328         955 :             const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    4329         955 :             CPL_IGNORE_RET_VAL(nInitialSize);
    4330         955 :             bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, &oOutPoly,
    4331             :                                     dfTopX, dfTopY, dfTileDim, nLastX, nLastY,
    4332             :                                     dfAreaOrLength);
    4333             :             int bIsValid;
    4334             :             {
    4335         955 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4336         955 :                 bIsValid = oOutPoly.IsValid();
    4337             :             }
    4338         955 :             if (!bIsValid)
    4339             :             {
    4340             :                 // Build a valid geometry from the initial MVT geometry and emit
    4341             :                 // it
    4342         110 :                 std::unique_ptr<OGRGeometry> poPolyValid(oOutPoly.MakeValid());
    4343          55 :                 if (poPolyValid)
    4344             :                 {
    4345          55 :                     poGPBFeature->resizeGeometryArray(nInitialSize);
    4346          55 :                     EmitValidPolygon(poPolyValid.get());
    4347             :                 }
    4348             :             }
    4349             :         }
    4350         384 :         else if (eGeomToEncodeType == wkbMultiPolygon ||
    4351             :                  eGeomToEncodeType == wkbGeometryCollection)
    4352             :         {
    4353             :             const OGRGeometryCollection *poGC =
    4354         384 :                 poGeomToEncode->toGeometryCollection();
    4355         384 :             int nLastX = 0;
    4356         384 :             int nLastY = 0;
    4357         768 :             OGRMultiPolygon oOutMP;
    4358         384 :             const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    4359         384 :             CPL_IGNORE_RET_VAL(nInitialSize);
    4360         785 :             for (auto &&poSubGeom : poGC)
    4361             :             {
    4362         401 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
    4363             :                 {
    4364         401 :                     const OGRPolygon *poPoly = poSubGeom->toPolygon();
    4365         401 :                     double dfPartArea = 0.0;
    4366         802 :                     auto poOutPoly = std::make_unique<OGRPolygon>();
    4367         401 :                     bGeomOK |= EncodePolygon(
    4368             :                         poGPBFeature.get(), poPoly, poOutPoly.get(), dfTopX,
    4369         401 :                         dfTopY, dfTileDim, nLastX, nLastY, dfPartArea);
    4370         401 :                     dfAreaOrLength += dfPartArea;
    4371         401 :                     oOutMP.addGeometryDirectly(poOutPoly.release());
    4372             :                 }
    4373             :             }
    4374             :             int bIsValid;
    4375             :             {
    4376         384 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4377         384 :                 bIsValid = oOutMP.IsValid();
    4378             :             }
    4379         384 :             if (!bIsValid)
    4380             :             {
    4381             :                 // Build a valid geometry from the initial MVT geometry and emit
    4382             :                 // it
    4383           6 :                 std::unique_ptr<OGRGeometry> poMPValid(oOutMP.MakeValid());
    4384           3 :                 if (poMPValid)
    4385             :                 {
    4386           3 :                     poGPBFeature->resizeGeometryArray(nInitialSize);
    4387           3 :                     EmitValidPolygon(poMPValid.get());
    4388             :                 }
    4389             :             }
    4390             :         }
    4391             :     }
    4392        4050 :     if (!bGeomOK)
    4393        2491 :         return OGRERR_NONE;
    4394             : 
    4395        5912 :     for (const auto &pair : poFeatureContent->oValues)
    4396             :     {
    4397        4353 :         GUInt32 nKey = poLayer->addKey(pair.first);
    4398        4353 :         GUInt32 nVal = poLayer->addValue(pair.second);
    4399        4353 :         poGPBFeature->addTag(nKey);
    4400        4353 :         poGPBFeature->addTag(nVal);
    4401             :     }
    4402        1559 :     if (poFeatureContent->nFID >= 0)
    4403             :     {
    4404          53 :         poGPBFeature->setId(poFeatureContent->nFID);
    4405             :     }
    4406             : 
    4407             : #ifdef notdef
    4408             :     {
    4409             :         MVTTile oTile;
    4410             :         poLayer->setName("x");
    4411             :         oTile.addLayer(poLayer);
    4412             : 
    4413             :         CPLString oBuffer(oTile.write());
    4414             : 
    4415             :         VSILFILE *fp = VSIFOpenL(
    4416             :             CPLSPrintf("/tmp/%d-%d-%d.pbf", nZ, nTileX, nTileY), "wb");
    4417             :         VSIFWriteL(oBuffer.data(), 1, oBuffer.size(), fp);
    4418             :         VSIFCloseL(fp);
    4419             :     }
    4420             : #endif
    4421             : 
    4422             :     // GPB encode the layer with our single feature
    4423        3118 :     CPLString oBuffer(poLayer->write());
    4424             : 
    4425             :     // Compress buffer
    4426        1559 :     size_t nCompressedSize = 0;
    4427        1559 :     void *pCompressed = CPLZLibDeflate(oBuffer.data(), oBuffer.size(), -1,
    4428             :                                        nullptr, 0, &nCompressedSize);
    4429        1559 :     oBuffer.assign(static_cast<char *>(pCompressed), nCompressedSize);
    4430        1559 :     CPLFree(pCompressed);
    4431             : 
    4432        1559 :     const auto InsertIntoDb = [&]()
    4433             :     {
    4434       15590 :         m_nTempTiles++;
    4435        1559 :         sqlite3_bind_int(m_hInsertStmt, 1, nZ);
    4436        1559 :         sqlite3_bind_int(m_hInsertStmt, 2, nTileX);
    4437        1559 :         sqlite3_bind_int(m_hInsertStmt, 3, nTileY);
    4438        1559 :         sqlite3_bind_text(m_hInsertStmt, 4, osTargetName.c_str(), -1,
    4439             :                           SQLITE_STATIC);
    4440        1559 :         sqlite3_bind_int64(m_hInsertStmt, 5, nSerial);
    4441        1559 :         sqlite3_bind_blob(m_hInsertStmt, 6, oBuffer.data(),
    4442        1559 :                           static_cast<int>(oBuffer.size()), SQLITE_STATIC);
    4443        1559 :         sqlite3_bind_int(m_hInsertStmt, 7,
    4444        1559 :                          static_cast<int>(poGPBFeature->getType()));
    4445        1559 :         sqlite3_bind_double(m_hInsertStmt, 8, dfAreaOrLength);
    4446        1559 :         int rc = sqlite3_step(m_hInsertStmt);
    4447        1559 :         sqlite3_reset(m_hInsertStmt);
    4448        1559 :         return rc;
    4449        1559 :     };
    4450             : 
    4451             :     int rc;
    4452        1559 :     if (m_bThreadPoolOK)
    4453             :     {
    4454        1534 :         std::lock_guard<std::mutex> oLock(m_oDBMutex);
    4455        1534 :         rc = InsertIntoDb();
    4456             :     }
    4457             :     else
    4458             :     {
    4459          25 :         rc = InsertIntoDb();
    4460             :     }
    4461             : 
    4462        1559 :     if (!(rc == SQLITE_OK || rc == SQLITE_DONE))
    4463             :     {
    4464          22 :         return OGRERR_FAILURE;
    4465             :     }
    4466             : 
    4467        1537 :     return OGRERR_NONE;
    4468             : }
    4469             : 
    4470             : /************************************************************************/
    4471             : /*                           MVTWriterTask()                            */
    4472             : /************************************************************************/
    4473             : 
    4474             : class MVTWriterTask
    4475             : {
    4476             :   public:
    4477             :     const OGRMVTWriterDataset *poDS;
    4478             :     int nZ;
    4479             :     int nTileX;
    4480             :     int nTileY;
    4481             :     CPLString osTargetName;
    4482             :     bool bIsMaxZoomForLayer;
    4483             :     std::shared_ptr<OGRMVTFeatureContent> poFeatureContent;
    4484             :     GIntBig nSerial;
    4485             :     std::shared_ptr<OGRGeometry> poGeom;
    4486             :     OGREnvelope sEnvelope;
    4487             : };
    4488             : 
    4489             : /************************************************************************/
    4490             : /*                           WriterTaskFunc()                           */
    4491             : /************************************************************************/
    4492             : 
    4493        4028 : void OGRMVTWriterDataset::WriterTaskFunc(void *pParam)
    4494             : {
    4495        4028 :     MVTWriterTask *poTask = static_cast<MVTWriterTask *>(pParam);
    4496       16112 :     OGRErr eErr = poTask->poDS->PreGenerateForTileReal(
    4497        4028 :         poTask->nZ, poTask->nTileX, poTask->nTileY, poTask->osTargetName,
    4498        4028 :         poTask->bIsMaxZoomForLayer, poTask->poFeatureContent.get(),
    4499        4028 :         poTask->nSerial, poTask->poGeom.get(), poTask->sEnvelope);
    4500        4028 :     if (eErr != OGRERR_NONE)
    4501             :     {
    4502          21 :         std::lock_guard oLock(poTask->poDS->m_oDBMutex);
    4503          21 :         poTask->poDS->m_bWriteFeatureError = true;
    4504             :     }
    4505        4028 :     delete poTask;
    4506        4028 : }
    4507             : 
    4508             : /************************************************************************/
    4509             : /*                         PreGenerateForTile()                         */
    4510             : /************************************************************************/
    4511             : 
    4512        4053 : OGRErr OGRMVTWriterDataset::PreGenerateForTile(
    4513             :     int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
    4514             :     bool bIsMaxZoomForLayer,
    4515             :     const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
    4516             :     GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
    4517             :     const OGREnvelope &sEnvelope) const
    4518             : {
    4519        4053 :     if (!m_bThreadPoolOK)
    4520             :     {
    4521          25 :         return PreGenerateForTileReal(
    4522             :             nZ, nTileX, nTileY, osTargetName, bIsMaxZoomForLayer,
    4523          50 :             poFeatureContent.get(), nSerial, poGeom.get(), sEnvelope);
    4524             :     }
    4525             :     else
    4526             :     {
    4527        4028 :         MVTWriterTask *poTask = new MVTWriterTask;
    4528        4028 :         poTask->poDS = this;
    4529        4028 :         poTask->nZ = nZ;
    4530        4028 :         poTask->nTileX = nTileX;
    4531        4028 :         poTask->nTileY = nTileY;
    4532        4028 :         poTask->osTargetName = osTargetName;
    4533        4028 :         poTask->bIsMaxZoomForLayer = bIsMaxZoomForLayer;
    4534        4028 :         poTask->poFeatureContent = poFeatureContent;
    4535        4028 :         poTask->nSerial = nSerial;
    4536        4028 :         poTask->poGeom = poGeom;
    4537        4028 :         poTask->sEnvelope = sEnvelope;
    4538        4028 :         m_oThreadPool.SubmitJob(OGRMVTWriterDataset::WriterTaskFunc, poTask);
    4539             :         // Do not queue more than 1000 jobs to avoid memory exhaustion
    4540        4028 :         m_oThreadPool.WaitCompletion(1000);
    4541             : 
    4542        4028 :         std::lock_guard oLock(m_oDBMutex);
    4543        4028 :         return m_bWriteFeatureError ? OGRERR_FAILURE : OGRERR_NONE;
    4544             :     }
    4545             : }
    4546             : 
    4547             : /************************************************************************/
    4548             : /*                       UpdateLayerProperties()                        */
    4549             : /************************************************************************/
    4550             : 
    4551        4353 : void OGRMVTWriterDataset::UpdateLayerProperties(
    4552             :     MVTLayerProperties *poLayerProperties, const std::string &osKey,
    4553             :     const MVTTileLayerValue &oValue)
    4554             : {
    4555        4353 :     auto oFieldIter = poLayerProperties->m_oMapFieldNameToIdx.find(osKey);
    4556        4353 :     MVTFieldProperties *poFieldProps = nullptr;
    4557        4353 :     if (oFieldIter == poLayerProperties->m_oMapFieldNameToIdx.end())
    4558             :     {
    4559         181 :         if (poLayerProperties->m_oSetFields.size() < knMAX_COUNT_FIELDS)
    4560             :         {
    4561         181 :             poLayerProperties->m_oSetFields.insert(osKey);
    4562         181 :             if (poLayerProperties->m_oMapFieldNameToIdx.size() <
    4563             :                 knMAX_REPORT_FIELDS)
    4564             :             {
    4565         362 :                 MVTFieldProperties oFieldProps;
    4566         181 :                 oFieldProps.m_osName = osKey;
    4567         181 :                 if (oValue.isNumeric())
    4568             :                 {
    4569          73 :                     oFieldProps.m_dfMinVal = oValue.getNumericValue();
    4570          73 :                     oFieldProps.m_dfMaxVal = oValue.getNumericValue();
    4571          73 :                     oFieldProps.m_bAllInt = true;  // overridden just below
    4572             :                 }
    4573         181 :                 oFieldProps.m_eType =
    4574         289 :                     oValue.isNumeric()  ? MVTTileLayerValue::ValueType::DOUBLE
    4575         108 :                     : oValue.isString() ? MVTTileLayerValue::ValueType::STRING
    4576             :                                         : MVTTileLayerValue::ValueType::BOOL;
    4577             : 
    4578         181 :                 poLayerProperties->m_oMapFieldNameToIdx[osKey] =
    4579         181 :                     poLayerProperties->m_aoFields.size();
    4580         181 :                 poLayerProperties->m_aoFields.push_back(std::move(oFieldProps));
    4581         181 :                 poFieldProps = &(poLayerProperties->m_aoFields.back());
    4582             :             }
    4583             :         }
    4584             :     }
    4585             :     else
    4586             :     {
    4587        4172 :         poFieldProps = &(poLayerProperties->m_aoFields[oFieldIter->second]);
    4588             :     }
    4589             : 
    4590        4353 :     if (poFieldProps)
    4591             :     {
    4592        4353 :         if (oValue.getType() == MVTTileLayerValue::ValueType::BOOL)
    4593             :         {
    4594          24 :             MVTTileLayerValue oUniqVal;
    4595          12 :             oUniqVal.setBoolValue(oValue.getBoolValue());
    4596          12 :             poFieldProps->m_oSetAllValues.insert(oUniqVal);
    4597          12 :             poFieldProps->m_oSetValues.insert(oUniqVal);
    4598             :         }
    4599        4341 :         else if (oValue.isNumeric())
    4600             :         {
    4601        1780 :             if (poFieldProps->m_bAllInt)
    4602             :             {
    4603         935 :                 poFieldProps->m_bAllInt =
    4604        1870 :                     oValue.getType() == MVTTileLayerValue::ValueType::INT ||
    4605        2751 :                     oValue.getType() == MVTTileLayerValue::ValueType::SINT ||
    4606        1798 :                     (oValue.getType() == MVTTileLayerValue::ValueType::UINT &&
    4607         881 :                      oValue.getUIntValue() < GINT64_MAX);
    4608             :             }
    4609        1780 :             double dfVal = oValue.getNumericValue();
    4610        1780 :             poFieldProps->m_dfMinVal =
    4611        1780 :                 std::min(poFieldProps->m_dfMinVal, dfVal);
    4612        1780 :             poFieldProps->m_dfMaxVal =
    4613        1780 :                 std::max(poFieldProps->m_dfMaxVal, dfVal);
    4614        1780 :             if (poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
    4615             :             {
    4616        3560 :                 MVTTileLayerValue oUniqVal;
    4617        1780 :                 oUniqVal.setDoubleValue(dfVal);
    4618        1780 :                 poFieldProps->m_oSetAllValues.insert(oUniqVal);
    4619        1780 :                 if (poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
    4620             :                 {
    4621        1780 :                     poFieldProps->m_oSetValues.insert(oUniqVal);
    4622             :                 }
    4623             :             }
    4624             :         }
    4625        5122 :         else if (oValue.isString() &&
    4626        2561 :                  poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
    4627             :         {
    4628        5122 :             auto osVal = oValue.getStringValue();
    4629        5122 :             MVTTileLayerValue oUniqVal;
    4630        2561 :             oUniqVal.setStringValue(osVal);
    4631        2561 :             poFieldProps->m_oSetAllValues.insert(oUniqVal);
    4632        5122 :             if (osVal.size() <= knMAX_STRING_VALUE_LENGTH &&
    4633        2561 :                 poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
    4634             :             {
    4635        2561 :                 poFieldProps->m_oSetValues.insert(oUniqVal);
    4636             :             }
    4637             :         }
    4638             :     }
    4639        4353 : }
    4640             : 
    4641             : /************************************************************************/
    4642             : /*                            GZIPCompress()                            */
    4643             : /************************************************************************/
    4644             : 
    4645         977 : static void GZIPCompress(std::string &oTileBuffer)
    4646             : {
    4647         977 :     if (!oTileBuffer.empty())
    4648             :     {
    4649             :         const CPLString osTmpFilename(
    4650        1954 :             VSIMemGenerateHiddenFilename("mvt_temp.gz"));
    4651        1954 :         CPLString osTmpGZipFilename("/vsigzip/" + osTmpFilename);
    4652         977 :         VSILFILE *fpGZip = VSIFOpenL(osTmpGZipFilename, "wb");
    4653         977 :         if (fpGZip)
    4654             :         {
    4655         977 :             VSIFWriteL(oTileBuffer.data(), 1, oTileBuffer.size(), fpGZip);
    4656         977 :             VSIFCloseL(fpGZip);
    4657             : 
    4658         977 :             vsi_l_offset nCompressedSize = 0;
    4659             :             GByte *pabyCompressed =
    4660         977 :                 VSIGetMemFileBuffer(osTmpFilename, &nCompressedSize, false);
    4661             :             oTileBuffer.assign(reinterpret_cast<char *>(pabyCompressed),
    4662         977 :                                static_cast<size_t>(nCompressedSize));
    4663             :         }
    4664         977 :         VSIUnlink(osTmpFilename);
    4665             :     }
    4666         977 : }
    4667             : 
    4668             : /************************************************************************/
    4669             : /*                    GetReducedPrecisionGeometry()                     */
    4670             : /************************************************************************/
    4671             : 
    4672             : static std::vector<GUInt32>
    4673         167 : GetReducedPrecisionGeometry(MVTTileLayerFeature::GeomType eGeomType,
    4674             :                             const std::vector<GUInt32> &anSrcGeometry,
    4675             :                             GUInt32 nSrcExtent, GUInt32 nDstExtent)
    4676             : {
    4677         167 :     std::vector<GUInt32> anDstGeometry;
    4678         167 :     size_t nLastMoveToIdx = 0;
    4679         167 :     int nX = 0;
    4680         167 :     int nY = 0;
    4681         167 :     int nFirstReducedX = 0;
    4682         167 :     int nFirstReducedY = 0;
    4683         167 :     int nLastReducedX = 0;
    4684         167 :     int nLastReducedY = 0;
    4685         167 :     int nLastReducedXValid = 0;
    4686         167 :     int nLastReducedYValid = 0;
    4687         167 :     std::unique_ptr<OGRLinearRing> poInRing;
    4688         167 :     std::unique_ptr<OGRLinearRing> poOutRing;
    4689         167 :     std::unique_ptr<OGRLinearRing> poOutOuterRing;
    4690         167 :     bool bDiscardInnerRings = false;
    4691         167 :     const bool bIsPoly = eGeomType == MVTTileLayerFeature::GeomType::POLYGON;
    4692         506 :     for (size_t iSrc = 0; iSrc < anSrcGeometry.size();)
    4693             :     {
    4694         339 :         const unsigned nCount = GetCmdCount(anSrcGeometry[iSrc]);
    4695         339 :         switch (GetCmdId(anSrcGeometry[iSrc]))
    4696             :         {
    4697         185 :             case knCMD_MOVETO:
    4698             :             {
    4699         185 :                 nLastMoveToIdx = anDstGeometry.size();
    4700             : 
    4701         185 :                 anDstGeometry.push_back(anSrcGeometry[iSrc]);
    4702         185 :                 iSrc++;
    4703             : 
    4704         185 :                 unsigned nDstPoints = 0;
    4705         185 :                 for (unsigned j = 0;
    4706         370 :                      iSrc + 1 < anSrcGeometry.size() && j < nCount;
    4707         185 :                      j++, iSrc += 2)
    4708             :                 {
    4709         185 :                     nX += DecodeSInt(anSrcGeometry[iSrc]);
    4710         185 :                     nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
    4711             : 
    4712         185 :                     int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
    4713         185 :                                                      nDstExtent / nSrcExtent);
    4714         185 :                     int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
    4715         185 :                                                      nDstExtent / nSrcExtent);
    4716         185 :                     int nDiffX = nReducedX - nLastReducedX;
    4717         185 :                     int nDiffY = nReducedY - nLastReducedY;
    4718         185 :                     if (j == 0)
    4719             :                     {
    4720         185 :                         if (bIsPoly)
    4721             :                         {
    4722          82 :                             poInRing = std::unique_ptr<OGRLinearRing>(
    4723          82 :                                 new OGRLinearRing());
    4724          82 :                             poOutRing = std::unique_ptr<OGRLinearRing>(
    4725          82 :                                 new OGRLinearRing());
    4726             :                         }
    4727         185 :                         nFirstReducedX = nReducedX;
    4728         185 :                         nFirstReducedY = nReducedY;
    4729             :                     }
    4730         185 :                     if (j == 0 || nDiffX != 0 || nDiffY != 0)
    4731             :                     {
    4732         185 :                         if (bIsPoly)
    4733             :                         {
    4734          41 :                             poInRing->addPoint(nX, nY);
    4735          41 :                             poOutRing->addPoint(nReducedX, nReducedY);
    4736             :                         }
    4737         185 :                         nDstPoints++;
    4738         185 :                         anDstGeometry.push_back(EncodeSInt(nDiffX));
    4739         185 :                         anDstGeometry.push_back(EncodeSInt(nDiffY));
    4740         185 :                         nLastReducedX = nReducedX;
    4741         185 :                         nLastReducedY = nReducedY;
    4742             :                     }
    4743             :                 }
    4744             :                 // Patch count of MOVETO
    4745         185 :                 anDstGeometry[nLastMoveToIdx] = GetCmdCountCombined(
    4746         185 :                     GetCmdId(anDstGeometry[nLastMoveToIdx]), nDstPoints);
    4747         185 :                 break;
    4748             :             }
    4749         113 :             case knCMD_LINETO:
    4750             :             {
    4751         113 :                 size_t nIdxToPatch = anDstGeometry.size();
    4752         113 :                 anDstGeometry.push_back(anSrcGeometry[iSrc]);
    4753         113 :                 iSrc++;
    4754         113 :                 unsigned nDstPoints = 0;
    4755         113 :                 int nLastReducedXBefore = nLastReducedX;
    4756         113 :                 int nLastReducedYBefore = nLastReducedY;
    4757         113 :                 for (unsigned j = 0;
    4758         267 :                      iSrc + 1 < anSrcGeometry.size() && j < nCount;
    4759         154 :                      j++, iSrc += 2)
    4760             :                 {
    4761         154 :                     nX += DecodeSInt(anSrcGeometry[iSrc]);
    4762         154 :                     nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
    4763             : 
    4764         154 :                     int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
    4765         154 :                                                      nDstExtent / nSrcExtent);
    4766         154 :                     int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
    4767         154 :                                                      nDstExtent / nSrcExtent);
    4768         154 :                     int nDiffX = nReducedX - nLastReducedX;
    4769         154 :                     int nDiffY = nReducedY - nLastReducedY;
    4770         154 :                     if (nDiffX != 0 || nDiffY != 0)
    4771             :                     {
    4772         114 :                         if (bIsPoly)
    4773             :                         {
    4774          60 :                             CPLAssert(poInRing);
    4775          60 :                             CPLAssert(poOutRing);
    4776          60 :                             poInRing->addPoint(nX, nY);
    4777          60 :                             poOutRing->addPoint(nReducedX, nReducedY);
    4778             :                         }
    4779         114 :                         nDstPoints++;
    4780         114 :                         anDstGeometry.push_back(EncodeSInt(nDiffX));
    4781         114 :                         anDstGeometry.push_back(EncodeSInt(nDiffY));
    4782         114 :                         nLastReducedXBefore = nLastReducedX;
    4783         114 :                         nLastReducedYBefore = nLastReducedY;
    4784         114 :                         nLastReducedX = nReducedX;
    4785         114 :                         nLastReducedY = nReducedY;
    4786             :                     }
    4787             :                 }
    4788             : 
    4789             :                 // If last point of ring is identical to first one, discard it
    4790         113 :                 if (nDstPoints > 0 && bIsPoly &&
    4791           1 :                     nLastReducedX == nFirstReducedX &&
    4792             :                     nLastReducedY == nFirstReducedY)
    4793             :                 {
    4794           0 :                     nLastReducedX = nLastReducedXBefore;
    4795           0 :                     nLastReducedY = nLastReducedYBefore;
    4796           0 :                     nDstPoints -= 1;
    4797           0 :                     anDstGeometry.resize(anDstGeometry.size() - 2);
    4798           0 :                     poOutRing->setNumPoints(poOutRing->getNumPoints() - 1);
    4799             :                 }
    4800             : 
    4801             :                 // Patch count of LINETO
    4802         113 :                 anDstGeometry[nIdxToPatch] = GetCmdCountCombined(
    4803         113 :                     GetCmdId(anDstGeometry[nIdxToPatch]), nDstPoints);
    4804             : 
    4805             :                 // A valid linestring should have at least one MOVETO +
    4806             :                 // one coord pair + one LINETO + one coord pair
    4807         113 :                 if (eGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
    4808             :                 {
    4809          72 :                     if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2)
    4810             :                     {
    4811             :                         // Remove last linestring
    4812          18 :                         nLastReducedX = nLastReducedXValid;
    4813          18 :                         nLastReducedY = nLastReducedYValid;
    4814          18 :                         anDstGeometry.resize(nLastMoveToIdx);
    4815             :                     }
    4816             :                     else
    4817             :                     {
    4818          54 :                         nLastReducedXValid = nLastReducedX;
    4819          54 :                         nLastReducedYValid = nLastReducedY;
    4820             :                     }
    4821             :                 }
    4822             : 
    4823         113 :                 break;
    4824             :             }
    4825          41 :             case knCMD_CLOSEPATH:
    4826             :             {
    4827          41 :                 CPLAssert(bIsPoly);
    4828          41 :                 CPLAssert(poInRing);
    4829          41 :                 CPLAssert(poOutRing);
    4830          41 :                 int bIsValid = true;
    4831             : 
    4832             :                 // A valid ring should have at least one MOVETO + one
    4833             :                 // coord pair + one LINETO + two coord pairs
    4834          41 :                 if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2 * 2)
    4835             :                 {
    4836             :                     // Remove ring. Normally if we remove an outer ring,
    4837             :                     // its inner rings should also be removed, given they are
    4838             :                     // smaller than the outer ring.
    4839          14 :                     bIsValid = false;
    4840             :                 }
    4841             :                 else
    4842             :                 {
    4843          27 :                     poInRing->closeRings();
    4844          27 :                     poOutRing->closeRings();
    4845          27 :                     bool bIsOuterRing = !poInRing->isClockwise();
    4846             :                     // Normally the first ring of a polygon geometry should
    4847             :                     // be a outer ring, except when it is degenerate enough
    4848             :                     // in which case poOutOuterRing might be null.
    4849          27 :                     if (bIsOuterRing)
    4850             :                     {
    4851             :                         // if the outer ring turned out to be a inner ring
    4852             :                         // once reduced
    4853          18 :                         if (poOutRing->isClockwise())
    4854             :                         {
    4855           0 :                             bIsValid = false;
    4856           0 :                             bDiscardInnerRings = true;
    4857             :                         }
    4858             :                         else
    4859             :                         {
    4860          18 :                             OGRPolygon oPoly;
    4861          18 :                             oPoly.addRing(poOutRing.get());
    4862          36 :                             poOutOuterRing = std::unique_ptr<OGRLinearRing>(
    4863          18 :                                 poOutRing.release());
    4864             :                             {
    4865             :                                 CPLErrorStateBackuper oErrorStateBackuper(
    4866          18 :                                     CPLQuietErrorHandler);
    4867          18 :                                 bIsValid = oPoly.IsValid();
    4868             :                             }
    4869          18 :                             bDiscardInnerRings = !bIsValid;
    4870             :                         }
    4871             :                     }
    4872           9 :                     else if (bDiscardInnerRings ||
    4873          18 :                              poOutOuterRing.get() == nullptr ||
    4874             :                              // if the inner ring turned out to be a outer ring
    4875             :                              // once reduced
    4876           9 :                              !poOutRing->isClockwise())
    4877             :                     {
    4878           0 :                         bIsValid = false;
    4879             :                     }
    4880             :                     else
    4881             :                     {
    4882          18 :                         OGRPolygon oPoly;
    4883           9 :                         oPoly.addRing(poOutOuterRing.get());
    4884           9 :                         oPoly.addRingDirectly(poOutRing.release());
    4885             :                         {
    4886             :                             CPLErrorStateBackuper oErrorStateBackuper(
    4887           9 :                                 CPLQuietErrorHandler);
    4888           9 :                             bIsValid = oPoly.IsValid();
    4889             :                         }
    4890             :                     }
    4891             :                 }
    4892             : 
    4893          41 :                 if (bIsValid)
    4894             :                 {
    4895          24 :                     nLastReducedXValid = nLastReducedX;
    4896          24 :                     nLastReducedYValid = nLastReducedY;
    4897          24 :                     anDstGeometry.push_back(anSrcGeometry[iSrc]);
    4898             :                 }
    4899             :                 else
    4900             :                 {
    4901             :                     // Remove this ring
    4902          17 :                     nLastReducedX = nLastReducedXValid;
    4903          17 :                     nLastReducedY = nLastReducedYValid;
    4904          17 :                     anDstGeometry.resize(nLastMoveToIdx);
    4905             :                 }
    4906             : 
    4907          41 :                 iSrc++;
    4908          41 :                 break;
    4909             :             }
    4910           0 :             default:
    4911             :             {
    4912           0 :                 CPLAssert(false);
    4913             :                 break;
    4914             :             }
    4915             :         }
    4916             :     }
    4917             : 
    4918         334 :     return anDstGeometry;
    4919             : }
    4920             : 
    4921             : /************************************************************************/
    4922             : /*                           EncodeFeature()                            */
    4923             : /************************************************************************/
    4924             : 
    4925        1705 : void OGRMVTWriterDataset::EncodeFeature(
    4926             :     const void *pabyBlob, int nBlobSize,
    4927             :     std::shared_ptr<MVTTileLayer> &poTargetLayer,
    4928             :     std::map<CPLString, GUInt32> &oMapKeyToIdx,
    4929             :     std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
    4930             :     MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
    4931             :     unsigned &nFeaturesInTile)
    4932             : {
    4933        1705 :     size_t nUncompressedSize = 0;
    4934             :     void *pCompressed =
    4935        1705 :         CPLZLibInflate(pabyBlob, nBlobSize, nullptr, 0, &nUncompressedSize);
    4936        1705 :     GByte *pabyUncompressed = static_cast<GByte *>(pCompressed);
    4937             : 
    4938        3410 :     MVTTileLayer oSrcTileLayer;
    4939        1705 :     if (nUncompressedSize &&
    4940        1705 :         oSrcTileLayer.read(pabyUncompressed,
    4941        1705 :                            pabyUncompressed + nUncompressedSize))
    4942             :     {
    4943        1705 :         const auto &srcFeatures = oSrcTileLayer.getFeatures();
    4944        1705 :         if (srcFeatures.size() == 1)  // should always be true !
    4945             :         {
    4946        1705 :             const auto &poSrcFeature = srcFeatures[0];
    4947             :             std::shared_ptr<MVTTileLayerFeature> poFeature(
    4948        3410 :                 new MVTTileLayerFeature());
    4949             : 
    4950        1705 :             if (poSrcFeature->hasId())
    4951          53 :                 poFeature->setId(poSrcFeature->getId());
    4952        1705 :             poFeature->setType(poSrcFeature->getType());
    4953        1705 :             if (poLayerProperties)
    4954             :             {
    4955        1526 :                 poLayerProperties->m_oCountGeomType[poSrcFeature->getType()]++;
    4956             :             }
    4957        1705 :             bool bOK = true;
    4958        1705 :             if (nExtent < m_nExtent)
    4959             :             {
    4960             : #ifdef for_debugging
    4961             :                 const auto &srcKeys = oSrcTileLayer.getKeys();
    4962             :                 const auto &srcValues = oSrcTileLayer.getValues();
    4963             :                 const auto &anSrcTags = poSrcFeature->getTags();
    4964             :                 for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
    4965             :                 {
    4966             :                     GUInt32 nSrcIdxKey = anSrcTags[i];
    4967             :                     GUInt32 nSrcIdxValue = anSrcTags[i + 1];
    4968             :                     if (nSrcIdxKey < srcKeys.size() &&
    4969             :                         nSrcIdxValue < srcValues.size())
    4970             :                     {
    4971             :                         auto &osKey = srcKeys[nSrcIdxKey];
    4972             :                         auto &oValue = srcValues[nSrcIdxValue];
    4973             :                         if (osKey == "tunnus" &&
    4974             :                             oValue.getUIntValue() == 28799760)
    4975             :                         {
    4976             :                             printf("foo\n"); /* ok */
    4977             :                             break;
    4978             :                         }
    4979             :                     }
    4980             :                 }
    4981             : #endif
    4982             : 
    4983         167 :                 poFeature->setGeometry(GetReducedPrecisionGeometry(
    4984             :                     poSrcFeature->getType(), poSrcFeature->getGeometry(),
    4985             :                     m_nExtent, nExtent));
    4986         167 :                 if (poFeature->getGeometry().empty())
    4987             :                 {
    4988          23 :                     bOK = false;
    4989             :                 }
    4990             :             }
    4991             :             else
    4992             :             {
    4993        1538 :                 poFeature->setGeometry(poSrcFeature->getGeometry());
    4994             :             }
    4995        1705 :             if (bOK)
    4996             :             {
    4997        1682 :                 const auto &srcKeys = oSrcTileLayer.getKeys();
    4998        6125 :                 for (const auto &osKey : srcKeys)
    4999             :                 {
    5000        4443 :                     auto oIter = oMapKeyToIdx.find(osKey);
    5001        4443 :                     if (oIter == oMapKeyToIdx.end())
    5002             :                     {
    5003        3645 :                         oMapKeyToIdx[osKey] = poTargetLayer->addKey(osKey);
    5004             :                     }
    5005             :                 }
    5006             : 
    5007        1682 :                 const auto &srcValues = oSrcTileLayer.getValues();
    5008        6125 :                 for (const auto &oValue : srcValues)
    5009             :                 {
    5010        4443 :                     auto oIter = oMapValueToIdx.find(oValue);
    5011        4443 :                     if (oIter == oMapValueToIdx.end())
    5012             :                     {
    5013        3771 :                         oMapValueToIdx[oValue] =
    5014        3771 :                             poTargetLayer->addValue(oValue);
    5015             :                     }
    5016             :                 }
    5017             : 
    5018        1682 :                 const auto &anSrcTags = poSrcFeature->getTags();
    5019        6125 :                 for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
    5020             :                 {
    5021        4443 :                     GUInt32 nSrcIdxKey = anSrcTags[i];
    5022        4443 :                     GUInt32 nSrcIdxValue = anSrcTags[i + 1];
    5023        8886 :                     if (nSrcIdxKey < srcKeys.size() &&
    5024        4443 :                         nSrcIdxValue < srcValues.size())
    5025             :                     {
    5026        4443 :                         const auto &osKey = srcKeys[nSrcIdxKey];
    5027        4443 :                         const auto &oValue = srcValues[nSrcIdxValue];
    5028             : 
    5029        4443 :                         if (poLayerProperties)
    5030             :                         {
    5031        4353 :                             UpdateLayerProperties(poLayerProperties, osKey,
    5032             :                                                   oValue);
    5033             :                         }
    5034             : 
    5035        4443 :                         poFeature->addTag(oMapKeyToIdx[osKey]);
    5036        4443 :                         poFeature->addTag(oMapValueToIdx[oValue]);
    5037             :                     }
    5038             :                 }
    5039             : 
    5040        1682 :                 nFeaturesInTile++;
    5041        1682 :                 poTargetLayer->addFeature(std::move(poFeature));
    5042             :             }
    5043             :         }
    5044             :     }
    5045             :     else
    5046             :     {
    5047             :         // Shouldn't fail
    5048           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Deserialization failure");
    5049             :     }
    5050             : 
    5051        1705 :     CPLFree(pabyUncompressed);
    5052        1705 : }
    5053             : 
    5054             : /************************************************************************/
    5055             : /*                             EncodeTile()                             */
    5056             : /************************************************************************/
    5057             : 
    5058         870 : std::string OGRMVTWriterDataset::EncodeTile(
    5059             :     int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer, sqlite3_stmt *hStmtRows,
    5060             :     std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
    5061             :     std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead)
    5062             : {
    5063        1740 :     MVTTile oTargetTile;
    5064             : 
    5065         870 :     sqlite3_bind_int(hStmtLayer, 1, nZ);
    5066         870 :     sqlite3_bind_int(hStmtLayer, 2, nX);
    5067         870 :     sqlite3_bind_int(hStmtLayer, 3, nY);
    5068             : 
    5069         870 :     unsigned nFeaturesInTile = 0;
    5070             :     const GIntBig nProgressStep =
    5071         870 :         std::max(static_cast<GIntBig>(1), m_nTempTiles / 10);
    5072             : 
    5073        4350 :     while (nFeaturesInTile < m_nMaxFeatures &&
    5074        2169 :            sqlite3_step(hStmtLayer) == SQLITE_ROW)
    5075             :     {
    5076             :         const char *pszLayerName =
    5077        1311 :             reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
    5078        1311 :         sqlite3_bind_int(hStmtRows, 1, nZ);
    5079        1311 :         sqlite3_bind_int(hStmtRows, 2, nX);
    5080        1311 :         sqlite3_bind_int(hStmtRows, 3, nY);
    5081        1311 :         sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
    5082             : 
    5083        1311 :         auto oIterMapLayerProps = oMapLayerProps.find(pszLayerName);
    5084        1311 :         MVTLayerProperties *poLayerProperties = nullptr;
    5085        1311 :         if (oIterMapLayerProps == oMapLayerProps.end())
    5086             :         {
    5087          76 :             if (oSetLayers.size() < knMAX_COUNT_LAYERS)
    5088             :             {
    5089          76 :                 oSetLayers.insert(pszLayerName);
    5090          76 :                 if (oMapLayerProps.size() < knMAX_REPORT_LAYERS)
    5091             :                 {
    5092          76 :                     MVTLayerProperties props;
    5093          76 :                     props.m_nMinZoom = nZ;
    5094          76 :                     props.m_nMaxZoom = nZ;
    5095          76 :                     oMapLayerProps[pszLayerName] = std::move(props);
    5096          76 :                     poLayerProperties = &(oMapLayerProps[pszLayerName]);
    5097             :                 }
    5098             :             }
    5099             :         }
    5100             :         else
    5101             :         {
    5102        1235 :             poLayerProperties = &(oIterMapLayerProps->second);
    5103             :         }
    5104        1311 :         if (poLayerProperties)
    5105             :         {
    5106        1311 :             poLayerProperties->m_nMinZoom =
    5107        1311 :                 std::min(nZ, poLayerProperties->m_nMinZoom);
    5108        1311 :             poLayerProperties->m_nMaxZoom =
    5109        1311 :                 std::max(nZ, poLayerProperties->m_nMaxZoom);
    5110             :         }
    5111             : 
    5112        2622 :         auto poTargetLayer = std::make_shared<MVTTileLayer>();
    5113        1311 :         oTargetTile.addLayer(poTargetLayer);
    5114        1311 :         poTargetLayer->setName(pszLayerName);
    5115        1311 :         poTargetLayer->setVersion(m_nMVTVersion);
    5116        1311 :         poTargetLayer->setExtent(m_nExtent);
    5117             : 
    5118        2622 :         std::map<CPLString, GUInt32> oMapKeyToIdx;
    5119        2622 :         std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
    5120             : 
    5121        5662 :         while (nFeaturesInTile < m_nMaxFeatures &&
    5122        2825 :                sqlite3_step(hStmtRows) == SQLITE_ROW)
    5123             :         {
    5124        1526 :             int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
    5125        1526 :             const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
    5126             : 
    5127        1526 :             EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
    5128             :                           oMapValueToIdx, poLayerProperties, m_nExtent,
    5129             :                           nFeaturesInTile);
    5130             : 
    5131        1526 :             nTempTilesRead++;
    5132        1526 :             if (nTempTilesRead == m_nTempTiles ||
    5133        1474 :                 (nTempTilesRead % nProgressStep) == 0)
    5134             :             {
    5135         534 :                 const int nPct =
    5136         534 :                     static_cast<int>((100 * nTempTilesRead) / m_nTempTiles);
    5137         534 :                 CPLDebug("MVT", "%d%%...", nPct);
    5138             :             }
    5139             :         }
    5140        1311 :         sqlite3_reset(hStmtRows);
    5141             :     }
    5142             : 
    5143         870 :     sqlite3_reset(hStmtLayer);
    5144             : 
    5145        1740 :     std::string oTileBuffer(oTargetTile.write());
    5146         870 :     size_t nSizeBefore = oTileBuffer.size();
    5147         870 :     if (m_bGZip)
    5148         870 :         GZIPCompress(oTileBuffer);
    5149         870 :     const size_t nSizeAfter = oTileBuffer.size();
    5150         870 :     const double dfCompressionRatio =
    5151         870 :         static_cast<double>(nSizeAfter) / nSizeBefore;
    5152             : 
    5153         870 :     const bool bTooManyFeatures = nFeaturesInTile >= m_nMaxFeatures;
    5154         870 :     if (bTooManyFeatures && !m_bMaxFeaturesOptSpecified)
    5155             :     {
    5156           1 :         m_bMaxFeaturesOptSpecified = true;
    5157           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    5158             :                  "At least one tile exceeded the default maximum number of "
    5159             :                  "features per tile (%u) and was truncated to satisfy it.",
    5160             :                  m_nMaxFeatures);
    5161             :     }
    5162             : 
    5163             :     // If the tile size is above the allowed values or there are too many
    5164             :     // features, then sort by descending area / length until we get to the
    5165             :     // limit.
    5166         870 :     bool bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
    5167         870 :     if (bTooBigTile && !m_bMaxTileSizeOptSpecified)
    5168             :     {
    5169           1 :         m_bMaxTileSizeOptSpecified = true;
    5170           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    5171             :                  "At least one tile exceeded the default maximum tile size of "
    5172             :                  "%u bytes and was encoded at lower resolution",
    5173             :                  m_nMaxTileSize);
    5174             :     }
    5175             : 
    5176         870 :     GUInt32 nExtent = m_nExtent;
    5177         952 :     while (bTooBigTile && !bTooManyFeatures && nExtent >= 256)
    5178             :     {
    5179          82 :         nExtent /= 2;
    5180          82 :         nSizeBefore = oTileBuffer.size();
    5181         164 :         oTileBuffer = RecodeTileLowerResolution(nZ, nX, nY, nExtent, hStmtLayer,
    5182          82 :                                                 hStmtRows);
    5183          82 :         bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
    5184          82 :         CPLDebug("MVT",
    5185             :                  "Recoding tile %d/%d/%d with extent = %u. "
    5186             :                  "From %u to %u bytes",
    5187             :                  nZ, nX, nY, nExtent, static_cast<unsigned>(nSizeBefore),
    5188          82 :                  static_cast<unsigned>(oTileBuffer.size()));
    5189             :     }
    5190             : 
    5191         870 :     if (bTooBigTile || bTooManyFeatures)
    5192             :     {
    5193          25 :         if (bTooBigTile)
    5194             :         {
    5195          13 :             CPLDebug("MVT", "For tile %d/%d/%d, tile size is %u > %u", nZ, nX,
    5196          13 :                      nY, static_cast<unsigned>(oTileBuffer.size()),
    5197             :                      m_nMaxTileSize);
    5198             :         }
    5199          25 :         if (bTooManyFeatures)
    5200             :         {
    5201          12 :             CPLDebug("MVT",
    5202             :                      "For tile %d/%d/%d, feature count limit of %u is reached",
    5203             :                      nZ, nX, nY, m_nMaxFeatures);
    5204             :         }
    5205             : 
    5206          25 :         oTargetTile.clear();
    5207             : 
    5208             :         const unsigned nTotalFeaturesInTile =
    5209          25 :             std::min(m_nMaxFeatures, nFeaturesInTile);
    5210             :         char *pszSQL =
    5211          25 :             sqlite3_mprintf("SELECT layer, feature FROM temp "
    5212             :                             "WHERE z = %d AND x = %d AND y = %d ORDER BY "
    5213             :                             "area_or_length DESC LIMIT %d",
    5214             :                             nZ, nX, nY, nTotalFeaturesInTile);
    5215          25 :         sqlite3_stmt *hTmpStmt = nullptr;
    5216          25 :         CPL_IGNORE_RET_VAL(
    5217          25 :             sqlite3_prepare_v2(m_hDB, pszSQL, -1, &hTmpStmt, nullptr));
    5218          25 :         sqlite3_free(pszSQL);
    5219          25 :         if (!hTmpStmt)
    5220           0 :             return std::string();
    5221             : 
    5222             :         class TargetTileLayerProps
    5223             :         {
    5224             :           public:
    5225             :             std::shared_ptr<MVTTileLayer> m_poLayer;
    5226             :             std::map<CPLString, GUInt32> m_oMapKeyToIdx;
    5227             :             std::map<MVTTileLayerValue, GUInt32> m_oMapValueToIdx;
    5228             :         };
    5229             : 
    5230          50 :         std::map<std::string, TargetTileLayerProps> oMapLayerNameToTargetLayer;
    5231             : 
    5232          25 :         nFeaturesInTile = 0;
    5233          25 :         const unsigned nCheckStep = std::max(1U, nTotalFeaturesInTile / 100);
    5234          49 :         while (sqlite3_step(hTmpStmt) == SQLITE_ROW)
    5235             :         {
    5236             :             const char *pszLayerName = reinterpret_cast<const char *>(
    5237          37 :                 sqlite3_column_text(hTmpStmt, 0));
    5238          37 :             int nBlobSize = sqlite3_column_bytes(hTmpStmt, 1);
    5239          37 :             const void *pabyBlob = sqlite3_column_blob(hTmpStmt, 1);
    5240             : 
    5241           0 :             std::shared_ptr<MVTTileLayer> poTargetLayer;
    5242             :             std::map<CPLString, GUInt32> *poMapKeyToIdx;
    5243             :             std::map<MVTTileLayerValue, GUInt32> *poMapValueToIdx;
    5244          37 :             auto oIter = oMapLayerNameToTargetLayer.find(pszLayerName);
    5245          37 :             if (oIter == oMapLayerNameToTargetLayer.end())
    5246             :             {
    5247          25 :                 poTargetLayer = std::make_shared<MVTTileLayer>();
    5248          25 :                 TargetTileLayerProps props;
    5249          25 :                 props.m_poLayer = poTargetLayer;
    5250          25 :                 oTargetTile.addLayer(poTargetLayer);
    5251          25 :                 poTargetLayer->setName(pszLayerName);
    5252          25 :                 poTargetLayer->setVersion(m_nMVTVersion);
    5253          25 :                 poTargetLayer->setExtent(nExtent);
    5254          25 :                 oMapLayerNameToTargetLayer[pszLayerName] = std::move(props);
    5255          25 :                 poMapKeyToIdx =
    5256          25 :                     &oMapLayerNameToTargetLayer[pszLayerName].m_oMapKeyToIdx;
    5257          25 :                 poMapValueToIdx =
    5258          25 :                     &oMapLayerNameToTargetLayer[pszLayerName].m_oMapValueToIdx;
    5259             :             }
    5260             :             else
    5261             :             {
    5262          12 :                 poTargetLayer = oIter->second.m_poLayer;
    5263          12 :                 poMapKeyToIdx = &oIter->second.m_oMapKeyToIdx;
    5264          12 :                 poMapValueToIdx = &oIter->second.m_oMapValueToIdx;
    5265             :             }
    5266             : 
    5267          37 :             EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, *poMapKeyToIdx,
    5268             :                           *poMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
    5269             : 
    5270          37 :             if (nFeaturesInTile == nTotalFeaturesInTile ||
    5271          18 :                 (bTooBigTile && (nFeaturesInTile % nCheckStep == 0)))
    5272             :             {
    5273          37 :                 if (oTargetTile.getSize() * dfCompressionRatio > m_nMaxTileSize)
    5274             :                 {
    5275          13 :                     break;
    5276             :                 }
    5277             :             }
    5278             :         }
    5279             : 
    5280          25 :         oTileBuffer = oTargetTile.write();
    5281          25 :         if (m_bGZip)
    5282          25 :             GZIPCompress(oTileBuffer);
    5283             : 
    5284          25 :         if (bTooBigTile)
    5285             :         {
    5286          13 :             CPLDebug("MVT", "For tile %d/%d/%d, final tile size is %u", nZ, nX,
    5287          13 :                      nY, static_cast<unsigned>(oTileBuffer.size()));
    5288             :         }
    5289             : 
    5290          25 :         sqlite3_finalize(hTmpStmt);
    5291             :     }
    5292             : 
    5293         870 :     return oTileBuffer;
    5294             : }
    5295             : 
    5296             : /************************************************************************/
    5297             : /*                     RecodeTileLowerResolution()                      */
    5298             : /************************************************************************/
    5299             : 
    5300          82 : std::string OGRMVTWriterDataset::RecodeTileLowerResolution(
    5301             :     int nZ, int nX, int nY, int nExtent, sqlite3_stmt *hStmtLayer,
    5302             :     sqlite3_stmt *hStmtRows)
    5303             : {
    5304         164 :     MVTTile oTargetTile;
    5305             : 
    5306          82 :     sqlite3_bind_int(hStmtLayer, 1, nZ);
    5307          82 :     sqlite3_bind_int(hStmtLayer, 2, nX);
    5308          82 :     sqlite3_bind_int(hStmtLayer, 3, nY);
    5309             : 
    5310          82 :     unsigned nFeaturesInTile = 0;
    5311         328 :     while (nFeaturesInTile < m_nMaxFeatures &&
    5312         164 :            sqlite3_step(hStmtLayer) == SQLITE_ROW)
    5313             :     {
    5314             :         const char *pszLayerName =
    5315          82 :             reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
    5316          82 :         sqlite3_bind_int(hStmtRows, 1, nZ);
    5317          82 :         sqlite3_bind_int(hStmtRows, 2, nX);
    5318          82 :         sqlite3_bind_int(hStmtRows, 3, nY);
    5319          82 :         sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
    5320             : 
    5321         164 :         auto poTargetLayer = std::make_shared<MVTTileLayer>();
    5322          82 :         oTargetTile.addLayer(poTargetLayer);
    5323          82 :         poTargetLayer->setName(pszLayerName);
    5324          82 :         poTargetLayer->setVersion(m_nMVTVersion);
    5325          82 :         poTargetLayer->setExtent(nExtent);
    5326             : 
    5327         164 :         std::map<CPLString, GUInt32> oMapKeyToIdx;
    5328         164 :         std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
    5329             : 
    5330         448 :         while (nFeaturesInTile < m_nMaxFeatures &&
    5331         224 :                sqlite3_step(hStmtRows) == SQLITE_ROW)
    5332             :         {
    5333         142 :             int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
    5334         142 :             const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
    5335             : 
    5336         142 :             EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
    5337             :                           oMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
    5338             :         }
    5339          82 :         sqlite3_reset(hStmtRows);
    5340             :     }
    5341             : 
    5342          82 :     sqlite3_reset(hStmtLayer);
    5343             : 
    5344          82 :     std::string oTileBuffer(oTargetTile.write());
    5345          82 :     if (m_bGZip)
    5346          82 :         GZIPCompress(oTileBuffer);
    5347             : 
    5348         164 :     return oTileBuffer;
    5349             : }
    5350             : 
    5351             : /************************************************************************/
    5352             : /*                            CreateOutput()                            */
    5353             : /************************************************************************/
    5354             : 
    5355         123 : bool OGRMVTWriterDataset::CreateOutput()
    5356             : {
    5357         123 :     if (m_bThreadPoolOK)
    5358         120 :         m_oThreadPool.WaitCompletion();
    5359             : 
    5360         246 :     std::map<CPLString, MVTLayerProperties> oMapLayerProps;
    5361         246 :     std::set<CPLString> oSetLayers;
    5362             : 
    5363         123 :     if (!m_oEnvelope.IsInit())
    5364             :     {
    5365          50 :         return GenerateMetadata(0, oMapLayerProps);
    5366             :     }
    5367             : 
    5368          73 :     CPLDebug("MVT", "Building output file from temporary database...");
    5369             : 
    5370          73 :     sqlite3_stmt *hStmtZXY = nullptr;
    5371          73 :     CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    5372             :         m_hDB, "SELECT DISTINCT z, x, y FROM temp ORDER BY z, x, y", -1,
    5373             :         &hStmtZXY, nullptr));
    5374          73 :     if (hStmtZXY == nullptr)
    5375             :     {
    5376           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5377           2 :         return false;
    5378             :     }
    5379             : 
    5380          71 :     sqlite3_stmt *hStmtLayer = nullptr;
    5381          71 :     CPL_IGNORE_RET_VAL(
    5382          71 :         sqlite3_prepare_v2(m_hDB,
    5383             :                            "SELECT DISTINCT layer FROM temp "
    5384             :                            "WHERE z = ? AND x = ? AND y = ? ORDER BY layer",
    5385             :                            -1, &hStmtLayer, nullptr));
    5386          71 :     if (hStmtLayer == nullptr)
    5387             :     {
    5388           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5389           0 :         sqlite3_finalize(hStmtZXY);
    5390           0 :         return false;
    5391             :     }
    5392          71 :     sqlite3_stmt *hStmtRows = nullptr;
    5393          71 :     CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    5394             :         m_hDB,
    5395             :         "SELECT feature FROM temp "
    5396             :         "WHERE z = ? AND x = ? AND y = ? AND layer = ? ORDER BY idx",
    5397             :         -1, &hStmtRows, nullptr));
    5398          71 :     if (hStmtRows == nullptr)
    5399             :     {
    5400           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5401           0 :         sqlite3_finalize(hStmtZXY);
    5402           0 :         sqlite3_finalize(hStmtLayer);
    5403           0 :         return false;
    5404             :     }
    5405             : 
    5406          71 :     sqlite3_stmt *hInsertStmt = nullptr;
    5407          71 :     if (m_hDBMBTILES)
    5408             :     {
    5409          43 :         CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    5410             :             m_hDBMBTILES,
    5411             :             "INSERT INTO tiles(zoom_level, tile_column, tile_row, "
    5412             :             "tile_data) VALUES (?,?,?,?)",
    5413             :             -1, &hInsertStmt, nullptr));
    5414          43 :         if (hInsertStmt == nullptr)
    5415             :         {
    5416           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5417           0 :             sqlite3_finalize(hStmtZXY);
    5418           0 :             sqlite3_finalize(hStmtLayer);
    5419           0 :             sqlite3_finalize(hStmtRows);
    5420           0 :             return false;
    5421             :         }
    5422             :     }
    5423             : 
    5424          71 :     int nLastZ = -1;
    5425          71 :     int nLastX = -1;
    5426          71 :     bool bRet = true;
    5427          71 :     GIntBig nTempTilesRead = 0;
    5428             : 
    5429         940 :     while (sqlite3_step(hStmtZXY) == SQLITE_ROW)
    5430             :     {
    5431         870 :         int nZ = sqlite3_column_int(hStmtZXY, 0);
    5432         870 :         int nX = sqlite3_column_int(hStmtZXY, 1);
    5433         870 :         int nY = sqlite3_column_int(hStmtZXY, 2);
    5434             : 
    5435             :         std::string oTileBuffer(EncodeTile(nZ, nX, nY, hStmtLayer, hStmtRows,
    5436             :                                            oMapLayerProps, oSetLayers,
    5437         870 :                                            nTempTilesRead));
    5438             : 
    5439         870 :         if (oTileBuffer.empty())
    5440             :         {
    5441           0 :             bRet = false;
    5442             :         }
    5443         870 :         else if (hInsertStmt)
    5444             :         {
    5445         531 :             sqlite3_bind_int(hInsertStmt, 1, nZ);
    5446         531 :             sqlite3_bind_int(hInsertStmt, 2, nX);
    5447         531 :             sqlite3_bind_int(hInsertStmt, 3, (1 << nZ) - 1 - nY);
    5448         531 :             sqlite3_bind_blob(hInsertStmt, 4, oTileBuffer.data(),
    5449         531 :                               static_cast<int>(oTileBuffer.size()),
    5450             :                               SQLITE_STATIC);
    5451         531 :             const int rc = sqlite3_step(hInsertStmt);
    5452         531 :             bRet = (rc == SQLITE_OK || rc == SQLITE_DONE);
    5453         531 :             sqlite3_reset(hInsertStmt);
    5454             :         }
    5455             :         else
    5456             :         {
    5457             :             const std::string osZDirname(CPLFormFilenameSafe(
    5458         678 :                 GetDescription(), CPLSPrintf("%d", nZ), nullptr));
    5459             :             const std::string osXDirname(CPLFormFilenameSafe(
    5460         678 :                 osZDirname.c_str(), CPLSPrintf("%d", nX), nullptr));
    5461         339 :             if (nZ != nLastZ)
    5462             :             {
    5463         114 :                 VSIMkdir(osZDirname.c_str(), 0755);
    5464         114 :                 nLastZ = nZ;
    5465         114 :                 nLastX = -1;
    5466             :             }
    5467         339 :             if (nX != nLastX)
    5468             :             {
    5469         194 :                 VSIMkdir(osXDirname.c_str(), 0755);
    5470         194 :                 nLastX = nX;
    5471             :             }
    5472             :             const std::string osTileFilename(
    5473             :                 CPLFormFilenameSafe(osXDirname.c_str(), CPLSPrintf("%d", nY),
    5474         678 :                                     m_osExtension.c_str()));
    5475         339 :             VSILFILE *fpOut = VSIFOpenL(osTileFilename.c_str(), "wb");
    5476         339 :             if (fpOut)
    5477             :             {
    5478         338 :                 const size_t nRet = VSIFWriteL(oTileBuffer.data(), 1,
    5479             :                                                oTileBuffer.size(), fpOut);
    5480         338 :                 bRet = (nRet == oTileBuffer.size());
    5481         338 :                 VSIFCloseL(fpOut);
    5482             :             }
    5483             :             else
    5484             :             {
    5485           1 :                 bRet = false;
    5486             :             }
    5487             :         }
    5488             : 
    5489         870 :         if (!bRet)
    5490             :         {
    5491           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    5492             :                      "Error while writing tile %d/%d/%d", nZ, nX, nY);
    5493           1 :             break;
    5494             :         }
    5495             :     }
    5496          71 :     sqlite3_finalize(hStmtZXY);
    5497          71 :     sqlite3_finalize(hStmtLayer);
    5498          71 :     sqlite3_finalize(hStmtRows);
    5499          71 :     if (hInsertStmt)
    5500          43 :         sqlite3_finalize(hInsertStmt);
    5501             : 
    5502          71 :     bRet &= GenerateMetadata(oSetLayers.size(), oMapLayerProps);
    5503             : 
    5504          71 :     return bRet;
    5505             : }
    5506             : 
    5507             : /************************************************************************/
    5508             : /*                     SphericalMercatorToLongLat()                     */
    5509             : /************************************************************************/
    5510             : 
    5511         234 : static void SphericalMercatorToLongLat(double *x, double *y)
    5512             : {
    5513         234 :     double lng = *x / kmSPHERICAL_RADIUS / M_PI * 180;
    5514             :     double lat =
    5515         234 :         2 * (atan(exp(*y / kmSPHERICAL_RADIUS)) - M_PI / 4) / M_PI * 180;
    5516         234 :     *x = lng;
    5517         234 :     *y = lat;
    5518         234 : }
    5519             : 
    5520             : /************************************************************************/
    5521             : /*                         WriteMetadataItem()                          */
    5522             : /************************************************************************/
    5523             : 
    5524             : template <class T>
    5525        1309 : static bool WriteMetadataItemT(const char *pszKey, T value,
    5526             :                                const char *pszValueFormat, sqlite3 *hDBMBTILES,
    5527             :                                CPLJSONObject &oRoot)
    5528             : {
    5529        1309 :     if (hDBMBTILES)
    5530             :     {
    5531             :         char *pszSQL;
    5532             : 
    5533         825 :         pszSQL = sqlite3_mprintf(
    5534             :             CPLSPrintf("INSERT INTO metadata(name, value) VALUES('%%q', '%s')",
    5535             :                        pszValueFormat),
    5536             :             pszKey, value);
    5537         825 :         OGRErr eErr = SQLCommand(hDBMBTILES, pszSQL);
    5538         825 :         sqlite3_free(pszSQL);
    5539         825 :         return eErr == OGRERR_NONE;
    5540             :     }
    5541             :     else
    5542             :     {
    5543         484 :         oRoot.Add(pszKey, value);
    5544         484 :         return true;
    5545             :     }
    5546             : }
    5547             : 
    5548             : /************************************************************************/
    5549             : /*                         WriteMetadataItem()                          */
    5550             : /************************************************************************/
    5551             : 
    5552         926 : static bool WriteMetadataItem(const char *pszKey, const char *pszValue,
    5553             :                               sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
    5554             : {
    5555         926 :     return WriteMetadataItemT(pszKey, pszValue, "%q", hDBMBTILES, oRoot);
    5556             : }
    5557             : 
    5558             : /************************************************************************/
    5559             : /*                         WriteMetadataItem()                          */
    5560             : /************************************************************************/
    5561             : 
    5562         371 : static bool WriteMetadataItem(const char *pszKey, int nValue,
    5563             :                               sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
    5564             : {
    5565         371 :     return WriteMetadataItemT(pszKey, nValue, "%d", hDBMBTILES, oRoot);
    5566             : }
    5567             : 
    5568             : /************************************************************************/
    5569             : /*                         WriteMetadataItem()                          */
    5570             : /************************************************************************/
    5571             : 
    5572          12 : static bool WriteMetadataItem(const char *pszKey, double dfValue,
    5573             :                               sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
    5574             : {
    5575          12 :     return WriteMetadataItemT(pszKey, dfValue, "%.17g", hDBMBTILES, oRoot);
    5576             : }
    5577             : 
    5578             : /************************************************************************/
    5579             : /*                          GenerateMetadata()                          */
    5580             : /************************************************************************/
    5581             : 
    5582         121 : bool OGRMVTWriterDataset::GenerateMetadata(
    5583             :     size_t nLayers, const std::map<CPLString, MVTLayerProperties> &oMap)
    5584             : {
    5585         242 :     CPLJSONDocument oDoc;
    5586         242 :     CPLJSONObject oRoot = oDoc.GetRoot();
    5587             : 
    5588         242 :     OGRSpatialReference oSRS_EPSG3857;
    5589             :     double dfTopXWebMercator;
    5590             :     double dfTopYWebMercator;
    5591             :     double dfTileDim0WebMercator;
    5592         121 :     InitWebMercatorTilingScheme(&oSRS_EPSG3857, dfTopXWebMercator,
    5593             :                                 dfTopYWebMercator, dfTileDim0WebMercator);
    5594             :     const bool bIsStandardTilingScheme =
    5595         238 :         m_poSRS->IsSame(&oSRS_EPSG3857) && m_dfTopX == dfTopXWebMercator &&
    5596         238 :         m_dfTopY == dfTopYWebMercator && m_dfTileDim0 == dfTileDim0WebMercator;
    5597         121 :     if (bIsStandardTilingScheme)
    5598             :     {
    5599         117 :         SphericalMercatorToLongLat(&(m_oEnvelope.MinX), &(m_oEnvelope.MinY));
    5600         117 :         SphericalMercatorToLongLat(&(m_oEnvelope.MaxX), &(m_oEnvelope.MaxY));
    5601         117 :         m_oEnvelope.MinY = std::max(-85.0, m_oEnvelope.MinY);
    5602         117 :         m_oEnvelope.MaxY = std::min(85.0, m_oEnvelope.MaxY);
    5603             :     }
    5604             :     else
    5605             :     {
    5606           8 :         OGRSpatialReference oSRS_EPSG4326;
    5607           4 :         oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
    5608           4 :         oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    5609             :         OGRCoordinateTransformation *poCT =
    5610           4 :             OGRCreateCoordinateTransformation(m_poSRS, &oSRS_EPSG4326);
    5611           4 :         if (poCT)
    5612             :         {
    5613           8 :             OGRPoint oPoint1(m_oEnvelope.MinX, m_oEnvelope.MinY);
    5614           4 :             oPoint1.transform(poCT);
    5615           8 :             OGRPoint oPoint2(m_oEnvelope.MinX, m_oEnvelope.MaxY);
    5616           4 :             oPoint2.transform(poCT);
    5617           8 :             OGRPoint oPoint3(m_oEnvelope.MaxX, m_oEnvelope.MaxY);
    5618           4 :             oPoint3.transform(poCT);
    5619           8 :             OGRPoint oPoint4(m_oEnvelope.MaxX, m_oEnvelope.MinY);
    5620           4 :             oPoint4.transform(poCT);
    5621           4 :             m_oEnvelope.MinX =
    5622           4 :                 std::min(std::min(oPoint1.getX(), oPoint2.getX()),
    5623           8 :                          std::min(oPoint3.getX(), oPoint4.getX()));
    5624           4 :             m_oEnvelope.MinY =
    5625           4 :                 std::min(std::min(oPoint1.getY(), oPoint2.getY()),
    5626           8 :                          std::min(oPoint3.getY(), oPoint4.getY()));
    5627           4 :             m_oEnvelope.MaxX =
    5628           4 :                 std::max(std::max(oPoint1.getX(), oPoint2.getX()),
    5629           8 :                          std::max(oPoint3.getX(), oPoint4.getX()));
    5630           4 :             m_oEnvelope.MaxY =
    5631           4 :                 std::max(std::max(oPoint1.getY(), oPoint2.getY()),
    5632           8 :                          std::max(oPoint3.getY(), oPoint4.getY()));
    5633           4 :             delete poCT;
    5634             :         }
    5635             :     }
    5636         121 :     const double dfCenterX = (m_oEnvelope.MinX + m_oEnvelope.MaxX) / 2;
    5637         121 :     const double dfCenterY = (m_oEnvelope.MinY + m_oEnvelope.MaxY) / 2;
    5638             :     CPLString osCenter(
    5639         242 :         CPLSPrintf("%.7f,%.7f,%d", dfCenterX, dfCenterY, m_nMinZoom));
    5640             :     CPLString osBounds(CPLSPrintf("%.7f,%.7f,%.7f,%.7f", m_oEnvelope.MinX,
    5641             :                                   m_oEnvelope.MinY, m_oEnvelope.MaxX,
    5642         242 :                                   m_oEnvelope.MaxY));
    5643             : 
    5644         121 :     WriteMetadataItem("name", m_osName, m_hDBMBTILES, oRoot);
    5645         121 :     WriteMetadataItem("description", m_osDescription, m_hDBMBTILES, oRoot);
    5646         121 :     WriteMetadataItem("version", m_nMetadataVersion, m_hDBMBTILES, oRoot);
    5647         121 :     WriteMetadataItem("minzoom", m_nMinZoom, m_hDBMBTILES, oRoot);
    5648         121 :     WriteMetadataItem("maxzoom", m_nMaxZoom, m_hDBMBTILES, oRoot);
    5649         121 :     WriteMetadataItem("center", !m_osCenter.empty() ? m_osCenter : osCenter,
    5650             :                       m_hDBMBTILES, oRoot);
    5651         121 :     WriteMetadataItem("bounds", !m_osBounds.empty() ? m_osBounds : osBounds,
    5652             :                       m_hDBMBTILES, oRoot);
    5653         121 :     WriteMetadataItem("type", m_osType, m_hDBMBTILES, oRoot);
    5654         121 :     WriteMetadataItem("format", "pbf", m_hDBMBTILES, oRoot);
    5655         121 :     if (m_hDBMBTILES)
    5656             :     {
    5657          75 :         WriteMetadataItem("scheme", "tms", m_hDBMBTILES, oRoot);
    5658             :     }
    5659             : 
    5660             :     // GDAL extension for custom tiling schemes
    5661         121 :     if (!bIsStandardTilingScheme)
    5662             :     {
    5663           4 :         const char *pszAuthName = m_poSRS->GetAuthorityName(nullptr);
    5664           4 :         const char *pszAuthCode = m_poSRS->GetAuthorityCode(nullptr);
    5665           4 :         if (pszAuthName && pszAuthCode)
    5666             :         {
    5667           4 :             WriteMetadataItem("crs",
    5668             :                               CPLSPrintf("%s:%s", pszAuthName, pszAuthCode),
    5669             :                               m_hDBMBTILES, oRoot);
    5670             :         }
    5671             :         else
    5672             :         {
    5673           0 :             char *pszWKT = nullptr;
    5674           0 :             m_poSRS->exportToWkt(&pszWKT);
    5675           0 :             WriteMetadataItem("crs", pszWKT, m_hDBMBTILES, oRoot);
    5676           0 :             CPLFree(pszWKT);
    5677             :         }
    5678           4 :         WriteMetadataItem("tile_origin_upper_left_x", m_dfTopX, m_hDBMBTILES,
    5679             :                           oRoot);
    5680           4 :         WriteMetadataItem("tile_origin_upper_left_y", m_dfTopY, m_hDBMBTILES,
    5681             :                           oRoot);
    5682           4 :         WriteMetadataItem("tile_dimension_zoom_0", m_dfTileDim0, m_hDBMBTILES,
    5683             :                           oRoot);
    5684           4 :         WriteMetadataItem("tile_matrix_width_zoom_0", m_nTileMatrixWidth0,
    5685             :                           m_hDBMBTILES, oRoot);
    5686           4 :         WriteMetadataItem("tile_matrix_height_zoom_0", m_nTileMatrixHeight0,
    5687             :                           m_hDBMBTILES, oRoot);
    5688             :     }
    5689             : 
    5690         242 :     CPLJSONDocument oJsonDoc;
    5691         242 :     CPLJSONObject oJsonRoot = oJsonDoc.GetRoot();
    5692             : 
    5693         242 :     CPLJSONArray oVectorLayers;
    5694         121 :     oJsonRoot.Add("vector_layers", oVectorLayers);
    5695         242 :     std::set<std::string> oAlreadyVisited;
    5696         289 :     for (const auto &poLayer : m_apoLayers)
    5697             :     {
    5698         168 :         auto oIter = oMap.find(poLayer->m_osTargetName);
    5699         244 :         if (oIter != oMap.end() &&
    5700          76 :             oAlreadyVisited.find(poLayer->m_osTargetName) ==
    5701         244 :                 oAlreadyVisited.end())
    5702             :         {
    5703          76 :             oAlreadyVisited.insert(poLayer->m_osTargetName);
    5704             : 
    5705         152 :             CPLJSONObject oLayerObj;
    5706          76 :             oLayerObj.Add("id", poLayer->m_osTargetName);
    5707          76 :             oLayerObj.Add("description",
    5708          76 :                           m_oMapLayerNameToDesc[poLayer->m_osTargetName]);
    5709          76 :             oLayerObj.Add("minzoom", oIter->second.m_nMinZoom);
    5710          76 :             oLayerObj.Add("maxzoom", oIter->second.m_nMaxZoom);
    5711             : 
    5712         152 :             CPLJSONObject oFields;
    5713          76 :             oLayerObj.Add("fields", oFields);
    5714          76 :             auto poFDefn = poLayer->GetLayerDefn();
    5715         280 :             for (int i = 0; i < poFDefn->GetFieldCount(); i++)
    5716             :             {
    5717         204 :                 auto poFieldDefn = poFDefn->GetFieldDefn(i);
    5718         204 :                 auto eType = poFieldDefn->GetType();
    5719         239 :                 if (eType == OFTInteger &&
    5720          35 :                     poFieldDefn->GetSubType() == OFSTBoolean)
    5721             :                 {
    5722           1 :                     oFields.Add(poFieldDefn->GetNameRef(), "Boolean");
    5723             :                 }
    5724         203 :                 else if (eType == OFTInteger || eType == OFTInteger64 ||
    5725             :                          eType == OFTReal)
    5726             :                 {
    5727          73 :                     oFields.Add(poFieldDefn->GetNameRef(), "Number");
    5728             :                 }
    5729             :                 else
    5730             :                 {
    5731         130 :                     oFields.Add(poFieldDefn->GetNameRef(), "String");
    5732             :                 }
    5733             :             }
    5734             : 
    5735          76 :             oVectorLayers.Add(oLayerObj);
    5736             :         }
    5737             :     }
    5738             : 
    5739         242 :     CPLJSONObject oTileStats;
    5740         121 :     oJsonRoot.Add("tilestats", oTileStats);
    5741         121 :     oTileStats.Add("layerCount", static_cast<int>(nLayers));
    5742         242 :     CPLJSONArray oTileStatsLayers;
    5743         121 :     oTileStats.Add("layers", oTileStatsLayers);
    5744         121 :     oAlreadyVisited.clear();
    5745         289 :     for (const auto &poLayer : m_apoLayers)
    5746             :     {
    5747         168 :         auto oIter = oMap.find(poLayer->m_osTargetName);
    5748         244 :         if (oIter != oMap.end() &&
    5749          76 :             oAlreadyVisited.find(poLayer->m_osTargetName) ==
    5750         244 :                 oAlreadyVisited.end())
    5751             :         {
    5752          76 :             oAlreadyVisited.insert(poLayer->m_osTargetName);
    5753          76 :             auto &oLayerProps = oIter->second;
    5754         152 :             CPLJSONObject oLayerObj;
    5755             : 
    5756         152 :             std::string osName(poLayer->m_osTargetName);
    5757          76 :             osName.resize(std::min(knMAX_LAYER_NAME_LENGTH, osName.size()));
    5758          76 :             oLayerObj.Add("layer", osName);
    5759          76 :             oLayerObj.Add(
    5760             :                 "count",
    5761          76 :                 m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]);
    5762             : 
    5763             :             // Find majority geometry type
    5764          76 :             MVTTileLayerFeature::GeomType eMaxGeomType =
    5765             :                 MVTTileLayerFeature::GeomType::UNKNOWN;
    5766          76 :             GIntBig nMaxCountGeom = 0;
    5767         304 :             for (int i = static_cast<int>(MVTTileLayerFeature::GeomType::POINT);
    5768         304 :                  i <= static_cast<int>(MVTTileLayerFeature::GeomType::POLYGON);
    5769             :                  i++)
    5770             :             {
    5771         228 :                 MVTTileLayerFeature::GeomType eGeomType =
    5772         228 :                     static_cast<MVTTileLayerFeature::GeomType>(i);
    5773             :                 auto oIterCountGeom =
    5774         228 :                     oLayerProps.m_oCountGeomType.find(eGeomType);
    5775         228 :                 if (oIterCountGeom != oLayerProps.m_oCountGeomType.end())
    5776             :                 {
    5777          80 :                     if (oIterCountGeom->second >= nMaxCountGeom)
    5778             :                     {
    5779          79 :                         eMaxGeomType = eGeomType;
    5780          79 :                         nMaxCountGeom = oIterCountGeom->second;
    5781             :                     }
    5782             :                 }
    5783             :             }
    5784          76 :             if (eMaxGeomType == MVTTileLayerFeature::GeomType::POINT)
    5785          63 :                 oLayerObj.Add("geometry", "Point");
    5786          13 :             else if (eMaxGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
    5787           6 :                 oLayerObj.Add("geometry", "LineString");
    5788           7 :             else if (eMaxGeomType == MVTTileLayerFeature::GeomType::POLYGON)
    5789           7 :                 oLayerObj.Add("geometry", "Polygon");
    5790             : 
    5791          76 :             oLayerObj.Add("attributeCount",
    5792          76 :                           static_cast<int>(oLayerProps.m_oSetFields.size()));
    5793         152 :             CPLJSONArray oAttributes;
    5794          76 :             oLayerObj.Add("attributes", oAttributes);
    5795         257 :             for (const auto &oFieldProps : oLayerProps.m_aoFields)
    5796             :             {
    5797         362 :                 CPLJSONObject oFieldObj;
    5798         181 :                 oAttributes.Add(oFieldObj);
    5799         362 :                 std::string osFieldNameTruncated(oFieldProps.m_osName);
    5800         181 :                 osFieldNameTruncated.resize(std::min(
    5801         181 :                     knMAX_FIELD_NAME_LENGTH, osFieldNameTruncated.size()));
    5802         181 :                 oFieldObj.Add("attribute", osFieldNameTruncated);
    5803         181 :                 oFieldObj.Add("count", static_cast<int>(
    5804         181 :                                            oFieldProps.m_oSetAllValues.size()));
    5805         181 :                 oFieldObj.Add("type",
    5806         181 :                               oFieldProps.m_eType ==
    5807             :                                       MVTTileLayerValue::ValueType::DOUBLE
    5808             :                                   ? "number"
    5809         108 :                               : oFieldProps.m_eType ==
    5810             :                                       MVTTileLayerValue::ValueType::STRING
    5811         108 :                                   ? "string"
    5812             :                                   : "boolean");
    5813             : 
    5814         362 :                 CPLJSONArray oValues;
    5815         181 :                 oFieldObj.Add("values", oValues);
    5816         408 :                 for (const auto &oIterValue : oFieldProps.m_oSetValues)
    5817             :                 {
    5818         227 :                     if (oIterValue.getType() ==
    5819             :                         MVTTileLayerValue::ValueType::BOOL)
    5820             :                     {
    5821           1 :                         oValues.Add(oIterValue.getBoolValue());
    5822             :                     }
    5823         226 :                     else if (oIterValue.isNumeric())
    5824             :                     {
    5825         104 :                         if (oFieldProps.m_bAllInt)
    5826             :                         {
    5827          53 :                             oValues.Add(static_cast<GInt64>(
    5828          53 :                                 oIterValue.getNumericValue()));
    5829             :                         }
    5830             :                         else
    5831             :                         {
    5832          51 :                             oValues.Add(oIterValue.getNumericValue());
    5833             :                         }
    5834             :                     }
    5835         122 :                     else if (oIterValue.isString())
    5836             :                     {
    5837         122 :                         oValues.Add(oIterValue.getStringValue());
    5838             :                     }
    5839             :                 }
    5840             : 
    5841         181 :                 if (oFieldProps.m_eType == MVTTileLayerValue::ValueType::DOUBLE)
    5842             :                 {
    5843          73 :                     if (oFieldProps.m_bAllInt)
    5844             :                     {
    5845          37 :                         oFieldObj.Add(
    5846          37 :                             "min", static_cast<GInt64>(oFieldProps.m_dfMinVal));
    5847          37 :                         oFieldObj.Add(
    5848          37 :                             "max", static_cast<GInt64>(oFieldProps.m_dfMaxVal));
    5849             :                     }
    5850             :                     else
    5851             :                     {
    5852          36 :                         oFieldObj.Add("min", oFieldProps.m_dfMinVal);
    5853          36 :                         oFieldObj.Add("max", oFieldProps.m_dfMaxVal);
    5854             :                     }
    5855             :                 }
    5856             :             }
    5857             : 
    5858          76 :             oTileStatsLayers.Add(oLayerObj);
    5859             :         }
    5860             :     }
    5861             : 
    5862         121 :     WriteMetadataItem("json", oJsonDoc.SaveAsString().c_str(), m_hDBMBTILES,
    5863             :                       oRoot);
    5864             : 
    5865         121 :     if (m_hDBMBTILES)
    5866             :     {
    5867          75 :         return true;
    5868             :     }
    5869             : 
    5870          46 :     return oDoc.Save(
    5871          92 :         CPLFormFilenameSafe(GetDescription(), "metadata.json", nullptr));
    5872             : }
    5873             : 
    5874             : /************************************************************************/
    5875             : /*                            WriteFeature()                            */
    5876             : /************************************************************************/
    5877             : 
    5878         251 : OGRErr OGRMVTWriterDataset::WriteFeature(OGRMVTWriterLayer *poLayer,
    5879             :                                          OGRFeature *poFeature, GIntBig nSerial,
    5880             :                                          OGRGeometry *poGeom)
    5881             : {
    5882         251 :     if (poFeature->GetGeometryRef() == poGeom)
    5883             :     {
    5884         193 :         m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]++;
    5885             :     }
    5886             : 
    5887         251 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    5888         251 :     if (eGeomType == wkbGeometryCollection)
    5889             :     {
    5890          21 :         OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    5891          78 :         for (int i = 0; i < poGC->getNumGeometries(); i++)
    5892             :         {
    5893          58 :             if (WriteFeature(poLayer, poFeature, nSerial,
    5894          58 :                              poGC->getGeometryRef(i)) != OGRERR_NONE)
    5895             :             {
    5896           1 :                 return OGRERR_FAILURE;
    5897             :             }
    5898             :         }
    5899          20 :         return OGRERR_NONE;
    5900             :     }
    5901             : 
    5902         230 :     OGREnvelope sExtent;
    5903         230 :     poGeom->getEnvelope(&sExtent);
    5904             : 
    5905         230 :     if (!m_oEnvelope.IsInit())
    5906             :     {
    5907          73 :         CPLDebug("MVT", "Creating temporary database...");
    5908             :     }
    5909             : 
    5910         230 :     m_oEnvelope.Merge(sExtent);
    5911             : 
    5912         230 :     if (!m_bReuseTempFile)
    5913             :     {
    5914         229 :         auto poFeatureContent = std::make_shared<OGRMVTFeatureContent>();
    5915         229 :         auto poSharedGeom = std::shared_ptr<OGRGeometry>(poGeom->clone());
    5916             : 
    5917         229 :         poFeatureContent->nFID = poFeature->GetFID();
    5918             : 
    5919         229 :         const OGRFeatureDefn *poFDefn = poFeature->GetDefnRef();
    5920         996 :         for (int i = 0; i < poFeature->GetFieldCount(); i++)
    5921             :         {
    5922         767 :             if (poFeature->IsFieldSetAndNotNull(i))
    5923             :             {
    5924         666 :                 MVTTileLayerValue oValue;
    5925         666 :                 const OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(i);
    5926         666 :                 OGRFieldType eFieldType = poFieldDefn->GetType();
    5927         666 :                 if (eFieldType == OFTInteger || eFieldType == OFTInteger64)
    5928             :                 {
    5929         145 :                     if (poFieldDefn->GetSubType() == OFSTBoolean)
    5930             :                     {
    5931           2 :                         oValue.setBoolValue(poFeature->GetFieldAsInteger(i) !=
    5932             :                                             0);
    5933             :                     }
    5934             :                     else
    5935             :                     {
    5936         143 :                         oValue.setValue(poFeature->GetFieldAsInteger64(i));
    5937             :                     }
    5938             :                 }
    5939         521 :                 else if (eFieldType == OFTReal)
    5940             :                 {
    5941         140 :                     oValue.setValue(poFeature->GetFieldAsDouble(i));
    5942             :                 }
    5943         381 :                 else if (eFieldType == OFTDate || eFieldType == OFTDateTime)
    5944             :                 {
    5945             :                     int nYear, nMonth, nDay, nHour, nMin, nTZ;
    5946             :                     float fSec;
    5947         238 :                     poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay,
    5948             :                                                   &nHour, &nMin, &fSec, &nTZ);
    5949         476 :                     CPLString osFormatted;
    5950         238 :                     if (eFieldType == OFTDate)
    5951             :                     {
    5952         119 :                         osFormatted.Printf("%04d-%02d-%02d", nYear, nMonth,
    5953         119 :                                            nDay);
    5954             :                     }
    5955             :                     else
    5956             :                     {
    5957             :                         char *pszFormatted =
    5958         119 :                             OGRGetXMLDateTime(poFeature->GetRawFieldRef(i));
    5959         119 :                         osFormatted = pszFormatted;
    5960         119 :                         CPLFree(pszFormatted);
    5961             :                     }
    5962         476 :                     oValue.setStringValue(osFormatted);
    5963             :                 }
    5964             :                 else
    5965             :                 {
    5966         143 :                     oValue.setStringValue(
    5967         286 :                         std::string(poFeature->GetFieldAsString(i)));
    5968             :                 }
    5969             : 
    5970         666 :                 poFeatureContent->oValues.emplace_back(
    5971         666 :                     std::pair<std::string, MVTTileLayerValue>(
    5972        1332 :                         poFieldDefn->GetNameRef(), oValue));
    5973             :             }
    5974             :         }
    5975             : 
    5976        1564 :         for (int nZ = poLayer->m_nMinZoom; nZ <= poLayer->m_nMaxZoom; nZ++)
    5977             :         {
    5978        1336 :             double dfTileDim = m_dfTileDim0 / (1 << nZ);
    5979        1336 :             double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
    5980             :             const int nTileMinX = std::max(
    5981        2672 :                 0, static_cast<int>((sExtent.MinX - m_dfTopX - dfBuffer) /
    5982        1336 :                                     dfTileDim));
    5983             :             const int nTileMinY = std::max(
    5984        2672 :                 0, static_cast<int>((m_dfTopY - sExtent.MaxY - dfBuffer) /
    5985        1336 :                                     dfTileDim));
    5986             :             const int nTileMaxX =
    5987        2672 :                 std::min(static_cast<int>((sExtent.MaxX - m_dfTopX + dfBuffer) /
    5988             :                                           dfTileDim),
    5989        2672 :                          static_cast<int>(std::min<int64_t>(
    5990        2672 :                              INT_MAX, (static_cast<int64_t>(1) << nZ) *
    5991        2672 :                                               m_nTileMatrixWidth0 -
    5992        1336 :                                           1)));
    5993             :             const int nTileMaxY =
    5994        2672 :                 std::min(static_cast<int>((m_dfTopY - sExtent.MinY + dfBuffer) /
    5995             :                                           dfTileDim),
    5996        2672 :                          static_cast<int>(std::min<int64_t>(
    5997        2672 :                              INT_MAX, (static_cast<int64_t>(1) << nZ) *
    5998        2672 :                                               m_nTileMatrixHeight0 -
    5999        1336 :                                           1)));
    6000        3588 :             for (int iX = nTileMinX; iX <= nTileMaxX; iX++)
    6001             :             {
    6002        6305 :                 for (int iY = nTileMinY; iY <= nTileMaxY; iY++)
    6003             :                 {
    6004        8106 :                     if (PreGenerateForTile(
    6005        4053 :                             nZ, iX, iY, poLayer->m_osTargetName,
    6006        4053 :                             (nZ == poLayer->m_nMaxZoom), poFeatureContent,
    6007        4053 :                             nSerial, poSharedGeom, sExtent) != OGRERR_NONE)
    6008             :                     {
    6009           1 :                         return OGRERR_FAILURE;
    6010             :                     }
    6011             :                 }
    6012             :             }
    6013             :         }
    6014             :     }
    6015             : 
    6016         229 :     return OGRERR_NONE;
    6017             : }
    6018             : 
    6019             : /************************************************************************/
    6020             : /*                           TestCapability()                           */
    6021             : /************************************************************************/
    6022             : 
    6023         207 : int OGRMVTWriterDataset::TestCapability(const char *pszCap) const
    6024             : {
    6025         207 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCRandomLayerWrite))
    6026         117 :         return true;
    6027          90 :     return false;
    6028             : }
    6029             : 
    6030             : /************************************************************************/
    6031             : /*                         ValidateMinMaxZoom()                         */
    6032             : /************************************************************************/
    6033             : 
    6034         301 : static bool ValidateMinMaxZoom(int nMinZoom, int nMaxZoom)
    6035             : {
    6036         301 :     if (nMinZoom < 0 || nMinZoom > 22)
    6037             :     {
    6038           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid MINZOOM");
    6039           2 :         return false;
    6040             :     }
    6041         299 :     if (nMaxZoom < 0 || nMaxZoom > 22)
    6042             :     {
    6043           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM");
    6044           1 :         return false;
    6045             :     }
    6046         298 :     if (nMaxZoom < nMinZoom)
    6047             :     {
    6048           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM < MINZOOM");
    6049           1 :         return false;
    6050             :     }
    6051         297 :     return true;
    6052             : }
    6053             : 
    6054             : /************************************************************************/
    6055             : /*                            ICreateLayer()                            */
    6056             : /************************************************************************/
    6057             : 
    6058             : OGRLayer *
    6059         171 : OGRMVTWriterDataset::ICreateLayer(const char *pszLayerName,
    6060             :                                   const OGRGeomFieldDefn *poGeomFieldDefn,
    6061             :                                   CSLConstList papszOptions)
    6062             : {
    6063         171 :     OGRSpatialReference *poSRSClone = nullptr;
    6064             :     const auto poSRS =
    6065         171 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6066         171 :     if (poSRS)
    6067             :     {
    6068           8 :         poSRSClone = poSRS->Clone();
    6069           8 :         poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6070             :     }
    6071             :     OGRMVTWriterLayer *poLayer =
    6072         171 :         new OGRMVTWriterLayer(this, pszLayerName, poSRSClone);
    6073         171 :     if (poSRSClone)
    6074           8 :         poSRSClone->Release();
    6075         171 :     poLayer->m_nMinZoom = m_nMinZoom;
    6076         171 :     poLayer->m_nMaxZoom = m_nMaxZoom;
    6077         171 :     poLayer->m_osTargetName = pszLayerName;
    6078             : 
    6079             :     /*
    6080             : 
    6081             :             {
    6082             :                 "src_layer":
    6083             :                     { "target_name": "",
    6084             :                       "description": "",
    6085             :                       "minzoom": 0,
    6086             :                       "maxzoom": 0
    6087             :                     }
    6088             :             }
    6089             :     */
    6090             : 
    6091         513 :     CPLJSONObject oObj = m_oConf.GetRoot().GetObj(pszLayerName);
    6092         342 :     CPLString osDescription;
    6093         171 :     if (oObj.IsValid())
    6094             :     {
    6095           4 :         std::string osTargetName = oObj.GetString("target_name");
    6096           2 :         if (!osTargetName.empty())
    6097           2 :             poLayer->m_osTargetName = std::move(osTargetName);
    6098           2 :         int nMinZoom = oObj.GetInteger("minzoom", -1);
    6099           2 :         if (nMinZoom >= 0)
    6100           2 :             poLayer->m_nMinZoom = nMinZoom;
    6101           2 :         int nMaxZoom = oObj.GetInteger("maxzoom", -1);
    6102           2 :         if (nMaxZoom >= 0)
    6103           2 :             poLayer->m_nMaxZoom = nMaxZoom;
    6104           2 :         osDescription = oObj.GetString("description");
    6105             :     }
    6106             : 
    6107         171 :     poLayer->m_nMinZoom = atoi(CSLFetchNameValueDef(
    6108             :         papszOptions, "MINZOOM", CPLSPrintf("%d", poLayer->m_nMinZoom)));
    6109         171 :     poLayer->m_nMaxZoom = atoi(CSLFetchNameValueDef(
    6110             :         papszOptions, "MAXZOOM", CPLSPrintf("%d", poLayer->m_nMaxZoom)));
    6111         171 :     if (!ValidateMinMaxZoom(poLayer->m_nMinZoom, poLayer->m_nMaxZoom))
    6112             :     {
    6113           1 :         delete poLayer;
    6114           1 :         return nullptr;
    6115             :     }
    6116             :     poLayer->m_osTargetName = CSLFetchNameValueDef(
    6117         170 :         papszOptions, "NAME", poLayer->m_osTargetName.c_str());
    6118             :     osDescription =
    6119         170 :         CSLFetchNameValueDef(papszOptions, "DESCRIPTION", osDescription);
    6120         170 :     if (!osDescription.empty())
    6121           4 :         m_oMapLayerNameToDesc[poLayer->m_osTargetName] =
    6122           2 :             std::move(osDescription);
    6123             : 
    6124         170 :     m_apoLayers.push_back(std::unique_ptr<OGRMVTWriterLayer>(poLayer));
    6125         170 :     return m_apoLayers.back().get();
    6126             : }
    6127             : 
    6128             : /************************************************************************/
    6129             : /*                               Create()                               */
    6130             : /************************************************************************/
    6131             : 
    6132         140 : GDALDataset *OGRMVTWriterDataset::Create(const char *pszFilename, int nXSize,
    6133             :                                          int nYSize, int nBandsIn,
    6134             :                                          GDALDataType eDT,
    6135             :                                          CSLConstList papszOptions)
    6136             : {
    6137         140 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0 || eDT != GDT_Unknown)
    6138             :     {
    6139           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6140             :                  "Only vector creation supported");
    6141           1 :         return nullptr;
    6142             :     }
    6143             : 
    6144         139 :     const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
    6145             :     const bool bMBTILESExt =
    6146         139 :         EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "mbtiles");
    6147         139 :     if (pszFormat == nullptr && bMBTILESExt)
    6148             :     {
    6149           4 :         pszFormat = "MBTILES";
    6150             :     }
    6151         139 :     const bool bMBTILES = pszFormat != nullptr && EQUAL(pszFormat, "MBTILES");
    6152             : 
    6153             :     // For debug only
    6154             :     bool bReuseTempFile =
    6155         139 :         CPLTestBool(CPLGetConfigOption("OGR_MVT_REUSE_TEMP_FILE", "NO"));
    6156             : 
    6157         139 :     if (bMBTILES)
    6158             :     {
    6159          80 :         if (!bMBTILESExt)
    6160             :         {
    6161           1 :             CPLError(CE_Failure, CPLE_FileIO,
    6162             :                      "%s should have mbtiles extension", pszFilename);
    6163           1 :             return nullptr;
    6164             :         }
    6165             : 
    6166          79 :         VSIUnlink(pszFilename);
    6167             :     }
    6168             :     else
    6169             :     {
    6170             :         VSIStatBufL sStat;
    6171          59 :         if (VSIStatL(pszFilename, &sStat) == 0)
    6172             :         {
    6173           3 :             CPLError(CE_Failure, CPLE_FileIO, "%s already exists", pszFilename);
    6174           5 :             return nullptr;
    6175             :         }
    6176             : 
    6177          56 :         if (VSIMkdir(pszFilename, 0755) != 0)
    6178             :         {
    6179           2 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
    6180             :                      pszFilename);
    6181           2 :             return nullptr;
    6182             :         }
    6183             :     }
    6184             : 
    6185         133 :     OGRMVTWriterDataset *poDS = new OGRMVTWriterDataset();
    6186         133 :     poDS->m_pMyVFS = OGRSQLiteCreateVFS(nullptr, poDS);
    6187         133 :     sqlite3_vfs_register(poDS->m_pMyVFS, 0);
    6188             : 
    6189         399 :     CPLString osTempDBDefault = CPLString(pszFilename) + ".temp.db";
    6190         133 :     if (STARTS_WITH(osTempDBDefault, "/vsizip/"))
    6191             :     {
    6192             :         osTempDBDefault =
    6193           0 :             CPLString(pszFilename + strlen("/vsizip/")) + ".temp.db";
    6194             :     }
    6195             :     CPLString osTempDB = CSLFetchNameValueDef(papszOptions, "TEMPORARY_DB",
    6196         266 :                                               osTempDBDefault.c_str());
    6197         133 :     if (!bReuseTempFile)
    6198         132 :         VSIUnlink(osTempDB);
    6199             : 
    6200         133 :     sqlite3 *hDB = nullptr;
    6201         133 :     if (sqlite3_open_v2(osTempDB, &hDB,
    6202             :                         SQLITE_OPEN_READWRITE |
    6203             :                             (bReuseTempFile ? 0 : SQLITE_OPEN_CREATE) |
    6204             :                             SQLITE_OPEN_NOMUTEX,
    6205         396 :                         poDS->m_pMyVFS->zName) != SQLITE_OK ||
    6206         130 :         hDB == nullptr)
    6207             :     {
    6208           3 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", osTempDB.c_str());
    6209           3 :         delete poDS;
    6210           3 :         sqlite3_close(hDB);
    6211           3 :         return nullptr;
    6212             :     }
    6213         130 :     poDS->m_osTempDB = osTempDB;
    6214         130 :     poDS->m_hDB = hDB;
    6215         130 :     poDS->m_bReuseTempFile = bReuseTempFile;
    6216             : 
    6217             :     // For Unix
    6218         259 :     if (!poDS->m_bReuseTempFile &&
    6219         129 :         CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
    6220             :     {
    6221         126 :         VSIUnlink(osTempDB);
    6222             :     }
    6223             : 
    6224         130 :     if (poDS->m_bReuseTempFile)
    6225             :     {
    6226           1 :         poDS->m_nTempTiles =
    6227           1 :             SQLGetInteger64(hDB, "SELECT COUNT(*) FROM temp", nullptr);
    6228             :     }
    6229             :     else
    6230             :     {
    6231         129 :         CPL_IGNORE_RET_VAL(SQLCommand(
    6232             :             hDB,
    6233             :             "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
    6234             :             "PRAGMA synchronous = OFF;"
    6235             :             "PRAGMA journal_mode = OFF;"
    6236             :             "PRAGMA temp_store = MEMORY;"
    6237             :             "CREATE TABLE temp(z INTEGER, x INTEGER, y INTEGER, layer TEXT, "
    6238             :             "idx INTEGER, feature BLOB, geomtype INTEGER, area_or_length "
    6239             :             "DOUBLE);"
    6240             :             "CREATE INDEX temp_index ON temp (z, x, y, layer, idx);"));
    6241             :     }
    6242             : 
    6243         130 :     sqlite3_stmt *hInsertStmt = nullptr;
    6244         130 :     CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    6245             :         hDB,
    6246             :         "INSERT INTO temp (z,x,y,layer,idx,feature,geomtype,area_or_length) "
    6247             :         "VALUES (?,?,?,?,?,?,?,?)",
    6248             :         -1, &hInsertStmt, nullptr));
    6249         130 :     if (hInsertStmt == nullptr)
    6250             :     {
    6251           0 :         delete poDS;
    6252           0 :         return nullptr;
    6253             :     }
    6254         130 :     poDS->m_hInsertStmt = hInsertStmt;
    6255             : 
    6256         130 :     poDS->m_nMinZoom = atoi(CSLFetchNameValueDef(
    6257             :         papszOptions, "MINZOOM", CPLSPrintf("%d", poDS->m_nMinZoom)));
    6258         130 :     poDS->m_nMaxZoom = atoi(CSLFetchNameValueDef(
    6259             :         papszOptions, "MAXZOOM", CPLSPrintf("%d", poDS->m_nMaxZoom)));
    6260         130 :     if (!ValidateMinMaxZoom(poDS->m_nMinZoom, poDS->m_nMaxZoom))
    6261             :     {
    6262           3 :         delete poDS;
    6263           3 :         return nullptr;
    6264             :     }
    6265             : 
    6266         127 :     const char *pszConf = CSLFetchNameValue(papszOptions, "CONF");
    6267         127 :     if (pszConf)
    6268             :     {
    6269             :         VSIStatBufL sStat;
    6270             :         bool bSuccess;
    6271           3 :         if (VSIStatL(pszConf, &sStat) == 0)
    6272             :         {
    6273           2 :             bSuccess = poDS->m_oConf.Load(pszConf);
    6274             :         }
    6275             :         else
    6276             :         {
    6277           1 :             bSuccess = poDS->m_oConf.LoadMemory(pszConf);
    6278             :         }
    6279           3 :         if (!bSuccess)
    6280             :         {
    6281           1 :             delete poDS;
    6282           1 :             return nullptr;
    6283             :         }
    6284             :     }
    6285             : 
    6286         126 :     poDS->m_dfSimplification =
    6287         126 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION", "0"));
    6288         126 :     poDS->m_dfSimplificationMaxZoom = CPLAtof(
    6289             :         CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION_MAX_ZOOM",
    6290             :                              CPLSPrintf("%g", poDS->m_dfSimplification)));
    6291         126 :     poDS->m_nExtent = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
    6292             :         papszOptions, "EXTENT", CPLSPrintf("%u", poDS->m_nExtent))));
    6293         126 :     poDS->m_nBuffer = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
    6294         126 :         papszOptions, "BUFFER", CPLSPrintf("%u", 5 * poDS->m_nExtent / 256))));
    6295             : 
    6296             :     {
    6297         126 :         const char *pszMaxSize = CSLFetchNameValue(papszOptions, "MAX_SIZE");
    6298         126 :         poDS->m_bMaxTileSizeOptSpecified = pszMaxSize != nullptr;
    6299             :         // This is used by unit tests
    6300         126 :         pszMaxSize = CSLFetchNameValueDef(papszOptions, "@MAX_SIZE_FOR_TEST",
    6301             :                                           pszMaxSize);
    6302         126 :         if (pszMaxSize)
    6303             :         {
    6304           3 :             poDS->m_nMaxTileSize =
    6305           3 :                 std::max(100U, static_cast<unsigned>(atoi(pszMaxSize)));
    6306             :         }
    6307             :     }
    6308             : 
    6309             :     {
    6310             :         const char *pszMaxFeatures =
    6311         126 :             CSLFetchNameValue(papszOptions, "MAX_FEATURES");
    6312         126 :         poDS->m_bMaxFeaturesOptSpecified = pszMaxFeatures != nullptr;
    6313         126 :         pszMaxFeatures = CSLFetchNameValueDef(
    6314             :             // This is used by unit tests
    6315             :             papszOptions, "@MAX_FEATURES_FOR_TEST", pszMaxFeatures);
    6316         126 :         if (pszMaxFeatures)
    6317             :         {
    6318           2 :             poDS->m_nMaxFeatures =
    6319           2 :                 std::max(1U, static_cast<unsigned>(atoi(pszMaxFeatures)));
    6320             :         }
    6321             :     }
    6322             : 
    6323             :     poDS->m_osName = CSLFetchNameValueDef(
    6324         126 :         papszOptions, "NAME", CPLGetBasenameSafe(pszFilename).c_str());
    6325             :     poDS->m_osDescription = CSLFetchNameValueDef(papszOptions, "DESCRIPTION",
    6326         126 :                                                  poDS->m_osDescription.c_str());
    6327             :     poDS->m_osType =
    6328         126 :         CSLFetchNameValueDef(papszOptions, "TYPE", poDS->m_osType.c_str());
    6329         126 :     poDS->m_bGZip = CPLFetchBool(papszOptions, "COMPRESS", poDS->m_bGZip);
    6330         126 :     poDS->m_osBounds = CSLFetchNameValueDef(papszOptions, "BOUNDS", "");
    6331         126 :     poDS->m_osCenter = CSLFetchNameValueDef(papszOptions, "CENTER", "");
    6332             :     poDS->m_osExtension = CSLFetchNameValueDef(papszOptions, "TILE_EXTENSION",
    6333         126 :                                                poDS->m_osExtension);
    6334             : 
    6335             :     const char *pszTilingScheme =
    6336         126 :         CSLFetchNameValue(papszOptions, "TILING_SCHEME");
    6337         126 :     if (pszTilingScheme)
    6338             :     {
    6339           6 :         if (bMBTILES)
    6340             :         {
    6341           1 :             CPLError(CE_Failure, CPLE_NotSupported,
    6342             :                      "Custom TILING_SCHEME not supported with MBTILES output");
    6343           1 :             delete poDS;
    6344           2 :             return nullptr;
    6345             :         }
    6346             : 
    6347           5 :         const CPLStringList aoList(CSLTokenizeString2(pszTilingScheme, ",", 0));
    6348           5 :         if (aoList.Count() >= 4)
    6349             :         {
    6350           4 :             poDS->m_poSRS->SetFromUserInput(aoList[0]);
    6351           4 :             poDS->m_dfTopX = CPLAtof(aoList[1]);
    6352           4 :             poDS->m_dfTopY = CPLAtof(aoList[2]);
    6353           4 :             poDS->m_dfTileDim0 = CPLAtof(aoList[3]);
    6354           4 :             if (aoList.Count() == 6)
    6355             :             {
    6356           2 :                 poDS->m_nTileMatrixWidth0 = std::max(1, atoi(aoList[4]));
    6357           2 :                 poDS->m_nTileMatrixHeight0 = std::max(1, atoi(aoList[5]));
    6358             :             }
    6359           2 :             else if (poDS->m_dfTopX == -180 && poDS->m_dfTileDim0 == 180)
    6360             :             {
    6361             :                 // Assumes WorldCRS84Quad with 2 tiles in width
    6362             :                 // cf https://github.com/OSGeo/gdal/issues/11749
    6363           1 :                 poDS->m_nTileMatrixWidth0 = 2;
    6364             :             }
    6365             :         }
    6366             :         else
    6367             :         {
    6368           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6369             :                      "Wrong format for TILING_SCHEME. "
    6370             :                      "Expecting EPSG:XXXX,tile_origin_upper_left_x,"
    6371             :                      "tile_origin_upper_left_y,tile_dimension_zoom_0[,tile_"
    6372             :                      "matrix_width_zoom_0,tile_matrix_height_zoom_0]");
    6373           1 :             delete poDS;
    6374           1 :             return nullptr;
    6375             :         }
    6376             :     }
    6377             : 
    6378         124 :     if (bMBTILES)
    6379             :     {
    6380         228 :         if (sqlite3_open_v2(pszFilename, &poDS->m_hDBMBTILES,
    6381             :                             SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
    6382             :                                 SQLITE_OPEN_NOMUTEX,
    6383         151 :                             poDS->m_pMyVFS->zName) != SQLITE_OK ||
    6384          75 :             poDS->m_hDBMBTILES == nullptr)
    6385             :         {
    6386           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
    6387           1 :             delete poDS;
    6388           1 :             return nullptr;
    6389             :         }
    6390             : 
    6391          75 :         if (SQLCommand(
    6392             :                 poDS->m_hDBMBTILES,
    6393             :                 "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
    6394             :                 "PRAGMA synchronous = OFF;"
    6395             :                 "PRAGMA journal_mode = OFF;"
    6396             :                 "PRAGMA temp_store = MEMORY;"
    6397             :                 "CREATE TABLE metadata (name text, value text);"
    6398             :                 "CREATE TABLE tiles (zoom_level integer, tile_column integer, "
    6399             :                 "tile_row integer, tile_data blob, "
    6400          75 :                 "UNIQUE (zoom_level, tile_column, tile_row))") != OGRERR_NONE)
    6401             :         {
    6402           0 :             delete poDS;
    6403           0 :             return nullptr;
    6404             :         }
    6405             :     }
    6406             : 
    6407         123 :     const int nThreads = GDALGetNumThreads(GDAL_DEFAULT_MAX_THREAD_COUNT,
    6408             :                                            /* bDefaultToAllCPUs = */ true);
    6409         123 :     if (nThreads > 1)
    6410             :     {
    6411         120 :         poDS->m_bThreadPoolOK =
    6412         120 :             poDS->m_oThreadPool.Setup(nThreads, nullptr, nullptr);
    6413             :     }
    6414             : 
    6415         123 :     poDS->SetDescription(pszFilename);
    6416         123 :     poDS->poDriver = GDALDriver::FromHandle(GDALGetDriverByName("MVT"));
    6417             : 
    6418         123 :     return poDS;
    6419             : }
    6420             : 
    6421          75 : GDALDataset *OGRMVTWriterDatasetCreate(const char *pszFilename, int nXSize,
    6422             :                                        int nYSize, int nBandsIn,
    6423             :                                        GDALDataType eDT,
    6424             :                                        CSLConstList papszOptions)
    6425             : {
    6426          75 :     return OGRMVTWriterDataset::Create(pszFilename, nXSize, nYSize, nBandsIn,
    6427          75 :                                        eDT, papszOptions);
    6428             : }
    6429             : 
    6430             : #endif  // HAVE_MVT_WRITE_SUPPORT
    6431             : 
    6432             : /************************************************************************/
    6433             : /*                           RegisterOGRMVT()                           */
    6434             : /************************************************************************/
    6435             : 
    6436        2059 : void RegisterOGRMVT()
    6437             : 
    6438             : {
    6439        2059 :     if (GDALGetDriverByName("MVT") != nullptr)
    6440         283 :         return;
    6441             : 
    6442        1776 :     GDALDriver *poDriver = new GDALDriver();
    6443             : 
    6444        1776 :     poDriver->SetDescription("MVT");
    6445        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    6446        1776 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Mapbox Vector Tiles");
    6447        1776 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/mvt.html");
    6448        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    6449        1776 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "mvt mvt.gz pbf");
    6450        1776 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
    6451             : 
    6452        1776 :     poDriver->SetMetadataItem(
    6453             :         GDAL_DMD_OPENOPTIONLIST,
    6454             :         "<OpenOptionList>"
    6455             :         "  <Option name='X' type='int' description='X coordinate of tile'/>"
    6456             :         "  <Option name='Y' type='int' description='Y coordinate of tile'/>"
    6457             :         "  <Option name='Z' type='int' description='Z coordinate of tile'/>"
    6458             :         //"  <Option name='@GEOREF_TOPX' type='float' description='X coordinate
    6459             :         // of top-left corner of tile'/>" "  <Option name='@GEOREF_TOPY'
    6460             :         // type='float' description='Y coordinate of top-left corner of tile'/>"
    6461             :         //"  <Option name='@GEOREF_TILEDIMX' type='float' description='Tile
    6462             :         // width in georeferenced units'/>" "  <Option name='@GEOREF_TILEDIMY'
    6463             :         // type='float' description='Tile height in georeferenced units'/>"
    6464             :         "  <Option name='METADATA_FILE' type='string' "
    6465             :         "description='Path to metadata.json'/>"
    6466             :         "  <Option name='CLIP' type='boolean' "
    6467             :         "description='Whether to clip geometries to tile extent' "
    6468             :         "default='YES'/>"
    6469             :         "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
    6470             :         "description="
    6471             :         "'For tilesets, extension of tiles'/>"
    6472             :         "  <Option name='TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN' type='int' "
    6473             :         "description="
    6474             :         "'For tilesets without metadata file, maximum number of tiles to use "
    6475             :         "to "
    6476             :         "establish the layer schemas' default='1000'/>"
    6477             :         "  <Option name='JSON_FIELD' type='boolean' description='For tilesets, "
    6478             :         "whether to put all attributes as a serialized JSon dictionary'/>"
    6479             :         "  <Option name='ADD_TILE_FIELDS' type='boolean' description='For "
    6480             :         "tilesets, "
    6481             :         "whether to add fields \"tile_z\", \"tile_x\", \"tile_y\" to each "
    6482             :         "layer, "
    6483             :         "containing the Z/X/Y coordinates of the tile from which the feature "
    6484             :         "originates.' "
    6485             :         "default='NO'/>"
    6486        1776 :         "</OpenOptionList>");
    6487             : 
    6488        1776 :     poDriver->pfnIdentify = OGRMVTDriverIdentify;
    6489        1776 :     poDriver->pfnOpen = OGRMVTDataset::Open;
    6490             : #ifdef HAVE_MVT_WRITE_SUPPORT
    6491        1776 :     poDriver->pfnCreate = OGRMVTWriterDataset::Create;
    6492        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    6493        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
    6494        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
    6495        1776 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
    6496        1776 :                               "Integer Integer64 Real String");
    6497        1776 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
    6498        1776 :                               "Boolean Float32");
    6499        1776 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
    6500             : 
    6501        1776 :     poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
    6502             : 
    6503        1776 :     poDriver->SetMetadataItem(
    6504             :         GDAL_DMD_CREATIONOPTIONLIST,
    6505             :         "<CreationOptionList>"
    6506             :         "  <Option name='NAME' type='string' description='Tileset name'/>"
    6507             :         "  <Option name='DESCRIPTION' type='string' "
    6508             :         "description='A description of the tileset'/>"
    6509             :         "  <Option name='TYPE' type='string-select' description='Layer type' "
    6510             :         "default='overlay'>"
    6511             :         "    <Value>overlay</Value>"
    6512             :         "    <Value>baselayer</Value>"
    6513             :         "  </Option>"
    6514             :         "  <Option name='FORMAT' type='string-select' description='Format'>"
    6515             :         "    <Value>DIRECTORY</Value>"
    6516             :         "    <Value>MBTILES</Value>"
    6517             :         "  </Option>"
    6518             :         "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
    6519             :         "description="
    6520             :         "'For tilesets as directories of files, extension of "
    6521             :         "tiles'/>" MVT_MBTILES_COMMON_DSCO
    6522             :         "  <Option name='BOUNDS' type='string' "
    6523             :         "description='Override default value for bounds metadata item'/>"
    6524             :         "  <Option name='CENTER' type='string' "
    6525             :         "description='Override default value for center metadata item'/>"
    6526             :         "  <Option name='TILING_SCHEME' type='string' "
    6527             :         "description='Custom tiling scheme with following format "
    6528             :         "\"EPSG:XXXX,tile_origin_upper_left_x,tile_origin_upper_left_y,"
    6529             :         "tile_dimension_zoom_0[,tile_matrix_width_zoom_0,tile_matrix_height_"
    6530             :         "zoom_0]\"'/>"
    6531        1776 :         "</CreationOptionList>");
    6532             : #endif  // HAVE_MVT_WRITE_SUPPORT
    6533             : 
    6534        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    6535             : 
    6536        1776 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    6537             : }

Generated by: LCOV version 1.14