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

Generated by: LCOV version 1.14