LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mvt - ogrmvtdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2778 2936 94.6 %
Date: 2024-11-21 22:18:42 Functions: 103 104 99.0 %

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

Generated by: LCOV version 1.14