LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mvt - ogrmvtdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2893 3047 94.9 %
Date: 2026-01-03 03:21:54 Functions: 104 106 98.1 %

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

Generated by: LCOV version 1.14