LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mvt - ogrmvtdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2852 3012 94.7 %
Date: 2025-10-25 23:36:32 Functions: 104 105 99.0 %

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

Generated by: LCOV version 1.14