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

Generated by: LCOV version 1.14