LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mvt - ogrmvtdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2879 3034 94.9 %
Date: 2025-11-16 02:02:06 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       54833 : static int OGRMVTDriverIdentify(GDALOpenInfo *poOpenInfo)
    1981             : 
    1982             : {
    1983       54833 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
    1984        1825 :         return TRUE;
    1985             : 
    1986       53008 :     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       53008 :     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       52286 :     if (poOpenInfo->nHeaderBytes <= 2)
    2066       49455 :         return FALSE;
    2067             : 
    2068             :     // GZip header ?
    2069        2831 :     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        2795 :     const GByte *pabyData = reinterpret_cast<GByte *>(poOpenInfo->pabyHeader);
    2087        2795 :     const GByte *const pabyDataStart = pabyData;
    2088             :     const GByte *pabyLayerStart;
    2089        2795 :     const GByte *const pabyDataLimit = pabyData + poOpenInfo->nHeaderBytes;
    2090        2795 :     const GByte *pabyLayerEnd = pabyDataLimit;
    2091        2795 :     int nKey = 0;
    2092        2795 :     unsigned int nLayerLength = 0;
    2093        2795 :     bool bLayerNameFound = false;
    2094        2795 :     bool bKeyFound = false;
    2095        2795 :     bool bFeatureFound = false;
    2096        2795 :     bool bVersionFound = false;
    2097             : 
    2098             :     try
    2099             :     {
    2100        2795 :         READ_FIELD_KEY(nKey);
    2101        2793 :         if (nKey != MAKE_KEY(knLAYER, WT_DATA))
    2102        2744 :             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           4 :                         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          10 :                                 std::unique_ptr<OGRLayer>(
    2791             :                                     new OGRMVTDirectoryLayer(
    2792           5 :                                         poDS, poTileLayer->GetName(),
    2793           5 :                                         poOpenInfo->pszFilename, oFields,
    2794          10 :                                         CPLJSONArray(), bJsonField, wkbUnknown,
    2795           5 :                                         nullptr)));
    2796           5 :                             poLayer = poDS->m_apoLayers.back().get();
    2797           5 :                             poLDefn = poLayer->GetLayerDefn();
    2798           5 :                             poLDefn->SetGeomType(eTileGeomType);
    2799             :                         }
    2800             :                         else
    2801             :                         {
    2802           4 :                             poLDefn = poLayer->GetLayerDefn();
    2803           4 :                             if (poLayer->GetGeomType() != eTileGeomType)
    2804             :                             {
    2805           0 :                                 poLDefn->SetGeomType(wkbUnknown);
    2806             :                             }
    2807             :                         }
    2808             : 
    2809           9 :                         if (!bJsonField)
    2810             :                         {
    2811          12 :                             for (int l = 1; l < poTileLDefn->GetFieldCount();
    2812             :                                  l++)
    2813             :                             {
    2814             :                                 OGRFieldDefn *poTileFDefn =
    2815           4 :                                     poTileLDefn->GetFieldDefn(l);
    2816           4 :                                 int nFieldIdx = poLDefn->GetFieldIndex(
    2817           4 :                                     poTileFDefn->GetNameRef());
    2818           4 :                                 if (nFieldIdx < 0)
    2819             :                                 {
    2820           1 :                                     poLDefn->AddFieldDefn(poTileFDefn);
    2821             :                                 }
    2822             :                                 else
    2823             :                                 {
    2824           6 :                                     MergeFieldDefn(
    2825           3 :                                         poLDefn->GetFieldDefn(nFieldIdx),
    2826             :                                         poTileFDefn->GetType(),
    2827             :                                         poTileFDefn->GetSubType());
    2828             :                                 }
    2829             :                             }
    2830             :                         }
    2831             :                     }
    2832           7 :                     nCountTiles++;
    2833             :                 }
    2834           1 :                 else if (!bTryToListDir)
    2835             :                 {
    2836           1 :                     nFailedAttempts++;
    2837             :                 }
    2838           8 :                 delete poTileDS;
    2839           8 :                 CSLDestroy(oOpenInfo.papszOpenOptions);
    2840             : 
    2841           8 :                 if (nFailedAttempts == 10)
    2842           0 :                     break;
    2843           8 :                 if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
    2844           0 :                     break;
    2845             :             }
    2846             : 
    2847           6 :             if (nFailedAttempts == 10)
    2848           0 :                 break;
    2849           6 :             if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
    2850           0 :                 break;
    2851             :         }
    2852           5 :         return poDS;
    2853             :     }
    2854             : 
    2855          36 :     CPLJSONArray oVectorLayers;
    2856          36 :     CPLJSONArray oTileStatLayers;
    2857          36 :     CPLJSONObject oBounds;
    2858             : 
    2859          18 :     OGRMVTDataset *poDS = new OGRMVTDataset(nullptr);
    2860             : 
    2861             :     CPLString osMetadataMemFilename =
    2862          36 :         VSIMemGenerateHiddenFilename("mvt_metadata.json");
    2863          18 :     if (!LoadMetadata(osMetadataFile, osMetadataContent, oVectorLayers,
    2864             :                       oTileStatLayers, oBounds, poDS->m_poSRS,
    2865          18 :                       poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
    2866          18 :                       poDS->m_dfTileDim0, poDS->m_nTileMatrixWidth0,
    2867          18 :                       poDS->m_nTileMatrixHeight0, osMetadataMemFilename))
    2868             :     {
    2869           0 :         delete poDS;
    2870           0 :         return nullptr;
    2871             :     }
    2872             : 
    2873          18 :     OGREnvelope sExtent;
    2874          18 :     bool bExtentValid = false;
    2875          18 :     if (oBounds.IsValid() && oBounds.GetType() == CPLJSONObject::Type::String)
    2876             :     {
    2877             :         CPLStringList aosTokens(
    2878          51 :             CSLTokenizeString2(oBounds.ToString().c_str(), ",", 0));
    2879          17 :         if (aosTokens.Count() == 4)
    2880             :         {
    2881          17 :             double dfX0 = CPLAtof(aosTokens[0]);
    2882          17 :             double dfY0 = CPLAtof(aosTokens[1]);
    2883          17 :             double dfX1 = CPLAtof(aosTokens[2]);
    2884          17 :             double dfY1 = CPLAtof(aosTokens[3]);
    2885          17 :             ConvertFromWGS84(poDS->m_poSRS, dfX0, dfY0, dfX1, dfY1);
    2886          17 :             bExtentValid = true;
    2887          17 :             sExtent.MinX = dfX0;
    2888          17 :             sExtent.MinY = dfY0;
    2889          17 :             sExtent.MaxX = dfX1;
    2890          17 :             sExtent.MaxY = dfY1;
    2891             :         }
    2892             :     }
    2893           2 :     else if (oBounds.IsValid() &&
    2894           1 :              oBounds.GetType() == CPLJSONObject::Type::Array)
    2895             :     {
    2896             :         // Cf https://free.tilehosting.com/data/v3.json?key=THE_KEY
    2897           2 :         CPLJSONArray oBoundArray = oBounds.ToArray();
    2898           1 :         if (oBoundArray.Size() == 4)
    2899             :         {
    2900           1 :             bExtentValid = true;
    2901           1 :             sExtent.MinX = oBoundArray[0].ToDouble();
    2902           1 :             sExtent.MinY = oBoundArray[1].ToDouble();
    2903           1 :             sExtent.MaxX = oBoundArray[2].ToDouble();
    2904           1 :             sExtent.MaxY = oBoundArray[3].ToDouble();
    2905           1 :             ConvertFromWGS84(poDS->m_poSRS, sExtent.MinX, sExtent.MinY,
    2906             :                              sExtent.MaxX, sExtent.MaxY);
    2907             :         }
    2908             :     }
    2909             : 
    2910          18 :     poDS->SetDescription(poOpenInfo->pszFilename);
    2911          18 :     poDS->m_bClip =
    2912          18 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
    2913          18 :     poDS->m_osTileExtension = std::move(osTileExtension);
    2914          18 :     poDS->m_osMetadataMemFilename = std::move(osMetadataMemFilename);
    2915          38 :     for (int i = 0; i < oVectorLayers.Size(); i++)
    2916             :     {
    2917          60 :         CPLJSONObject oId = oVectorLayers[i].GetObj("id");
    2918          20 :         if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
    2919             :         {
    2920          20 :             OGRwkbGeometryType eGeomType = wkbUnknown;
    2921          20 :             if (oTileStatLayers.IsValid())
    2922             :             {
    2923          19 :                 eGeomType = OGRMVTFindGeomTypeFromTileStat(
    2924          38 :                     oTileStatLayers, oId.ToString().c_str());
    2925             :             }
    2926             : 
    2927          60 :             CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
    2928             :             CPLJSONArray oAttributesFromTileStats =
    2929             :                 OGRMVTFindAttributesFromTileStat(oTileStatLayers,
    2930          40 :                                                  oId.ToString().c_str());
    2931             : 
    2932          20 :             poDS->m_apoLayers.push_back(
    2933          40 :                 std::unique_ptr<OGRLayer>(new OGRMVTDirectoryLayer(
    2934          40 :                     poDS, oId.ToString().c_str(), poOpenInfo->pszFilename,
    2935             :                     oFields, oAttributesFromTileStats, bJsonField, eGeomType,
    2936          20 :                     (bExtentValid) ? &sExtent : nullptr)));
    2937             :         }
    2938             :     }
    2939             : 
    2940          18 :     return poDS;
    2941             : }
    2942             : 
    2943             : /************************************************************************/
    2944             : /*                                Open()                                */
    2945             : /************************************************************************/
    2946             : 
    2947         906 : GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo)
    2948             : {
    2949         906 :     return Open(poOpenInfo, true);
    2950             : }
    2951             : 
    2952        1001 : GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo, bool bRecurseAllowed)
    2953             : 
    2954             : {
    2955        1001 :     if (!OGRMVTDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
    2956           0 :         return nullptr;
    2957             : 
    2958        1001 :     VSILFILE *fp = poOpenInfo->fpL;
    2959        2002 :     CPLString osFilename(poOpenInfo->pszFilename);
    2960        1001 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
    2961             :     {
    2962         960 :         osFilename = poOpenInfo->pszFilename + strlen("MVT:");
    2963        1920 :         if (STARTS_WITH(osFilename, "/vsigzip/http://") ||
    2964         960 :             STARTS_WITH(osFilename, "/vsigzip/https://"))
    2965             :         {
    2966           0 :             osFilename = osFilename.substr(strlen("/vsigzip/"));
    2967             :         }
    2968             : 
    2969             :         // If the filename has no extension and is a directory, consider
    2970             :         // we open a directory
    2971             :         VSIStatBufL sStat;
    2972         865 :         if (bRecurseAllowed && !STARTS_WITH(osFilename, "/vsigzip/") &&
    2973         864 :             strchr((CPLGetFilename(osFilename)), '.') == nullptr &&
    2974        1825 :             VSIStatL(osFilename, &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
    2975             :         {
    2976           3 :             GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
    2977           3 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
    2978           3 :             GDALDataset *poDS = OpenDirectory(&oOpenInfo);
    2979           3 :             if (poDS)
    2980           1 :                 poDS->SetDescription(poOpenInfo->pszFilename);
    2981           3 :             return poDS;
    2982             :         }
    2983             : 
    2984             :         // For a network resource, if the filename is an integer, consider it
    2985             :         // is a directory and open as such
    2986        1819 :         if (bRecurseAllowed &&
    2987         862 :             (STARTS_WITH(osFilename, "/vsicurl") ||
    2988         862 :              STARTS_WITH(osFilename, "http://") ||
    2989        1819 :              STARTS_WITH(osFilename, "https://")) &&
    2990           6 :             CPLGetValueType(CPLGetFilename(osFilename)) == CPL_VALUE_INTEGER)
    2991             :         {
    2992           5 :             GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
    2993           5 :             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
    2994           5 :             GDALDataset *poDS = OpenDirectory(&oOpenInfo);
    2995           5 :             if (poDS)
    2996           4 :                 poDS->SetDescription(poOpenInfo->pszFilename);
    2997           5 :             return poDS;
    2998             :         }
    2999             : 
    3000        1894 :         if (!STARTS_WITH(osFilename, "http://") &&
    3001         942 :             !STARTS_WITH(osFilename, "https://"))
    3002             :         {
    3003             :             CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES",
    3004        1884 :                                           "NO", false);
    3005             :             CPLConfigOptionSetter oSetter2("CPL_VSIL_GZIP_SAVE_INFO", "NO",
    3006        1884 :                                            false);
    3007         942 :             fp = VSIFOpenL(osFilename, "rb");
    3008             :             // Is it a gzipped file ?
    3009         942 :             if (fp && !STARTS_WITH(osFilename, "/vsigzip/"))
    3010             :             {
    3011         940 :                 GByte abyHeaderBytes[2] = {0, 0};
    3012         940 :                 VSIFReadL(abyHeaderBytes, 2, 1, fp);
    3013         940 :                 if (abyHeaderBytes[0] == 0x1F && abyHeaderBytes[1] == 0x8B)
    3014             :                 {
    3015         404 :                     VSIFCloseL(fp);
    3016         404 :                     fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
    3017             :                 }
    3018             :             }
    3019             :         }
    3020             :     }
    3021          82 :     else if (bRecurseAllowed &&
    3022          41 :              (poOpenInfo->bIsDirectory ||
    3023          24 :               (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl") &&
    3024           0 :                CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
    3025             :                    CPL_VALUE_INTEGER)))
    3026             :     {
    3027          17 :         return OpenDirectory(poOpenInfo);
    3028             :     }
    3029             :     // Is it a gzipped file ?
    3030          24 :     else if (poOpenInfo->nHeaderBytes >= 2 &&
    3031          24 :              poOpenInfo->pabyHeader[0] == 0x1F &&
    3032          18 :              poOpenInfo->pabyHeader[1] == 0x8B)
    3033             :     {
    3034             :         CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
    3035          18 :                                       false);
    3036          18 :         fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
    3037             :     }
    3038             :     else
    3039             :     {
    3040           6 :         poOpenInfo->fpL = nullptr;
    3041             :     }
    3042         977 :     if (fp == nullptr && !STARTS_WITH(osFilename, "http://") &&
    3043           1 :         !STARTS_WITH(osFilename, "https://"))
    3044             :     {
    3045           1 :         return nullptr;
    3046             :     }
    3047             : 
    3048        1950 :     CPLString osY = CPLGetBasenameSafe(osFilename);
    3049        2925 :     CPLString osX = CPLGetBasenameSafe(CPLGetPathSafe(osFilename).c_str());
    3050        1950 :     CPLString osZ = CPLGetBasenameSafe(
    3051        3900 :         CPLGetPathSafe(CPLGetPathSafe(osFilename).c_str()).c_str());
    3052         975 :     size_t nPos = osY.find('.');
    3053         975 :     if (nPos != std::string::npos)
    3054           0 :         osY.resize(nPos);
    3055             : 
    3056        1950 :     CPLString osMetadataFile;
    3057         975 :     if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE"))
    3058             :     {
    3059             :         osMetadataFile =
    3060         953 :             CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
    3061             :     }
    3062          22 :     else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
    3063          34 :              CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
    3064          12 :              CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
    3065             :     {
    3066          12 :         osMetadataFile = CPLFormFilenameSafe(
    3067          24 :             CPLGetPathSafe(
    3068          24 :                 CPLGetPathSafe(CPLGetPathSafe(osFilename).c_str()).c_str())
    3069             :                 .c_str(),
    3070          12 :             "metadata.json", nullptr);
    3071          12 :         if (osMetadataFile.find("/vsigzip/") == 0)
    3072             :         {
    3073           2 :             osMetadataFile = osMetadataFile.substr(strlen("/vsigzip/"));
    3074             :         }
    3075             :         VSIStatBufL sStat;
    3076          12 :         if (osMetadataFile.empty() || VSIStatL(osMetadataFile, &sStat) != 0)
    3077             :         {
    3078           1 :             osMetadataFile.clear();
    3079             :         }
    3080             :     }
    3081             : 
    3082         975 :     if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X") &&
    3083        1804 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y") &&
    3084         829 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z"))
    3085             :     {
    3086         829 :         osX = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X");
    3087         829 :         osY = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y");
    3088         829 :         osZ = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z");
    3089             :     }
    3090             : 
    3091             :     GByte *pabyDataMod;
    3092             :     size_t nFileSize;
    3093             : 
    3094         975 :     if (fp == nullptr)
    3095             :     {
    3096             :         bool bSilenceErrors =
    3097          10 :             CPLFetchBool(poOpenInfo->papszOpenOptions,
    3098             :                          "DO_NOT_ERROR_ON_MISSING_TILE", false);
    3099          10 :         if (bSilenceErrors)
    3100           9 :             CPLPushErrorHandler(CPLQuietErrorHandler);
    3101          10 :         CPLHTTPResult *psResult = CPLHTTPFetch(osFilename, nullptr);
    3102          10 :         if (bSilenceErrors)
    3103           9 :             CPLPopErrorHandler();
    3104          10 :         if (psResult == nullptr)
    3105           0 :             return nullptr;
    3106          10 :         if (psResult->pszErrBuf != nullptr)
    3107             :         {
    3108           5 :             CPLHTTPDestroyResult(psResult);
    3109           5 :             return nullptr;
    3110             :         }
    3111           5 :         pabyDataMod = psResult->pabyData;
    3112           5 :         if (pabyDataMod == nullptr)
    3113             :         {
    3114           0 :             CPLHTTPDestroyResult(psResult);
    3115           0 :             return nullptr;
    3116             :         }
    3117           5 :         nFileSize = psResult->nDataLen;
    3118           5 :         psResult->pabyData = nullptr;
    3119           5 :         CPLHTTPDestroyResult(psResult);
    3120             : 
    3121             :         // zlib decompress if needed
    3122           5 :         if (nFileSize > 2 && pabyDataMod[0] == 0x1F && pabyDataMod[1] == 0x8B)
    3123             :         {
    3124           5 :             size_t nOutBytes = 0;
    3125             :             void *pUncompressed =
    3126           5 :                 CPLZLibInflate(pabyDataMod, nFileSize, nullptr, 0, &nOutBytes);
    3127           5 :             CPLFree(pabyDataMod);
    3128           5 :             if (pUncompressed == nullptr)
    3129             :             {
    3130           0 :                 return nullptr;
    3131             :             }
    3132           5 :             pabyDataMod = static_cast<GByte *>(pUncompressed);
    3133           5 :             nFileSize = nOutBytes;
    3134             :         }
    3135             :     }
    3136             :     else
    3137             :     {
    3138             :         // Check file size and ingest into memory
    3139         965 :         VSIFSeekL(fp, 0, SEEK_END);
    3140         965 :         vsi_l_offset nFileSizeL = VSIFTellL(fp);
    3141         965 :         if (nFileSizeL > 10 * 1024 * 1024)
    3142             :         {
    3143           0 :             VSIFCloseL(fp);
    3144           0 :             return nullptr;
    3145             :         }
    3146         965 :         nFileSize = static_cast<size_t>(nFileSizeL);
    3147         965 :         pabyDataMod = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nFileSize + 1));
    3148         965 :         if (pabyDataMod == nullptr)
    3149             :         {
    3150           0 :             VSIFCloseL(fp);
    3151           0 :             return nullptr;
    3152             :         }
    3153         965 :         VSIFSeekL(fp, 0, SEEK_SET);
    3154         965 :         VSIFReadL(pabyDataMod, 1, nFileSize, fp);
    3155         965 :         pabyDataMod[nFileSize] = 0;
    3156         965 :         VSIFCloseL(fp);
    3157             :     }
    3158             : 
    3159         970 :     const GByte *pabyData = pabyDataMod;
    3160             : 
    3161             :     // First scan to browse through layers
    3162         970 :     const GByte *pabyDataLimit = pabyData + nFileSize;
    3163         970 :     int nKey = 0;
    3164         970 :     OGRMVTDataset *poDS = new OGRMVTDataset(pabyDataMod);
    3165         970 :     poDS->SetDescription(poOpenInfo->pszFilename);
    3166        1940 :     poDS->m_bClip =
    3167         970 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
    3168             : 
    3169        1933 :     if (!(CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
    3170         963 :           CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
    3171         938 :           CPLGetValueType(osZ) == CPL_VALUE_INTEGER))
    3172             :     {
    3173             :         // See
    3174             :         // https://github.com/mapbox/mvt-fixtures/tree/master/real-world/compressed
    3175          32 :         int nX = 0;
    3176          32 :         int nY = 0;
    3177          32 :         int nZ = 0;
    3178             :         CPLString osBasename(
    3179          96 :             CPLGetBasenameSafe(CPLGetBasenameSafe(osFilename).c_str()));
    3180          63 :         if (sscanf(osBasename, "%d-%d-%d", &nZ, &nX, &nY) == 3 ||
    3181          31 :             sscanf(osBasename, "%d_%d_%d", &nZ, &nX, &nY) == 3)
    3182             :         {
    3183           1 :             osX = CPLSPrintf("%d", nX);
    3184           1 :             osY = CPLSPrintf("%d", nY);
    3185           1 :             osZ = CPLSPrintf("%d", nZ);
    3186             :         }
    3187             :     }
    3188             : 
    3189        1940 :     CPLJSONArray oVectorLayers;
    3190         970 :     oVectorLayers.Deinit();
    3191             : 
    3192        1940 :     CPLJSONArray oTileStatLayers;
    3193         970 :     oTileStatLayers.Deinit();
    3194             : 
    3195         970 :     if (!osMetadataFile.empty())
    3196             :     {
    3197         912 :         CPLJSONObject oBounds;
    3198         912 :         LoadMetadata(osMetadataFile, CPLString(), oVectorLayers,
    3199             :                      oTileStatLayers, oBounds, poDS->m_poSRS,
    3200         912 :                      poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
    3201         912 :                      poDS->m_dfTileDim0, poDS->m_nTileMatrixWidth0,
    3202        1824 :                      poDS->m_nTileMatrixHeight0, CPLString());
    3203             :     }
    3204             : 
    3205             :     const char *pszGeorefTopX =
    3206         970 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPX");
    3207             :     const char *pszGeorefTopY =
    3208         970 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPY");
    3209             :     const char *pszGeorefTileDimX =
    3210         970 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMX");
    3211             :     const char *pszGeorefTileDimY =
    3212         970 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMY");
    3213         970 :     if (pszGeorefTopX && pszGeorefTopY && pszGeorefTileDimX &&
    3214             :         pszGeorefTileDimY)
    3215             :     {
    3216           3 :         poDS->m_bGeoreferenced = true;
    3217           3 :         poDS->m_dfTileDimX = CPLAtof(pszGeorefTileDimX);
    3218           3 :         poDS->m_dfTileDimY = CPLAtof(pszGeorefTileDimY);
    3219           3 :         poDS->m_dfTopX = CPLAtof(pszGeorefTopX);
    3220           3 :         poDS->m_dfTopY = CPLAtof(pszGeorefTopY);
    3221           3 :         poDS->m_poSRS->Release();
    3222           3 :         poDS->m_poSRS = nullptr;
    3223             :     }
    3224         967 :     else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
    3225        1906 :              CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
    3226         939 :              CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
    3227             :     {
    3228         939 :         int nX = atoi(osX);
    3229         939 :         int nY = atoi(osY);
    3230         939 :         int nZ = atoi(osZ);
    3231         939 :         if (nZ >= 0 && nZ < 30 && nX >= 0 &&
    3232         939 :             nX < (static_cast<int64_t>(1) << nZ) * poDS->m_nTileMatrixWidth0 &&
    3233         937 :             nY >= 0 &&
    3234         937 :             nY < (static_cast<int64_t>(1) << nZ) * poDS->m_nTileMatrixHeight0)
    3235             :         {
    3236         937 :             poDS->m_bGeoreferenced = true;
    3237         937 :             poDS->m_dfTileDimX = poDS->m_dfTileDim0 / (1 << nZ);
    3238         937 :             poDS->m_dfTileDimY = poDS->m_dfTileDimX;
    3239         937 :             poDS->m_dfTopX = poDS->m_dfTopXOrigin + nX * poDS->m_dfTileDimX;
    3240         937 :             poDS->m_dfTopY = poDS->m_dfTopYOrigin - nY * poDS->m_dfTileDimY;
    3241             :         }
    3242             :     }
    3243             : 
    3244             :     try
    3245             :     {
    3246        2023 :         while (pabyData < pabyDataLimit)
    3247             :         {
    3248        1054 :             READ_FIELD_KEY(nKey);
    3249        1054 :             if (nKey == MAKE_KEY(knLAYER, WT_DATA))
    3250             :             {
    3251        1053 :                 unsigned int nLayerSize = 0;
    3252        1053 :                 READ_SIZE(pabyData, pabyDataLimit, nLayerSize);
    3253        1053 :                 const GByte *pabyDataLayer = pabyData;
    3254        1053 :                 const GByte *pabyDataLimitLayer = pabyData + nLayerSize;
    3255        1301 :                 while (pabyData < pabyDataLimitLayer)
    3256             :                 {
    3257        1301 :                     READ_VARINT32(pabyData, pabyDataLimitLayer, nKey);
    3258        1301 :                     if (nKey == MAKE_KEY(knLAYER_NAME, WT_DATA))
    3259             :                     {
    3260        1053 :                         char *pszLayerName = nullptr;
    3261        1053 :                         READ_TEXT(pabyData, pabyDataLimitLayer, pszLayerName);
    3262             : 
    3263        2106 :                         CPLJSONObject oFields;
    3264        1053 :                         oFields.Deinit();
    3265        1053 :                         if (oVectorLayers.IsValid())
    3266             :                         {
    3267        1098 :                             for (int i = 0; i < oVectorLayers.Size(); i++)
    3268             :                             {
    3269             :                                 CPLJSONObject oId =
    3270        2196 :                                     oVectorLayers[i].GetObj("id");
    3271        2196 :                                 if (oId.IsValid() &&
    3272        1098 :                                     oId.GetType() ==
    3273             :                                         CPLJSONObject::Type::String)
    3274             :                                 {
    3275        1098 :                                     if (oId.ToString() == pszLayerName)
    3276             :                                     {
    3277             :                                         oFields =
    3278         969 :                                             oVectorLayers[i].GetObj("fields");
    3279         969 :                                         break;
    3280             :                                     }
    3281             :                                 }
    3282             :                             }
    3283             :                         }
    3284             : 
    3285        1053 :                         OGRwkbGeometryType eGeomType = wkbUnknown;
    3286        1053 :                         if (oTileStatLayers.IsValid())
    3287             :                         {
    3288         566 :                             eGeomType = OGRMVTFindGeomTypeFromTileStat(
    3289             :                                 oTileStatLayers, pszLayerName);
    3290             :                         }
    3291             :                         CPLJSONArray oAttributesFromTileStats =
    3292             :                             OGRMVTFindAttributesFromTileStat(oTileStatLayers,
    3293        2106 :                                                              pszLayerName);
    3294             : 
    3295        1053 :                         poDS->m_apoLayers.push_back(
    3296        2106 :                             std::make_unique<OGRMVTLayer>(
    3297             :                                 poDS, pszLayerName, pabyDataLayer, nLayerSize,
    3298             :                                 oFields, oAttributesFromTileStats, eGeomType));
    3299        1053 :                         CPLFree(pszLayerName);
    3300        1053 :                         break;
    3301             :                     }
    3302             :                     else
    3303             :                     {
    3304         248 :                         SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimitLayer, FALSE);
    3305             :                     }
    3306             :                 }
    3307        1053 :                 pabyData = pabyDataLimitLayer;
    3308             :             }
    3309             :             else
    3310             :             {
    3311           1 :                 if (nKey == 0 && !poDS->m_apoLayers.empty())
    3312             :                 {
    3313             :                     // File attached to https://github.com/OSGeo/gdal/issues/13268
    3314             :                     // has 0-byte padding after the layer definition.
    3315           1 :                     break;
    3316             :                 }
    3317           0 :                 SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
    3318             :             }
    3319             :         }
    3320             : 
    3321         970 :         return poDS;
    3322             :     }
    3323           0 :     catch (const GPBException &e)
    3324             :     {
    3325           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
    3326           0 :         delete poDS;
    3327           0 :         return nullptr;
    3328             :     }
    3329             : }
    3330             : 
    3331             : #ifdef HAVE_MVT_WRITE_SUPPORT
    3332             : 
    3333             : /************************************************************************/
    3334             : /*                           OGRMVTWriterDataset                        */
    3335             : /************************************************************************/
    3336             : 
    3337             : class OGRMVTWriterLayer;
    3338             : 
    3339             : struct OGRMVTFeatureContent
    3340             : {
    3341             :     std::vector<std::pair<std::string, MVTTileLayerValue>> oValues;
    3342             :     GIntBig nFID;
    3343             : };
    3344             : 
    3345             : class OGRMVTWriterDataset final : public GDALDataset
    3346             : {
    3347             :     class MVTFieldProperties
    3348             :     {
    3349             :       public:
    3350             :         CPLString m_osName;
    3351             :         std::set<MVTTileLayerValue> m_oSetValues;
    3352             :         std::set<MVTTileLayerValue> m_oSetAllValues;
    3353             :         double m_dfMinVal = 0;
    3354             :         double m_dfMaxVal = 0;
    3355             :         bool m_bAllInt = false;
    3356             :         MVTTileLayerValue::ValueType m_eType =
    3357             :             MVTTileLayerValue::ValueType::NONE;
    3358             :     };
    3359             : 
    3360             :     class MVTLayerProperties
    3361             :     {
    3362             :       public:
    3363             :         int m_nMinZoom = 0;
    3364             :         int m_nMaxZoom = 0;
    3365             :         std::map<MVTTileLayerFeature::GeomType, GIntBig> m_oCountGeomType;
    3366             :         std::map<CPLString, size_t> m_oMapFieldNameToIdx;
    3367             :         std::vector<MVTFieldProperties> m_aoFields;
    3368             :         std::set<CPLString> m_oSetFields;
    3369             :     };
    3370             : 
    3371             :     std::vector<std::unique_ptr<OGRMVTWriterLayer>> m_apoLayers;
    3372             :     CPLString m_osTempDB;
    3373             :     mutable std::mutex m_oDBMutex;
    3374             :     mutable bool m_bWriteFeatureError = false;
    3375             :     sqlite3_vfs *m_pMyVFS = nullptr;
    3376             :     sqlite3 *m_hDB = nullptr;
    3377             :     sqlite3_stmt *m_hInsertStmt = nullptr;
    3378             :     int m_nMinZoom = 0;
    3379             :     int m_nMaxZoom = 5;
    3380             :     double m_dfSimplification = 0.0;
    3381             :     double m_dfSimplificationMaxZoom = 0.0;
    3382             :     CPLJSONDocument m_oConf;
    3383             :     unsigned m_nExtent = knDEFAULT_EXTENT;
    3384             :     int m_nMetadataVersion = 2;
    3385             :     int m_nMVTVersion = 2;
    3386             :     int m_nBuffer = 5 * knDEFAULT_EXTENT / 256;
    3387             :     bool m_bGZip = true;
    3388             :     mutable CPLWorkerThreadPool m_oThreadPool;
    3389             :     bool m_bThreadPoolOK = false;
    3390             :     mutable GIntBig m_nTempTiles = 0;
    3391             :     CPLString m_osName;
    3392             :     CPLString m_osDescription;
    3393             :     CPLString m_osType{"overlay"};
    3394             :     sqlite3 *m_hDBMBTILES = nullptr;
    3395             :     OGREnvelope m_oEnvelope;
    3396             :     bool m_bMaxTileSizeOptSpecified = false;
    3397             :     bool m_bMaxFeaturesOptSpecified = false;
    3398             :     unsigned m_nMaxTileSize = 500000;
    3399             :     unsigned m_nMaxFeatures = 200000;
    3400             :     std::map<std::string, std::string> m_oMapLayerNameToDesc;
    3401             :     std::map<std::string, GIntBig> m_oMapLayerNameToFeatureCount;
    3402             :     CPLString m_osBounds;
    3403             :     CPLString m_osCenter;
    3404             :     CPLString m_osExtension{"pbf"};
    3405             :     OGRSpatialReference *m_poSRS = nullptr;
    3406             :     double m_dfTopX = 0.0;
    3407             :     double m_dfTopY = 0.0;
    3408             :     double m_dfTileDim0 = 0.0;
    3409             :     int m_nTileMatrixWidth0 =
    3410             :         1;  // Number of tiles along X axis at zoom level 0
    3411             :     int m_nTileMatrixHeight0 =
    3412             :         1;  // Number of tiles along Y axis at zoom level 0
    3413             :     bool m_bReuseTempFile = false;  // debug only
    3414             : 
    3415             :     OGRErr PreGenerateForTile(
    3416             :         int nZ, int nX, int nY, const CPLString &osTargetName,
    3417             :         bool bIsMaxZoomForLayer,
    3418             :         const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
    3419             :         GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
    3420             :         const OGREnvelope &sEnvelope) const;
    3421             : 
    3422             :     static void WriterTaskFunc(void *pParam);
    3423             : 
    3424             :     OGRErr PreGenerateForTileReal(int nZ, int nX, int nY,
    3425             :                                   const CPLString &osTargetName,
    3426             :                                   bool bIsMaxZoomForLayer,
    3427             :                                   const OGRMVTFeatureContent *poFeatureContent,
    3428             :                                   GIntBig nSerial, const OGRGeometry *poGeom,
    3429             :                                   const OGREnvelope &sEnvelope) const;
    3430             : 
    3431             :     void ConvertToTileCoords(double dfX, double dfY, int &nX, int &nY,
    3432             :                              double dfTopX, double dfTopY,
    3433             :                              double dfTileDim) const;
    3434             : 
    3435             :     enum class ExpectedWindingOrder
    3436             :     {
    3437             :         NONE,
    3438             :         CLOCKWISE,
    3439             :         COUNTERCLOCKWISE,
    3440             :     };
    3441             :     bool EncodeLineString(MVTTileLayerFeature *poGPBFeature,
    3442             :                           const OGRLineString *poLS, OGRLineString *poOutLS,
    3443             :                           bool bWriteLastPoint,
    3444             :                           ExpectedWindingOrder eExpectedWindingOrder,
    3445             :                           GUInt32 nMinLineTo, double dfTopX, double dfTopY,
    3446             :                           double dfTileDim, int &nLastX, int &nLastY) const;
    3447             :     bool EncodePolygon(MVTTileLayerFeature *poGPBFeature,
    3448             :                        const OGRPolygon *poPoly, OGRPolygon *poOutPoly,
    3449             :                        double dfTopX, double dfTopY, double dfTileDim,
    3450             :                        int &nLastX, int &nLastY, double &dfArea) const;
    3451             : #ifdef notdef
    3452             :     bool EncodeRepairedOuterRing(MVTTileLayerFeature *poGPBFeature,
    3453             :                                  OGRPolygon &oOutPoly, int &nLastX,
    3454             :                                  int &nLastY) const;
    3455             : #endif
    3456             : 
    3457             :     static void UpdateLayerProperties(MVTLayerProperties *poLayerProperties,
    3458             :                                       const std::string &osKey,
    3459             :                                       const MVTTileLayerValue &oValue);
    3460             : 
    3461             :     void EncodeFeature(const void *pabyBlob, int nBlobSize,
    3462             :                        std::shared_ptr<MVTTileLayer> &poTargetLayer,
    3463             :                        std::map<CPLString, GUInt32> &oMapKeyToIdx,
    3464             :                        std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
    3465             :                        MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
    3466             :                        unsigned &nFeaturesInTile);
    3467             : 
    3468             :     std::string
    3469             :     EncodeTile(int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer,
    3470             :                sqlite3_stmt *hStmtRows,
    3471             :                std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
    3472             :                std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead);
    3473             : 
    3474             :     std::string RecodeTileLowerResolution(int nZ, int nX, int nY, int nExtent,
    3475             :                                           sqlite3_stmt *hStmtLayer,
    3476             :                                           sqlite3_stmt *hStmtRows);
    3477             : 
    3478             :     bool CreateOutput();
    3479             : 
    3480             :     bool GenerateMetadata(size_t nLayers,
    3481             :                           const std::map<CPLString, MVTLayerProperties> &oMap);
    3482             : 
    3483             :   public:
    3484             :     OGRMVTWriterDataset();
    3485             :     ~OGRMVTWriterDataset() override;
    3486             : 
    3487             :     CPLErr Close() override;
    3488             : 
    3489             :     OGRLayer *ICreateLayer(const char *pszName,
    3490             :                            const OGRGeomFieldDefn *poGeomFieldDefn,
    3491             :                            CSLConstList papszOptions) override;
    3492             : 
    3493             :     int TestCapability(const char *) const override;
    3494             : 
    3495             :     OGRErr WriteFeature(OGRMVTWriterLayer *poLayer, OGRFeature *poFeature,
    3496             :                         GIntBig nSerial, OGRGeometry *poGeom);
    3497             : 
    3498             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
    3499             :                                int nBandsIn, GDALDataType eDT,
    3500             :                                char **papszOptions);
    3501             : 
    3502         182 :     OGRSpatialReference *GetSRS()
    3503             :     {
    3504         182 :         return m_poSRS;
    3505             :     }
    3506             : };
    3507             : 
    3508             : /************************************************************************/
    3509             : /*                           OGRMVTWriterLayer                          */
    3510             : /************************************************************************/
    3511             : 
    3512             : class OGRMVTWriterLayer final : public OGRLayer
    3513             : {
    3514             :     friend class OGRMVTWriterDataset;
    3515             : 
    3516             :     OGRMVTWriterDataset *m_poDS = nullptr;
    3517             :     OGRFeatureDefn *m_poFeatureDefn = nullptr;
    3518             :     OGRCoordinateTransformation *m_poCT = nullptr;
    3519             :     GIntBig m_nSerial = 0;
    3520             :     int m_nMinZoom = 0;
    3521             :     int m_nMaxZoom = 5;
    3522             :     CPLString m_osTargetName;
    3523             : 
    3524             :   public:
    3525             :     OGRMVTWriterLayer(OGRMVTWriterDataset *poDS, const char *pszLayerName,
    3526             :                       OGRSpatialReference *poSRS);
    3527             :     ~OGRMVTWriterLayer() override;
    3528             : 
    3529          48 :     void ResetReading() override
    3530             :     {
    3531          48 :     }
    3532             : 
    3533          48 :     OGRFeature *GetNextFeature() override
    3534             :     {
    3535          48 :         return nullptr;
    3536             :     }
    3537             : 
    3538        1441 :     const OGRFeatureDefn *GetLayerDefn() const override
    3539             :     {
    3540        1441 :         return m_poFeatureDefn;
    3541             :     }
    3542             : 
    3543             :     int TestCapability(const char *) const override;
    3544             :     OGRErr ICreateFeature(OGRFeature *) override;
    3545             :     OGRErr CreateField(const OGRFieldDefn *, int) override;
    3546             : 
    3547          49 :     GDALDataset *GetDataset() override
    3548             :     {
    3549          49 :         return m_poDS;
    3550             :     }
    3551             : };
    3552             : 
    3553             : /************************************************************************/
    3554             : /*                          OGRMVTWriterLayer()                         */
    3555             : /************************************************************************/
    3556             : 
    3557         171 : OGRMVTWriterLayer::OGRMVTWriterLayer(OGRMVTWriterDataset *poDS,
    3558             :                                      const char *pszLayerName,
    3559         171 :                                      OGRSpatialReference *poSRSIn)
    3560             : {
    3561         171 :     m_poDS = poDS;
    3562         171 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
    3563         171 :     SetDescription(m_poFeatureDefn->GetName());
    3564         171 :     m_poFeatureDefn->Reference();
    3565             : 
    3566         171 :     m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
    3567             : 
    3568         171 :     if (poSRSIn != nullptr && !poDS->GetSRS()->IsSame(poSRSIn))
    3569             :     {
    3570           3 :         m_poCT = OGRCreateCoordinateTransformation(poSRSIn, poDS->GetSRS());
    3571           3 :         if (m_poCT == nullptr)
    3572             :         {
    3573             :             // If we can't create a transformation, issue a warning - but
    3574             :             // continue the transformation.
    3575           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    3576             :                      "Failed to create coordinate transformation between the "
    3577             :                      "input and target coordinate systems.");
    3578             :         }
    3579             :     }
    3580         171 : }
    3581             : 
    3582             : /************************************************************************/
    3583             : /*                          ~OGRMVTWriterLayer()                        */
    3584             : /************************************************************************/
    3585             : 
    3586         342 : OGRMVTWriterLayer::~OGRMVTWriterLayer()
    3587             : {
    3588         171 :     m_poFeatureDefn->Release();
    3589         171 :     delete m_poCT;
    3590         342 : }
    3591             : 
    3592             : /************************************************************************/
    3593             : /*                            TestCapability()                          */
    3594             : /************************************************************************/
    3595             : 
    3596         359 : int OGRMVTWriterLayer::TestCapability(const char *pszCap) const
    3597             : {
    3598             : 
    3599         359 :     if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCCreateField))
    3600          96 :         return true;
    3601         263 :     return false;
    3602             : }
    3603             : 
    3604             : /************************************************************************/
    3605             : /*                            CreateField()                             */
    3606             : /************************************************************************/
    3607             : 
    3608         313 : OGRErr OGRMVTWriterLayer::CreateField(const OGRFieldDefn *poFieldDefn, int)
    3609             : {
    3610         313 :     m_poFeatureDefn->AddFieldDefn(poFieldDefn);
    3611         313 :     return OGRERR_NONE;
    3612             : }
    3613             : 
    3614             : /************************************************************************/
    3615             : /*                            ICreateFeature()                          */
    3616             : /************************************************************************/
    3617             : 
    3618         295 : OGRErr OGRMVTWriterLayer::ICreateFeature(OGRFeature *poFeature)
    3619             : {
    3620         295 :     OGRGeometry *poGeom = poFeature->GetGeometryRef();
    3621         295 :     if (poGeom == nullptr || poGeom->IsEmpty())
    3622         102 :         return OGRERR_NONE;
    3623         193 :     if (m_poCT)
    3624             :     {
    3625           2 :         poGeom->transform(m_poCT);
    3626             :     }
    3627         193 :     m_nSerial++;
    3628         193 :     return m_poDS->WriteFeature(this, poFeature, m_nSerial, poGeom);
    3629             : }
    3630             : 
    3631             : /************************************************************************/
    3632             : /*                          OGRMVTWriterDataset()                       */
    3633             : /************************************************************************/
    3634             : 
    3635         133 : OGRMVTWriterDataset::OGRMVTWriterDataset()
    3636             : {
    3637             :     // Default WebMercator tiling scheme
    3638         133 :     m_poSRS = new OGRSpatialReference();
    3639         133 :     m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    3640             : 
    3641         133 :     InitWebMercatorTilingScheme(m_poSRS, m_dfTopX, m_dfTopY, m_dfTileDim0);
    3642         133 : }
    3643             : 
    3644             : /************************************************************************/
    3645             : /*                         ~OGRMVTWriterDataset()                       */
    3646             : /************************************************************************/
    3647             : 
    3648         266 : OGRMVTWriterDataset::~OGRMVTWriterDataset()
    3649             : {
    3650         133 :     OGRMVTWriterDataset::Close();
    3651             : 
    3652         133 :     if (m_pMyVFS)
    3653             :     {
    3654         133 :         sqlite3_vfs_unregister(m_pMyVFS);
    3655         133 :         CPLFree(m_pMyVFS->pAppData);
    3656         133 :         CPLFree(m_pMyVFS);
    3657             :     }
    3658             : 
    3659         133 :     m_poSRS->Release();
    3660         266 : }
    3661             : 
    3662             : /************************************************************************/
    3663             : /*                              Close()                                 */
    3664             : /************************************************************************/
    3665             : 
    3666         256 : CPLErr OGRMVTWriterDataset::Close()
    3667             : {
    3668         256 :     CPLErr eErr = CE_None;
    3669         256 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    3670             :     {
    3671         133 :         if (GetDescription()[0] != '\0')
    3672             :         {
    3673         123 :             if (!CreateOutput())
    3674           3 :                 eErr = CE_Failure;
    3675             :         }
    3676         133 :         if (m_hInsertStmt != nullptr)
    3677             :         {
    3678         130 :             sqlite3_finalize(m_hInsertStmt);
    3679             :         }
    3680         133 :         if (m_hDB)
    3681             :         {
    3682         130 :             sqlite3_close(m_hDB);
    3683             :         }
    3684         133 :         if (m_hDBMBTILES)
    3685             :         {
    3686          76 :             sqlite3_close(m_hDBMBTILES);
    3687             :         }
    3688         262 :         if (!m_osTempDB.empty() && !m_bReuseTempFile &&
    3689         129 :             CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
    3690             :         {
    3691         128 :             VSIUnlink(m_osTempDB);
    3692             :         }
    3693             : 
    3694         133 :         if (GDALDataset::Close() != CE_None)
    3695           0 :             eErr = CE_Failure;
    3696             :     }
    3697         256 :     return eErr;
    3698             : }
    3699             : 
    3700             : /************************************************************************/
    3701             : /*                        ConvertToTileCoords()                     */
    3702             : /************************************************************************/
    3703             : 
    3704       19835 : void OGRMVTWriterDataset::ConvertToTileCoords(double dfX, double dfY, int &nX,
    3705             :                                               int &nY, double dfTopX,
    3706             :                                               double dfTopY,
    3707             :                                               double dfTileDim) const
    3708             : {
    3709       19835 :     if (dfTileDim == 0)
    3710             :     {
    3711        3020 :         nX = static_cast<int>(dfX);
    3712        3020 :         nY = static_cast<int>(dfY);
    3713             :     }
    3714             :     else
    3715             :     {
    3716       16815 :         nX = static_cast<int>(
    3717       16815 :             std::round((dfX - dfTopX) * m_nExtent / dfTileDim));
    3718       16815 :         nY = static_cast<int>(
    3719       16815 :             std::round((dfTopY - dfY) * m_nExtent / dfTileDim));
    3720             :     }
    3721       19835 : }
    3722             : 
    3723             : /************************************************************************/
    3724             : /*                       GetCmdCountCombined()                          */
    3725             : /************************************************************************/
    3726             : 
    3727        4052 : static unsigned GetCmdCountCombined(unsigned int nCmdId, unsigned int nCmdCount)
    3728             : {
    3729        4052 :     return (nCmdId | (nCmdCount << 3));
    3730             : }
    3731             : 
    3732             : /************************************************************************/
    3733             : /*                          EncodeLineString()                          */
    3734             : /************************************************************************/
    3735             : 
    3736        2972 : bool OGRMVTWriterDataset::EncodeLineString(
    3737             :     MVTTileLayerFeature *poGPBFeature, const OGRLineString *poLS,
    3738             :     OGRLineString *poOutLS, bool bWriteLastPoint,
    3739             :     ExpectedWindingOrder eExpectedWindingOrder, GUInt32 nMinLineTo,
    3740             :     double dfTopX, double dfTopY, double dfTileDim, int &nLastX,
    3741             :     int &nLastY) const
    3742             : {
    3743        2972 :     const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    3744        2961 :     const int nLastXOri = nLastX;
    3745        2961 :     const int nLastYOri = nLastY;
    3746        2961 :     GUInt32 nLineToCount = 0;
    3747        2961 :     const int nPoints = poLS->getNumPoints() - (bWriteLastPoint ? 0 : 1);
    3748        2966 :     bool bReverseOrder = false;
    3749             : 
    3750        4599 :     if (eExpectedWindingOrder != ExpectedWindingOrder::NONE &&
    3751        1638 :         poLS->getNumPoints() >= 4)
    3752             :     {
    3753             :         // Do the check on winding order in integer coordinates, since very flat
    3754             :         // rings in non rounded coordinates can change orientation after going
    3755             :         // to integer coordinates! In that case, let's remove them if they are
    3756             :         // inner rings.
    3757        1616 :         int nLastXTmp = nLastX;
    3758        1616 :         int nLastYTmp = nLastY;
    3759        1616 :         OGRLinearRing oRingInteger;
    3760       12496 :         for (int i = 0; i < nPoints; i++)
    3761             :         {
    3762             :             int nX, nY;
    3763       10847 :             const double dfX = poLS->getX(i);
    3764       10823 :             const double dfY = poLS->getY(i);
    3765       10848 :             ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
    3766       10837 :             const int nDiffX = nX - nLastXTmp;
    3767       10837 :             const int nDiffY = nY - nLastYTmp;
    3768       10837 :             if (i == 0 || nDiffX != 0 || nDiffY != 0)
    3769             :             {
    3770             :                 // The minus sign is because the Y axis is positive-downward
    3771             :                 // in vector tile coordinates!
    3772             :                 // Cf https://docs.mapbox.com/data/tilesets/guides/vector-tiles-standards/#winding-order
    3773        4949 :                 oRingInteger.addPoint(nX, -nY);
    3774        4969 :                 nLastXTmp = nX;
    3775        4969 :                 nLastYTmp = nY;
    3776             :             }
    3777             :         }
    3778        1649 :         oRingInteger.closeRings();
    3779        1646 :         if (oRingInteger.getNumPoints() < 4)
    3780        1167 :             return false;
    3781         471 :         const auto bIsClockWise = oRingInteger.isClockwise();
    3782         455 :         if (eExpectedWindingOrder == ExpectedWindingOrder::COUNTERCLOCKWISE)
    3783             :         {
    3784         387 :             if ((dfTileDim != 0 && bIsClockWise != poLS->isClockwise()) ||
    3785         124 :                 (dfTileDim == 0 && bIsClockWise == poLS->isClockwise()))
    3786             :             {
    3787           2 :                 return false;
    3788             :             }
    3789             :         }
    3790         453 :         bReverseOrder =
    3791         192 :             (eExpectedWindingOrder == ExpectedWindingOrder::CLOCKWISE &&
    3792         906 :              !bIsClockWise) ||
    3793         261 :             (eExpectedWindingOrder == ExpectedWindingOrder::COUNTERCLOCKWISE &&
    3794             :              bIsClockWise);
    3795             :     }
    3796             : 
    3797        1800 :     int nFirstX = 0;
    3798        1800 :     int nFirstY = 0;
    3799        1800 :     int nLastXValid = nLastX;
    3800        1800 :     int nLastYValid = nLastY;
    3801        1800 :     if (poOutLS)
    3802        1778 :         poOutLS->setNumPoints(nPoints);
    3803             : 
    3804        9377 :     for (int i = 0; i < nPoints; i++)
    3805             :     {
    3806             :         int nX, nY;
    3807        7614 :         int nSrcIdx = bReverseOrder ? poLS->getNumPoints() - 1 - i : i;
    3808        7614 :         double dfX = poLS->getX(nSrcIdx);
    3809        7611 :         double dfY = poLS->getY(nSrcIdx);
    3810        7605 :         ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
    3811        7581 :         int nDiffX = nX - nLastX;
    3812        7581 :         int nDiffY = nY - nLastY;
    3813        7581 :         if (i == 0 || nDiffX != 0 || nDiffY != 0)
    3814             :         {
    3815        5119 :             if (i > 0)
    3816             :             {
    3817        3355 :                 nLineToCount++;
    3818        3355 :                 if (nLineToCount == 1)
    3819             :                 {
    3820         522 :                     poGPBFeature->addGeometry(
    3821             :                         GetCmdCountCombined(knCMD_MOVETO, 1));
    3822         525 :                     const int nLastDiffX = nLastX - nLastXOri;
    3823         525 :                     const int nLastDiffY = nLastY - nLastYOri;
    3824         525 :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
    3825         525 :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
    3826         527 :                     if (poOutLS)
    3827         525 :                         poOutLS->setPoint(0, nLastX, nLastY);
    3828             : 
    3829             :                     // To be modified later
    3830         528 :                     poGPBFeature->addGeometry(
    3831             :                         GetCmdCountCombined(knCMD_LINETO, 0));
    3832             :                 }
    3833             : 
    3834        3354 :                 poGPBFeature->addGeometry(EncodeSInt(nDiffX));
    3835        3354 :                 poGPBFeature->addGeometry(EncodeSInt(nDiffY));
    3836        3357 :                 if (poOutLS)
    3837        3357 :                     poOutLS->setPoint(nLineToCount, nX, nY);
    3838             :             }
    3839             :             else
    3840             :             {
    3841        1764 :                 nFirstX = nX;
    3842        1764 :                 nFirstY = nY;
    3843             :             }
    3844        5137 :             nLastXValid = nLastX;
    3845        5137 :             nLastYValid = nLastY;
    3846        5137 :             nLastX = nX;
    3847        5137 :             nLastY = nY;
    3848             :         }
    3849             :     }
    3850             : 
    3851             :     // If last point of ring is identical to first one, discard it
    3852        1763 :     if (nMinLineTo == 2 && nLineToCount > 0 && nFirstX == nLastX &&
    3853          81 :         nFirstY == nLastY)
    3854             :     {
    3855          41 :         poGPBFeature->resizeGeometryArray(poGPBFeature->getGeometryCount() - 2);
    3856          49 :         nLineToCount--;
    3857          49 :         nLastX = nLastXValid;
    3858          49 :         nLastY = nLastYValid;
    3859             :     }
    3860             : 
    3861        1771 :     if (nLineToCount >= nMinLineTo)
    3862             :     {
    3863         525 :         if (poOutLS)
    3864         524 :             poOutLS->setNumPoints(1 + nLineToCount);
    3865             :         // Patch actual number of points in LINETO command
    3866         525 :         poGPBFeature->setGeometry(
    3867             :             nInitialSize + 3, GetCmdCountCombined(knCMD_LINETO, nLineToCount));
    3868         522 :         return true;
    3869             :     }
    3870             :     else
    3871             :     {
    3872        1246 :         poGPBFeature->resizeGeometryArray(nInitialSize);
    3873        1242 :         nLastX = nLastXOri;
    3874        1242 :         nLastY = nLastYOri;
    3875        1242 :         return false;
    3876             :     }
    3877             : }
    3878             : 
    3879             : #ifdef notdef
    3880             : /************************************************************************/
    3881             : /*                     EncodeRepairedOuterRing()                        */
    3882             : /************************************************************************/
    3883             : 
    3884             : bool OGRMVTWriterDataset::EncodeRepairedOuterRing(
    3885             :     MVTTileLayerFeature *poGPBFeature, OGRPolygon &oInPoly, int &nLastX,
    3886             :     int &nLastY) const
    3887             : {
    3888             :     std::unique_ptr<OGRGeometry> poFixedGeom(oInPoly.Buffer(0));
    3889             :     if (!poFixedGeom.get() || poFixedGeom->IsEmpty())
    3890             :     {
    3891             :         return false;
    3892             :     }
    3893             : 
    3894             :     OGRPolygon *poPoly = nullptr;
    3895             :     if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbMultiPolygon)
    3896             :     {
    3897             :         OGRMultiPolygon *poMP = poFixedGeom.get()->toMultiPolygon();
    3898             :         poPoly = poMP->getGeometryRef(0)->toPolygon();
    3899             :     }
    3900             :     else if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbPolygon)
    3901             :     {
    3902             :         poPoly = poFixedGeom.get()->toPolygon();
    3903             :     }
    3904             :     if (!poPoly)
    3905             :         return false;
    3906             : 
    3907             :     OGRLinearRing *poRing = poPoly->getExteriorRing();
    3908             :     const bool bReverseOrder = !poRing->isClockwise();
    3909             : 
    3910             :     const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    3911             :     const int nLastXOri = nLastX;
    3912             :     const int nLastYOri = nLastY;
    3913             :     GUInt32 nLineToCount = 0;
    3914             :     const int nPoints = poRing->getNumPoints() - 1;
    3915             :     auto poOutLinearRing = std::make_unique<OGRLinearRing>();
    3916             :     poOutLinearRing->setNumPoints(nPoints);
    3917             :     for (int i = 0; i < nPoints; i++)
    3918             :     {
    3919             :         int nSrcIdx = bReverseOrder ? poRing->getNumPoints() - 1 - i : i;
    3920             :         double dfX = poRing->getX(nSrcIdx);
    3921             :         double dfY = poRing->getY(nSrcIdx);
    3922             :         int nX = static_cast<int>(std::round(dfX));
    3923             :         int nY = static_cast<int>(std::round(dfY));
    3924             :         if (nX != dfX || nY != dfY)
    3925             :             continue;
    3926             :         int nDiffX = nX - nLastX;
    3927             :         int nDiffY = nY - nLastY;
    3928             :         if (i == 0 || nDiffX != 0 || nDiffY != 0)
    3929             :         {
    3930             :             if (i > 0)
    3931             :             {
    3932             :                 nLineToCount++;
    3933             :                 if (nLineToCount == 1)
    3934             :                 {
    3935             :                     poGPBFeature->addGeometry(
    3936             :                         GetCmdCountCombined(knCMD_MOVETO, 1));
    3937             :                     const int nLastDiffX = nLastX - nLastXOri;
    3938             :                     const int nLastDiffY = nLastY - nLastYOri;
    3939             :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
    3940             :                     poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
    3941             :                     poOutLinearRing->setPoint(0, nLastX, nLastY);
    3942             : 
    3943             :                     // To be modified later
    3944             :                     poGPBFeature->addGeometry(
    3945             :                         GetCmdCountCombined(knCMD_LINETO, 0));
    3946             :                 }
    3947             : 
    3948             :                 poGPBFeature->addGeometry(EncodeSInt(nDiffX));
    3949             :                 poGPBFeature->addGeometry(EncodeSInt(nDiffY));
    3950             :                 poOutLinearRing->setPoint(nLineToCount, nX, nY);
    3951             :             }
    3952             :             nLastX = nX;
    3953             :             nLastY = nY;
    3954             :         }
    3955             :     }
    3956             :     if (nLineToCount >= 2)
    3957             :     {
    3958             :         poOutLinearRing->setNumPoints(1 + nLineToCount);
    3959             :         OGRPolygon oOutPoly;
    3960             :         oOutPoly.addRingDirectly(poOutLinearRing.release());
    3961             :         int bIsValid;
    3962             :         {
    3963             :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    3964             :             bIsValid = oOutPoly.IsValid();
    3965             :         }
    3966             :         if (bIsValid)
    3967             :         {
    3968             :             // Patch actual number of points in LINETO command
    3969             :             poGPBFeature->setGeometry(
    3970             :                 nInitialSize + 3,
    3971             :                 GetCmdCountCombined(knCMD_LINETO, nLineToCount));
    3972             :             poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
    3973             :             return true;
    3974             :         }
    3975             :     }
    3976             : 
    3977             :     poGPBFeature->resizeGeometryArray(nInitialSize);
    3978             :     nLastX = nLastXOri;
    3979             :     nLastY = nLastYOri;
    3980             :     return false;
    3981             : }
    3982             : #endif
    3983             : 
    3984             : /************************************************************************/
    3985             : /*                          EncodePolygon()                             */
    3986             : /************************************************************************/
    3987             : 
    3988        1381 : bool OGRMVTWriterDataset::EncodePolygon(MVTTileLayerFeature *poGPBFeature,
    3989             :                                         const OGRPolygon *poPoly,
    3990             :                                         OGRPolygon *poOutPoly, double dfTopX,
    3991             :                                         double dfTopY, double dfTileDim,
    3992             :                                         int &nLastX, int &nLastY,
    3993             :                                         double &dfArea) const
    3994             : {
    3995        1381 :     dfArea = 0;
    3996        2724 :     auto poOutOuterRing = std::make_unique<OGRLinearRing>();
    3997        1824 :     for (int i = 0; i < 1 + poPoly->getNumInteriorRings(); i++)
    3998             :     {
    3999        1634 :         const OGRLinearRing *poRing = (i == 0) ? poPoly->getExteriorRing()
    4000         271 :                                                : poPoly->getInteriorRing(i - 1);
    4001        3272 :         if (poRing->getNumPoints() < 4 ||
    4002        3247 :             poRing->getX(0) != poRing->getX(poRing->getNumPoints() - 1) ||
    4003        1664 :             poRing->getY(0) != poRing->getY(poRing->getNumPoints() - 1))
    4004             :         {
    4005           0 :             if (i == 0)
    4006        1139 :                 return false;
    4007         198 :             continue;
    4008             :         }
    4009        1621 :         const bool bWriteLastPoint = false;
    4010        1621 :         const auto eExpectedWindingOrder =
    4011        1621 :             ((i == 0) ? ExpectedWindingOrder::CLOCKWISE
    4012             :                       : ExpectedWindingOrder::COUNTERCLOCKWISE);
    4013        1621 :         const GUInt32 nMinLineTo = 2;
    4014           0 :         std::unique_ptr<OGRLinearRing> poOutInnerRing;
    4015        1621 :         if (i > 0)
    4016         270 :             poOutInnerRing = std::make_unique<OGRLinearRing>();
    4017             :         OGRLinearRing *poOutRing =
    4018        1620 :             poOutInnerRing.get() ? poOutInnerRing.get() : poOutOuterRing.get();
    4019             : 
    4020             :         bool bSuccess =
    4021        1627 :             EncodeLineString(poGPBFeature, poRing, poOutRing, bWriteLastPoint,
    4022             :                              eExpectedWindingOrder, nMinLineTo, dfTopX, dfTopY,
    4023             :                              dfTileDim, nLastX, nLastY);
    4024        1620 :         if (!bSuccess)
    4025             :         {
    4026        1121 :             if (i == 0)
    4027        1129 :                 return false;
    4028           0 :             continue;
    4029             :         }
    4030             : 
    4031         499 :         if (poOutPoly == nullptr)
    4032             :         {
    4033         179 :             poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
    4034         180 :             continue;
    4035             :         }
    4036             : 
    4037         320 :         poOutRing->closeRings();
    4038             : 
    4039         274 :         poOutPoly->addRing(poOutRing);
    4040         274 :         if (i > 0)
    4041         139 :             dfArea -= poOutRing->get_Area();
    4042             :         else
    4043         135 :             dfArea = poOutRing->get_Area();
    4044             : 
    4045         275 :         poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
    4046             :     }
    4047             : 
    4048         204 :     return true;
    4049             : }
    4050             : 
    4051             : /************************************************************************/
    4052             : /*                          PreGenerateForTile()                        */
    4053             : /************************************************************************/
    4054             : 
    4055        3986 : OGRErr OGRMVTWriterDataset::PreGenerateForTileReal(
    4056             :     int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
    4057             :     bool bIsMaxZoomForLayer, const OGRMVTFeatureContent *poFeatureContent,
    4058             :     GIntBig nSerial, const OGRGeometry *poGeom,
    4059             :     const OGREnvelope &sEnvelope) const
    4060             : {
    4061        3986 :     double dfTileDim = m_dfTileDim0 / (1 << nZ);
    4062        3986 :     double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
    4063        3986 :     double dfTopX = m_dfTopX + nTileX * dfTileDim;
    4064        3986 :     double dfTopY = m_dfTopY - nTileY * dfTileDim;
    4065        3986 :     double dfBottomRightX = dfTopX + dfTileDim;
    4066        3986 :     double dfBottomRightY = dfTopY - dfTileDim;
    4067        3986 :     double dfIntersectTopX = dfTopX - dfBuffer;
    4068        3986 :     double dfIntersectTopY = dfTopY + dfBuffer;
    4069        3986 :     double dfIntersectBottomRightX = dfBottomRightX + dfBuffer;
    4070        3986 :     double dfIntersectBottomRightY = dfBottomRightY - dfBuffer;
    4071             : 
    4072             :     const OGRGeometry *poIntersection;
    4073        3962 :     std::unique_ptr<OGRGeometry> poIntersectionHolder;  // keep in that scope
    4074        3986 :     if (sEnvelope.MinX >= dfIntersectTopX &&
    4075        3975 :         sEnvelope.MinY >= dfIntersectBottomRightY &&
    4076        3972 :         sEnvelope.MaxX <= dfIntersectBottomRightX &&
    4077        3952 :         sEnvelope.MaxY <= dfIntersectTopY)
    4078             :     {
    4079        3947 :         poIntersection = poGeom;
    4080             :     }
    4081             :     else
    4082             :     {
    4083          39 :         OGRLinearRing *poLR = new OGRLinearRing();
    4084          54 :         poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
    4085          54 :         poLR->addPoint(dfIntersectTopX, dfIntersectBottomRightY);
    4086          54 :         poLR->addPoint(dfIntersectBottomRightX, dfIntersectBottomRightY);
    4087          54 :         poLR->addPoint(dfIntersectBottomRightX, dfIntersectTopY);
    4088          54 :         poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
    4089          54 :         OGRPolygon oPoly;
    4090          54 :         oPoly.addRingDirectly(poLR);
    4091             : 
    4092          54 :         CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4093          54 :         auto poTmp = poGeom->Intersection(&oPoly);
    4094          54 :         poIntersection = poTmp;
    4095          54 :         poIntersectionHolder.reset(poTmp);
    4096          53 :         if (poIntersection == nullptr || poIntersection->IsEmpty())
    4097             :         {
    4098           3 :             return OGRERR_NONE;
    4099             :         }
    4100             :     }
    4101             : 
    4102             :     // Create a layer with a single feature in it
    4103             :     std::shared_ptr<MVTTileLayer> poLayer =
    4104        7924 :         std::shared_ptr<MVTTileLayer>(new MVTTileLayer());
    4105             :     std::shared_ptr<MVTTileLayerFeature> poGPBFeature =
    4106        7818 :         std::shared_ptr<MVTTileLayerFeature>(new MVTTileLayerFeature());
    4107        3934 :     poLayer->addFeature(poGPBFeature);
    4108             : 
    4109        3955 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    4110        3989 :     if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
    4111        1379 :         poGPBFeature->setType(MVTTileLayerFeature::GeomType::POINT);
    4112        2610 :     else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
    4113        1336 :         poGPBFeature->setType(MVTTileLayerFeature::GeomType::LINESTRING);
    4114        1274 :     else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
    4115        1274 :         poGPBFeature->setType(MVTTileLayerFeature::GeomType::POLYGON);
    4116             :     else
    4117             :     {
    4118           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
    4119           0 :         return OGRERR_NONE;
    4120             :     }
    4121             : 
    4122             :     OGRwkbGeometryType eGeomToEncodeType =
    4123        3955 :         wkbFlatten(poIntersection->getGeometryType());
    4124             : 
    4125             :     // Simplify contour if requested by user
    4126        3928 :     const OGRGeometry *poGeomToEncode = poIntersection;
    4127        3865 :     std::unique_ptr<OGRGeometry> poGeomSimplified;
    4128        3928 :     const double dfSimplification =
    4129        3928 :         bIsMaxZoomForLayer ? m_dfSimplificationMaxZoom : m_dfSimplification;
    4130        3928 :     if (dfSimplification > 0 &&
    4131          12 :         (eGeomType == wkbLineString || eGeomType == wkbMultiLineString ||
    4132          12 :          eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon))
    4133             :     {
    4134           0 :         const double dfTol = dfTileDim / m_nExtent;
    4135           0 :         poGeomSimplified = std::unique_ptr<OGRGeometry>(
    4136          12 :             poIntersection->SimplifyPreserveTopology(dfTol * dfSimplification));
    4137          12 :         if (poGeomSimplified.get())
    4138             :         {
    4139          12 :             poGeomToEncode = poGeomSimplified.get();
    4140          12 :             eGeomToEncodeType = wkbFlatten(poGeomSimplified->getGeometryType());
    4141             :         }
    4142             :     }
    4143             : 
    4144        3959 :     bool bGeomOK = false;
    4145        3959 :     double dfAreaOrLength = 0.0;
    4146             : 
    4147             :     const auto EmitValidPolygon =
    4148          58 :         [this, &bGeomOK, &dfAreaOrLength,
    4149         184 :          &poGPBFeature](const OGRGeometry *poValidGeom)
    4150             :     {
    4151          58 :         bGeomOK = false;
    4152          58 :         dfAreaOrLength = 0;
    4153          58 :         int nLastX = 0;
    4154          58 :         int nLastY = 0;
    4155             : 
    4156          58 :         if (wkbFlatten(poValidGeom->getGeometryType()) == wkbPolygon)
    4157             :         {
    4158          12 :             const OGRPolygon *poPoly = poValidGeom->toPolygon();
    4159          12 :             double dfPartArea = 0.0;
    4160          12 :             bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
    4161             :                                     0, nLastX, nLastY, dfPartArea);
    4162          12 :             dfAreaOrLength = dfPartArea;
    4163             :         }
    4164          45 :         else if (OGR_GT_IsSubClassOf(poValidGeom->getGeometryType(),
    4165          45 :                                      wkbGeometryCollection))
    4166             :         {
    4167         127 :             for (auto &&poSubGeom : poValidGeom->toGeometryCollection())
    4168             :             {
    4169          87 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
    4170             :                 {
    4171          36 :                     const OGRPolygon *poPoly = poSubGeom->toPolygon();
    4172          35 :                     double dfPartArea = 0.0;
    4173          35 :                     bGeomOK |=
    4174          35 :                         EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
    4175          35 :                                       0, nLastX, nLastY, dfPartArea);
    4176          35 :                     dfAreaOrLength += dfPartArea;
    4177             :                 }
    4178          52 :                 else if (wkbFlatten(poSubGeom->getGeometryType()) ==
    4179             :                          wkbMultiPolygon)
    4180             :                 {
    4181             :                     const OGRMultiPolygon *poMPoly =
    4182           5 :                         poSubGeom->toMultiPolygon();
    4183          15 :                     for (const auto *poPoly : poMPoly)
    4184             :                     {
    4185          10 :                         double dfPartArea = 0.0;
    4186          10 :                         bGeomOK |=
    4187          10 :                             EncodePolygon(poGPBFeature.get(), poPoly, nullptr,
    4188          10 :                                           0, 0, 0, nLastX, nLastY, dfPartArea);
    4189          10 :                         dfAreaOrLength += dfPartArea;
    4190             :                     }
    4191             :                 }
    4192             :             }
    4193             :         }
    4194          58 :     };
    4195             : 
    4196        3959 :     if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
    4197             :     {
    4198        1345 :         if (eGeomToEncodeType == wkbPoint)
    4199             :         {
    4200         968 :             const OGRPoint *poPoint = poIntersection->toPoint();
    4201             :             int nX, nY;
    4202         970 :             double dfX = poPoint->getX();
    4203         980 :             double dfY = poPoint->getY();
    4204         985 :             bGeomOK = true;
    4205         985 :             ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
    4206         975 :             poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_MOVETO, 1));
    4207         971 :             poGPBFeature->addGeometry(EncodeSInt(nX));
    4208         980 :             poGPBFeature->addGeometry(EncodeSInt(nY));
    4209             :         }
    4210         377 :         else if (eGeomToEncodeType == wkbMultiPoint ||
    4211             :                  eGeomToEncodeType == wkbGeometryCollection)
    4212             :         {
    4213             :             const OGRGeometryCollection *poGC =
    4214         376 :                 poIntersection->toGeometryCollection();
    4215         738 :             std::set<std::pair<int, int>> oSetUniqueCoords;
    4216         376 :             poGPBFeature->addGeometry(
    4217             :                 GetCmdCountCombined(knCMD_MOVETO, 0));  // To be modified later
    4218         381 :             int nLastX = 0;
    4219         381 :             int nLastY = 0;
    4220         764 :             for (auto &&poSubGeom : poGC)
    4221             :             {
    4222         384 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPoint)
    4223             :                 {
    4224         378 :                     const OGRPoint *poPoint = poSubGeom->toPoint();
    4225             :                     int nX, nY;
    4226         380 :                     double dfX = poPoint->getX();
    4227         382 :                     double dfY = poPoint->getY();
    4228         383 :                     ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY,
    4229             :                                         dfTileDim);
    4230         382 :                     if (oSetUniqueCoords.find(std::pair<int, int>(nX, nY)) ==
    4231         761 :                         oSetUniqueCoords.end())
    4232             :                     {
    4233         379 :                         oSetUniqueCoords.insert(std::pair<int, int>(nX, nY));
    4234             : 
    4235         375 :                         int nDiffX = nX - nLastX;
    4236         375 :                         int nDiffY = nY - nLastY;
    4237         375 :                         poGPBFeature->addGeometry(EncodeSInt(nDiffX));
    4238         377 :                         poGPBFeature->addGeometry(EncodeSInt(nDiffY));
    4239         382 :                         nLastX = nX;
    4240         382 :                         nLastY = nY;
    4241             :                     }
    4242             :                 }
    4243             :             }
    4244         375 :             GUInt32 nPoints = static_cast<GUInt32>(oSetUniqueCoords.size());
    4245         375 :             bGeomOK = nPoints > 0;
    4246         375 :             poGPBFeature->setGeometry(
    4247             :                 0, GetCmdCountCombined(knCMD_MOVETO, nPoints));
    4248        1353 :         }
    4249             :     }
    4250        2614 :     else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
    4251             :     {
    4252        1337 :         const bool bWriteLastPoint = true;
    4253        1337 :         const GUInt32 nMinLineTo = 1;
    4254             : 
    4255        1337 :         if (eGeomToEncodeType == wkbLineString)
    4256             :         {
    4257         929 :             const OGRLineString *poLS = poGeomToEncode->toLineString();
    4258         924 :             int nLastX = 0;
    4259         924 :             int nLastY = 0;
    4260         924 :             OGRLineString oOutLS;
    4261         926 :             bGeomOK = EncodeLineString(
    4262             :                 poGPBFeature.get(), poLS, &oOutLS, bWriteLastPoint,
    4263             :                 ExpectedWindingOrder::NONE, nMinLineTo, dfTopX, dfTopY,
    4264             :                 dfTileDim, nLastX, nLastY);
    4265         923 :             dfAreaOrLength = oOutLS.get_Length();
    4266             :         }
    4267         408 :         else if (eGeomToEncodeType == wkbMultiLineString ||
    4268             :                  eGeomToEncodeType == wkbGeometryCollection)
    4269             :         {
    4270             :             const OGRGeometryCollection *poGC =
    4271         395 :                 poGeomToEncode->toGeometryCollection();
    4272         382 :             int nLastX = 0;
    4273         382 :             int nLastY = 0;
    4274         774 :             for (auto &&poSubGeom : poGC)
    4275             :             {
    4276         394 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbLineString)
    4277             :                 {
    4278         393 :                     const OGRLineString *poLS = poSubGeom->toLineString();
    4279         391 :                     OGRLineString oOutLS;
    4280         394 :                     bool bSubGeomOK = EncodeLineString(
    4281             :                         poGPBFeature.get(), poLS, &oOutLS, bWriteLastPoint,
    4282             :                         ExpectedWindingOrder::NONE, nMinLineTo, dfTopX, dfTopY,
    4283             :                         dfTileDim, nLastX, nLastY);
    4284         391 :                     if (bSubGeomOK)
    4285          18 :                         dfAreaOrLength += oOutLS.get_Length();
    4286         391 :                     bGeomOK |= bSubGeomOK;
    4287             :                 }
    4288             :             }
    4289        1310 :         }
    4290             :     }
    4291        1277 :     else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
    4292             :     {
    4293        1299 :         if (eGeomToEncodeType == wkbPolygon)
    4294             :         {
    4295         938 :             const OGRPolygon *poPoly = poGeomToEncode->toPolygon();
    4296         942 :             int nLastX = 0;
    4297         942 :             int nLastY = 0;
    4298        1846 :             OGRPolygon oOutPoly;
    4299         931 :             const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    4300         925 :             CPL_IGNORE_RET_VAL(nInitialSize);
    4301         914 :             bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, &oOutPoly,
    4302             :                                     dfTopX, dfTopY, dfTileDim, nLastX, nLastY,
    4303             :                                     dfAreaOrLength);
    4304             :             int bIsValid;
    4305             :             {
    4306         950 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4307         920 :                 bIsValid = oOutPoly.IsValid();
    4308             :             }
    4309         904 :             if (!bIsValid)
    4310             :             {
    4311             :                 // Build a valid geometry from the initial MVT geometry and emit
    4312             :                 // it
    4313         109 :                 std::unique_ptr<OGRGeometry> poPolyValid(oOutPoly.MakeValid());
    4314          54 :                 if (poPolyValid)
    4315             :                 {
    4316          54 :                     poGPBFeature->resizeGeometryArray(nInitialSize);
    4317          54 :                     EmitValidPolygon(poPolyValid.get());
    4318             :                 }
    4319             :             }
    4320             :         }
    4321         361 :         else if (eGeomToEncodeType == wkbMultiPolygon ||
    4322             :                  eGeomToEncodeType == wkbGeometryCollection)
    4323             :         {
    4324             :             const OGRGeometryCollection *poGC =
    4325         373 :                 poGeomToEncode->toGeometryCollection();
    4326         370 :             int nLastX = 0;
    4327         370 :             int nLastY = 0;
    4328         729 :             OGRMultiPolygon oOutMP;
    4329         359 :             const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
    4330         356 :             CPL_IGNORE_RET_VAL(nInitialSize);
    4331         706 :             for (auto &&poSubGeom : poGC)
    4332             :             {
    4333         370 :                 if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
    4334             :                 {
    4335         382 :                     const OGRPolygon *poPoly = poSubGeom->toPolygon();
    4336         382 :                     double dfPartArea = 0.0;
    4337         728 :                     auto poOutPoly = std::make_unique<OGRPolygon>();
    4338         378 :                     bGeomOK |= EncodePolygon(
    4339             :                         poGPBFeature.get(), poPoly, poOutPoly.get(), dfTopX,
    4340         376 :                         dfTopY, dfTileDim, nLastX, nLastY, dfPartArea);
    4341         376 :                     dfAreaOrLength += dfPartArea;
    4342         376 :                     oOutMP.addGeometryDirectly(poOutPoly.release());
    4343             :                 }
    4344             :             }
    4345             :             int bIsValid;
    4346             :             {
    4347         351 :                 CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4348         355 :                 bIsValid = oOutMP.IsValid();
    4349             :             }
    4350         359 :             if (!bIsValid)
    4351             :             {
    4352             :                 // Build a valid geometry from the initial MVT geometry and emit
    4353             :                 // it
    4354           6 :                 std::unique_ptr<OGRGeometry> poMPValid(oOutMP.MakeValid());
    4355           3 :                 if (poMPValid)
    4356             :                 {
    4357           3 :                     poGPBFeature->resizeGeometryArray(nInitialSize);
    4358           3 :                     EmitValidPolygon(poMPValid.get());
    4359             :                 }
    4360             :             }
    4361             :         }
    4362             :     }
    4363        3827 :     if (!bGeomOK)
    4364        2323 :         return OGRERR_NONE;
    4365             : 
    4366        5784 :     for (const auto &pair : poFeatureContent->oValues)
    4367             :     {
    4368        4284 :         GUInt32 nKey = poLayer->addKey(pair.first);
    4369        4298 :         GUInt32 nVal = poLayer->addValue(pair.second);
    4370        4247 :         poGPBFeature->addTag(nKey);
    4371        4265 :         poGPBFeature->addTag(nVal);
    4372             :     }
    4373        1521 :     if (poFeatureContent->nFID >= 0)
    4374             :     {
    4375          53 :         poGPBFeature->setId(poFeatureContent->nFID);
    4376             :     }
    4377             : 
    4378             : #ifdef notdef
    4379             :     {
    4380             :         MVTTile oTile;
    4381             :         poLayer->setName("x");
    4382             :         oTile.addLayer(poLayer);
    4383             : 
    4384             :         CPLString oBuffer(oTile.write());
    4385             : 
    4386             :         VSILFILE *fp = VSIFOpenL(
    4387             :             CPLSPrintf("/tmp/%d-%d-%d.pbf", nZ, nTileX, nTileY), "wb");
    4388             :         VSIFWriteL(oBuffer.data(), 1, oBuffer.size(), fp);
    4389             :         VSIFCloseL(fp);
    4390             :     }
    4391             : #endif
    4392             : 
    4393             :     // GPB encode the layer with our single feature
    4394        3063 :     CPLString oBuffer(poLayer->write());
    4395             : 
    4396             :     // Compress buffer
    4397        1534 :     size_t nCompressedSize = 0;
    4398        1534 :     void *pCompressed = CPLZLibDeflate(oBuffer.data(), oBuffer.size(), -1,
    4399             :                                        nullptr, 0, &nCompressedSize);
    4400        1510 :     oBuffer.assign(static_cast<char *>(pCompressed), nCompressedSize);
    4401        1497 :     CPLFree(pCompressed);
    4402             : 
    4403        1542 :     const auto InsertIntoDb = [&]()
    4404             :     {
    4405       15420 :         m_nTempTiles++;
    4406        1542 :         sqlite3_bind_int(m_hInsertStmt, 1, nZ);
    4407        1542 :         sqlite3_bind_int(m_hInsertStmt, 2, nTileX);
    4408        1542 :         sqlite3_bind_int(m_hInsertStmt, 3, nTileY);
    4409        1542 :         sqlite3_bind_text(m_hInsertStmt, 4, osTargetName.c_str(), -1,
    4410             :                           SQLITE_STATIC);
    4411        1542 :         sqlite3_bind_int64(m_hInsertStmt, 5, nSerial);
    4412        1542 :         sqlite3_bind_blob(m_hInsertStmt, 6, oBuffer.data(),
    4413        1542 :                           static_cast<int>(oBuffer.size()), SQLITE_STATIC);
    4414        1542 :         sqlite3_bind_int(m_hInsertStmt, 7,
    4415        1542 :                          static_cast<int>(poGPBFeature->getType()));
    4416        1542 :         sqlite3_bind_double(m_hInsertStmt, 8, dfAreaOrLength);
    4417        1542 :         int rc = sqlite3_step(m_hInsertStmt);
    4418        1542 :         sqlite3_reset(m_hInsertStmt);
    4419        1542 :         return rc;
    4420        1505 :     };
    4421             : 
    4422             :     int rc;
    4423        1505 :     if (m_bThreadPoolOK)
    4424             :     {
    4425        1499 :         std::lock_guard<std::mutex> oLock(m_oDBMutex);
    4426        1517 :         rc = InsertIntoDb();
    4427             :     }
    4428             :     else
    4429             :     {
    4430           6 :         rc = InsertIntoDb();
    4431             :     }
    4432             : 
    4433        1542 :     if (!(rc == SQLITE_OK || rc == SQLITE_DONE))
    4434             :     {
    4435           5 :         return OGRERR_FAILURE;
    4436             :     }
    4437             : 
    4438        1537 :     return OGRERR_NONE;
    4439             : }
    4440             : 
    4441             : /************************************************************************/
    4442             : /*                           MVTWriterTask()                            */
    4443             : /************************************************************************/
    4444             : 
    4445             : class MVTWriterTask
    4446             : {
    4447             :   public:
    4448             :     const OGRMVTWriterDataset *poDS;
    4449             :     int nZ;
    4450             :     int nTileX;
    4451             :     int nTileY;
    4452             :     CPLString osTargetName;
    4453             :     bool bIsMaxZoomForLayer;
    4454             :     std::shared_ptr<OGRMVTFeatureContent> poFeatureContent;
    4455             :     GIntBig nSerial;
    4456             :     std::shared_ptr<OGRGeometry> poGeom;
    4457             :     OGREnvelope sEnvelope;
    4458             : };
    4459             : 
    4460             : /************************************************************************/
    4461             : /*                          WriterTaskFunc()                            */
    4462             : /************************************************************************/
    4463             : 
    4464        4007 : void OGRMVTWriterDataset::WriterTaskFunc(void *pParam)
    4465             : {
    4466        4007 :     MVTWriterTask *poTask = static_cast<MVTWriterTask *>(pParam);
    4467       15876 :     OGRErr eErr = poTask->poDS->PreGenerateForTileReal(
    4468        3965 :         poTask->nZ, poTask->nTileX, poTask->nTileY, poTask->osTargetName,
    4469        3977 :         poTask->bIsMaxZoomForLayer, poTask->poFeatureContent.get(),
    4470        4007 :         poTask->nSerial, poTask->poGeom.get(), poTask->sEnvelope);
    4471        3927 :     if (eErr != OGRERR_NONE)
    4472             :     {
    4473           4 :         std::lock_guard oLock(poTask->poDS->m_oDBMutex);
    4474           4 :         poTask->poDS->m_bWriteFeatureError = true;
    4475             :     }
    4476        3927 :     delete poTask;
    4477        4019 : }
    4478             : 
    4479             : /************************************************************************/
    4480             : /*                         PreGenerateForTile()                         */
    4481             : /************************************************************************/
    4482             : 
    4483        4036 : OGRErr OGRMVTWriterDataset::PreGenerateForTile(
    4484             :     int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
    4485             :     bool bIsMaxZoomForLayer,
    4486             :     const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
    4487             :     GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
    4488             :     const OGREnvelope &sEnvelope) const
    4489             : {
    4490        4036 :     if (!m_bThreadPoolOK)
    4491             :     {
    4492          25 :         return PreGenerateForTileReal(
    4493             :             nZ, nTileX, nTileY, osTargetName, bIsMaxZoomForLayer,
    4494          50 :             poFeatureContent.get(), nSerial, poGeom.get(), sEnvelope);
    4495             :     }
    4496             :     else
    4497             :     {
    4498        4011 :         MVTWriterTask *poTask = new MVTWriterTask;
    4499        4011 :         poTask->poDS = this;
    4500        4011 :         poTask->nZ = nZ;
    4501        4011 :         poTask->nTileX = nTileX;
    4502        4011 :         poTask->nTileY = nTileY;
    4503        4011 :         poTask->osTargetName = osTargetName;
    4504        4011 :         poTask->bIsMaxZoomForLayer = bIsMaxZoomForLayer;
    4505        4011 :         poTask->poFeatureContent = poFeatureContent;
    4506        4011 :         poTask->nSerial = nSerial;
    4507        4011 :         poTask->poGeom = poGeom;
    4508        4011 :         poTask->sEnvelope = sEnvelope;
    4509        4011 :         m_oThreadPool.SubmitJob(OGRMVTWriterDataset::WriterTaskFunc, poTask);
    4510             :         // Do not queue more than 1000 jobs to avoid memory exhaustion
    4511        4011 :         m_oThreadPool.WaitCompletion(1000);
    4512             : 
    4513        4011 :         std::lock_guard oLock(m_oDBMutex);
    4514        4011 :         return m_bWriteFeatureError ? OGRERR_FAILURE : OGRERR_NONE;
    4515             :     }
    4516             : }
    4517             : 
    4518             : /************************************************************************/
    4519             : /*                        UpdateLayerProperties()                       */
    4520             : /************************************************************************/
    4521             : 
    4522        4353 : void OGRMVTWriterDataset::UpdateLayerProperties(
    4523             :     MVTLayerProperties *poLayerProperties, const std::string &osKey,
    4524             :     const MVTTileLayerValue &oValue)
    4525             : {
    4526        4353 :     auto oFieldIter = poLayerProperties->m_oMapFieldNameToIdx.find(osKey);
    4527        4353 :     MVTFieldProperties *poFieldProps = nullptr;
    4528        4353 :     if (oFieldIter == poLayerProperties->m_oMapFieldNameToIdx.end())
    4529             :     {
    4530         181 :         if (poLayerProperties->m_oSetFields.size() < knMAX_COUNT_FIELDS)
    4531             :         {
    4532         181 :             poLayerProperties->m_oSetFields.insert(osKey);
    4533         181 :             if (poLayerProperties->m_oMapFieldNameToIdx.size() <
    4534             :                 knMAX_REPORT_FIELDS)
    4535             :             {
    4536         362 :                 MVTFieldProperties oFieldProps;
    4537         181 :                 oFieldProps.m_osName = osKey;
    4538         181 :                 if (oValue.isNumeric())
    4539             :                 {
    4540          73 :                     oFieldProps.m_dfMinVal = oValue.getNumericValue();
    4541          73 :                     oFieldProps.m_dfMaxVal = oValue.getNumericValue();
    4542          73 :                     oFieldProps.m_bAllInt = true;  // overridden just below
    4543             :                 }
    4544         181 :                 oFieldProps.m_eType =
    4545         289 :                     oValue.isNumeric()  ? MVTTileLayerValue::ValueType::DOUBLE
    4546         108 :                     : oValue.isString() ? MVTTileLayerValue::ValueType::STRING
    4547             :                                         : MVTTileLayerValue::ValueType::BOOL;
    4548             : 
    4549         181 :                 poLayerProperties->m_oMapFieldNameToIdx[osKey] =
    4550         181 :                     poLayerProperties->m_aoFields.size();
    4551         181 :                 poLayerProperties->m_aoFields.push_back(std::move(oFieldProps));
    4552         181 :                 poFieldProps = &(poLayerProperties->m_aoFields.back());
    4553             :             }
    4554             :         }
    4555             :     }
    4556             :     else
    4557             :     {
    4558        4172 :         poFieldProps = &(poLayerProperties->m_aoFields[oFieldIter->second]);
    4559             :     }
    4560             : 
    4561        4353 :     if (poFieldProps)
    4562             :     {
    4563        4353 :         if (oValue.getType() == MVTTileLayerValue::ValueType::BOOL)
    4564             :         {
    4565          24 :             MVTTileLayerValue oUniqVal;
    4566          12 :             oUniqVal.setBoolValue(oValue.getBoolValue());
    4567          12 :             poFieldProps->m_oSetAllValues.insert(oUniqVal);
    4568          12 :             poFieldProps->m_oSetValues.insert(oUniqVal);
    4569             :         }
    4570        4341 :         else if (oValue.isNumeric())
    4571             :         {
    4572        1780 :             if (poFieldProps->m_bAllInt)
    4573             :             {
    4574         935 :                 poFieldProps->m_bAllInt =
    4575        1870 :                     oValue.getType() == MVTTileLayerValue::ValueType::INT ||
    4576        2751 :                     oValue.getType() == MVTTileLayerValue::ValueType::SINT ||
    4577        1798 :                     (oValue.getType() == MVTTileLayerValue::ValueType::UINT &&
    4578         881 :                      oValue.getUIntValue() < GINT64_MAX);
    4579             :             }
    4580        1780 :             double dfVal = oValue.getNumericValue();
    4581        1780 :             poFieldProps->m_dfMinVal =
    4582        1780 :                 std::min(poFieldProps->m_dfMinVal, dfVal);
    4583        1780 :             poFieldProps->m_dfMaxVal =
    4584        1780 :                 std::max(poFieldProps->m_dfMaxVal, dfVal);
    4585        1780 :             if (poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
    4586             :             {
    4587        3560 :                 MVTTileLayerValue oUniqVal;
    4588        1780 :                 oUniqVal.setDoubleValue(dfVal);
    4589        1780 :                 poFieldProps->m_oSetAllValues.insert(oUniqVal);
    4590        1780 :                 if (poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
    4591             :                 {
    4592        1780 :                     poFieldProps->m_oSetValues.insert(oUniqVal);
    4593             :                 }
    4594             :             }
    4595             :         }
    4596        5122 :         else if (oValue.isString() &&
    4597        2561 :                  poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
    4598             :         {
    4599        5122 :             auto osVal = oValue.getStringValue();
    4600        5122 :             MVTTileLayerValue oUniqVal;
    4601        2561 :             oUniqVal.setStringValue(osVal);
    4602        2561 :             poFieldProps->m_oSetAllValues.insert(oUniqVal);
    4603        5122 :             if (osVal.size() <= knMAX_STRING_VALUE_LENGTH &&
    4604        2561 :                 poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
    4605             :             {
    4606        2561 :                 poFieldProps->m_oSetValues.insert(oUniqVal);
    4607             :             }
    4608             :         }
    4609             :     }
    4610        4353 : }
    4611             : 
    4612             : /************************************************************************/
    4613             : /*                           GZIPCompress()                             */
    4614             : /************************************************************************/
    4615             : 
    4616         977 : static void GZIPCompress(std::string &oTileBuffer)
    4617             : {
    4618         977 :     if (!oTileBuffer.empty())
    4619             :     {
    4620             :         const CPLString osTmpFilename(
    4621        1954 :             VSIMemGenerateHiddenFilename("mvt_temp.gz"));
    4622        1954 :         CPLString osTmpGZipFilename("/vsigzip/" + osTmpFilename);
    4623         977 :         VSILFILE *fpGZip = VSIFOpenL(osTmpGZipFilename, "wb");
    4624         977 :         if (fpGZip)
    4625             :         {
    4626         977 :             VSIFWriteL(oTileBuffer.data(), 1, oTileBuffer.size(), fpGZip);
    4627         977 :             VSIFCloseL(fpGZip);
    4628             : 
    4629         977 :             vsi_l_offset nCompressedSize = 0;
    4630             :             GByte *pabyCompressed =
    4631         977 :                 VSIGetMemFileBuffer(osTmpFilename, &nCompressedSize, false);
    4632             :             oTileBuffer.assign(reinterpret_cast<char *>(pabyCompressed),
    4633         977 :                                static_cast<size_t>(nCompressedSize));
    4634             :         }
    4635         977 :         VSIUnlink(osTmpFilename);
    4636             :     }
    4637         977 : }
    4638             : 
    4639             : /************************************************************************/
    4640             : /*                     GetReducedPrecisionGeometry()                    */
    4641             : /************************************************************************/
    4642             : 
    4643             : static std::vector<GUInt32>
    4644         167 : GetReducedPrecisionGeometry(MVTTileLayerFeature::GeomType eGeomType,
    4645             :                             const std::vector<GUInt32> &anSrcGeometry,
    4646             :                             GUInt32 nSrcExtent, GUInt32 nDstExtent)
    4647             : {
    4648         167 :     std::vector<GUInt32> anDstGeometry;
    4649         167 :     size_t nLastMoveToIdx = 0;
    4650         167 :     int nX = 0;
    4651         167 :     int nY = 0;
    4652         167 :     int nFirstReducedX = 0;
    4653         167 :     int nFirstReducedY = 0;
    4654         167 :     int nLastReducedX = 0;
    4655         167 :     int nLastReducedY = 0;
    4656         167 :     int nLastReducedXValid = 0;
    4657         167 :     int nLastReducedYValid = 0;
    4658         167 :     std::unique_ptr<OGRLinearRing> poInRing;
    4659         167 :     std::unique_ptr<OGRLinearRing> poOutRing;
    4660         167 :     std::unique_ptr<OGRLinearRing> poOutOuterRing;
    4661         167 :     bool bDiscardInnerRings = false;
    4662         167 :     const bool bIsPoly = eGeomType == MVTTileLayerFeature::GeomType::POLYGON;
    4663         506 :     for (size_t iSrc = 0; iSrc < anSrcGeometry.size();)
    4664             :     {
    4665         339 :         const unsigned nCount = GetCmdCount(anSrcGeometry[iSrc]);
    4666         339 :         switch (GetCmdId(anSrcGeometry[iSrc]))
    4667             :         {
    4668         185 :             case knCMD_MOVETO:
    4669             :             {
    4670         185 :                 nLastMoveToIdx = anDstGeometry.size();
    4671             : 
    4672         185 :                 anDstGeometry.push_back(anSrcGeometry[iSrc]);
    4673         185 :                 iSrc++;
    4674             : 
    4675         185 :                 unsigned nDstPoints = 0;
    4676         185 :                 for (unsigned j = 0;
    4677         370 :                      iSrc + 1 < anSrcGeometry.size() && j < nCount;
    4678         185 :                      j++, iSrc += 2)
    4679             :                 {
    4680         185 :                     nX += DecodeSInt(anSrcGeometry[iSrc]);
    4681         185 :                     nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
    4682             : 
    4683         185 :                     int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
    4684         185 :                                                      nDstExtent / nSrcExtent);
    4685         185 :                     int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
    4686         185 :                                                      nDstExtent / nSrcExtent);
    4687         185 :                     int nDiffX = nReducedX - nLastReducedX;
    4688         185 :                     int nDiffY = nReducedY - nLastReducedY;
    4689         185 :                     if (j == 0)
    4690             :                     {
    4691         185 :                         if (bIsPoly)
    4692             :                         {
    4693          82 :                             poInRing = std::unique_ptr<OGRLinearRing>(
    4694          82 :                                 new OGRLinearRing());
    4695          82 :                             poOutRing = std::unique_ptr<OGRLinearRing>(
    4696          82 :                                 new OGRLinearRing());
    4697             :                         }
    4698         185 :                         nFirstReducedX = nReducedX;
    4699         185 :                         nFirstReducedY = nReducedY;
    4700             :                     }
    4701         185 :                     if (j == 0 || nDiffX != 0 || nDiffY != 0)
    4702             :                     {
    4703         185 :                         if (bIsPoly)
    4704             :                         {
    4705          41 :                             poInRing->addPoint(nX, nY);
    4706          41 :                             poOutRing->addPoint(nReducedX, nReducedY);
    4707             :                         }
    4708         185 :                         nDstPoints++;
    4709         185 :                         anDstGeometry.push_back(EncodeSInt(nDiffX));
    4710         185 :                         anDstGeometry.push_back(EncodeSInt(nDiffY));
    4711         185 :                         nLastReducedX = nReducedX;
    4712         185 :                         nLastReducedY = nReducedY;
    4713             :                     }
    4714             :                 }
    4715             :                 // Patch count of MOVETO
    4716         185 :                 anDstGeometry[nLastMoveToIdx] = GetCmdCountCombined(
    4717         185 :                     GetCmdId(anDstGeometry[nLastMoveToIdx]), nDstPoints);
    4718         185 :                 break;
    4719             :             }
    4720         113 :             case knCMD_LINETO:
    4721             :             {
    4722         113 :                 size_t nIdxToPatch = anDstGeometry.size();
    4723         113 :                 anDstGeometry.push_back(anSrcGeometry[iSrc]);
    4724         113 :                 iSrc++;
    4725         113 :                 unsigned nDstPoints = 0;
    4726         113 :                 int nLastReducedXBefore = nLastReducedX;
    4727         113 :                 int nLastReducedYBefore = nLastReducedY;
    4728         113 :                 for (unsigned j = 0;
    4729         267 :                      iSrc + 1 < anSrcGeometry.size() && j < nCount;
    4730         154 :                      j++, iSrc += 2)
    4731             :                 {
    4732         154 :                     nX += DecodeSInt(anSrcGeometry[iSrc]);
    4733         154 :                     nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
    4734             : 
    4735         154 :                     int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
    4736         154 :                                                      nDstExtent / nSrcExtent);
    4737         154 :                     int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
    4738         154 :                                                      nDstExtent / nSrcExtent);
    4739         154 :                     int nDiffX = nReducedX - nLastReducedX;
    4740         154 :                     int nDiffY = nReducedY - nLastReducedY;
    4741         154 :                     if (nDiffX != 0 || nDiffY != 0)
    4742             :                     {
    4743         114 :                         if (bIsPoly)
    4744             :                         {
    4745          60 :                             CPLAssert(poInRing);
    4746          60 :                             CPLAssert(poOutRing);
    4747          60 :                             poInRing->addPoint(nX, nY);
    4748          60 :                             poOutRing->addPoint(nReducedX, nReducedY);
    4749             :                         }
    4750         114 :                         nDstPoints++;
    4751         114 :                         anDstGeometry.push_back(EncodeSInt(nDiffX));
    4752         114 :                         anDstGeometry.push_back(EncodeSInt(nDiffY));
    4753         114 :                         nLastReducedXBefore = nLastReducedX;
    4754         114 :                         nLastReducedYBefore = nLastReducedY;
    4755         114 :                         nLastReducedX = nReducedX;
    4756         114 :                         nLastReducedY = nReducedY;
    4757             :                     }
    4758             :                 }
    4759             : 
    4760             :                 // If last point of ring is identical to first one, discard it
    4761         113 :                 if (nDstPoints > 0 && bIsPoly &&
    4762           1 :                     nLastReducedX == nFirstReducedX &&
    4763             :                     nLastReducedY == nFirstReducedY)
    4764             :                 {
    4765           0 :                     nLastReducedX = nLastReducedXBefore;
    4766           0 :                     nLastReducedY = nLastReducedYBefore;
    4767           0 :                     nDstPoints -= 1;
    4768           0 :                     anDstGeometry.resize(anDstGeometry.size() - 2);
    4769           0 :                     poOutRing->setNumPoints(poOutRing->getNumPoints() - 1);
    4770             :                 }
    4771             : 
    4772             :                 // Patch count of LINETO
    4773         113 :                 anDstGeometry[nIdxToPatch] = GetCmdCountCombined(
    4774         113 :                     GetCmdId(anDstGeometry[nIdxToPatch]), nDstPoints);
    4775             : 
    4776             :                 // A valid linestring should have at least one MOVETO +
    4777             :                 // one coord pair + one LINETO + one coord pair
    4778         113 :                 if (eGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
    4779             :                 {
    4780          72 :                     if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2)
    4781             :                     {
    4782             :                         // Remove last linestring
    4783          18 :                         nLastReducedX = nLastReducedXValid;
    4784          18 :                         nLastReducedY = nLastReducedYValid;
    4785          18 :                         anDstGeometry.resize(nLastMoveToIdx);
    4786             :                     }
    4787             :                     else
    4788             :                     {
    4789          54 :                         nLastReducedXValid = nLastReducedX;
    4790          54 :                         nLastReducedYValid = nLastReducedY;
    4791             :                     }
    4792             :                 }
    4793             : 
    4794         113 :                 break;
    4795             :             }
    4796          41 :             case knCMD_CLOSEPATH:
    4797             :             {
    4798          41 :                 CPLAssert(bIsPoly);
    4799          41 :                 CPLAssert(poInRing);
    4800          41 :                 CPLAssert(poOutRing);
    4801          41 :                 int bIsValid = true;
    4802             : 
    4803             :                 // A valid ring should have at least one MOVETO + one
    4804             :                 // coord pair + one LINETO + two coord pairs
    4805          41 :                 if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2 * 2)
    4806             :                 {
    4807             :                     // Remove ring. Normally if we remove an outer ring,
    4808             :                     // its inner rings should also be removed, given they are
    4809             :                     // smaller than the outer ring.
    4810          14 :                     bIsValid = false;
    4811             :                 }
    4812             :                 else
    4813             :                 {
    4814          27 :                     poInRing->closeRings();
    4815          27 :                     poOutRing->closeRings();
    4816          27 :                     bool bIsOuterRing = !poInRing->isClockwise();
    4817             :                     // Normally the first ring of a polygon geometry should
    4818             :                     // be a outer ring, except when it is degenerate enough
    4819             :                     // in which case poOutOuterRing might be null.
    4820          27 :                     if (bIsOuterRing)
    4821             :                     {
    4822             :                         // if the outer ring turned out to be a inner ring
    4823             :                         // once reduced
    4824          18 :                         if (poOutRing->isClockwise())
    4825             :                         {
    4826           0 :                             bIsValid = false;
    4827           0 :                             bDiscardInnerRings = true;
    4828             :                         }
    4829             :                         else
    4830             :                         {
    4831          18 :                             OGRPolygon oPoly;
    4832          18 :                             oPoly.addRing(poOutRing.get());
    4833          36 :                             poOutOuterRing = std::unique_ptr<OGRLinearRing>(
    4834          18 :                                 poOutRing.release());
    4835             :                             {
    4836             :                                 CPLErrorStateBackuper oErrorStateBackuper(
    4837          18 :                                     CPLQuietErrorHandler);
    4838          18 :                                 bIsValid = oPoly.IsValid();
    4839             :                             }
    4840          18 :                             bDiscardInnerRings = !bIsValid;
    4841             :                         }
    4842             :                     }
    4843           9 :                     else if (bDiscardInnerRings ||
    4844          18 :                              poOutOuterRing.get() == nullptr ||
    4845             :                              // if the inner ring turned out to be a outer ring
    4846             :                              // once reduced
    4847           9 :                              !poOutRing->isClockwise())
    4848             :                     {
    4849           0 :                         bIsValid = false;
    4850             :                     }
    4851             :                     else
    4852             :                     {
    4853          18 :                         OGRPolygon oPoly;
    4854           9 :                         oPoly.addRing(poOutOuterRing.get());
    4855           9 :                         oPoly.addRingDirectly(poOutRing.release());
    4856             :                         {
    4857             :                             CPLErrorStateBackuper oErrorStateBackuper(
    4858           9 :                                 CPLQuietErrorHandler);
    4859           9 :                             bIsValid = oPoly.IsValid();
    4860             :                         }
    4861             :                     }
    4862             :                 }
    4863             : 
    4864          41 :                 if (bIsValid)
    4865             :                 {
    4866          24 :                     nLastReducedXValid = nLastReducedX;
    4867          24 :                     nLastReducedYValid = nLastReducedY;
    4868          24 :                     anDstGeometry.push_back(anSrcGeometry[iSrc]);
    4869             :                 }
    4870             :                 else
    4871             :                 {
    4872             :                     // Remove this ring
    4873          17 :                     nLastReducedX = nLastReducedXValid;
    4874          17 :                     nLastReducedY = nLastReducedYValid;
    4875          17 :                     anDstGeometry.resize(nLastMoveToIdx);
    4876             :                 }
    4877             : 
    4878          41 :                 iSrc++;
    4879          41 :                 break;
    4880             :             }
    4881           0 :             default:
    4882             :             {
    4883           0 :                 CPLAssert(false);
    4884             :                 break;
    4885             :             }
    4886             :         }
    4887             :     }
    4888             : 
    4889         334 :     return anDstGeometry;
    4890             : }
    4891             : 
    4892             : /************************************************************************/
    4893             : /*                          EncodeFeature()                             */
    4894             : /************************************************************************/
    4895             : 
    4896        1705 : void OGRMVTWriterDataset::EncodeFeature(
    4897             :     const void *pabyBlob, int nBlobSize,
    4898             :     std::shared_ptr<MVTTileLayer> &poTargetLayer,
    4899             :     std::map<CPLString, GUInt32> &oMapKeyToIdx,
    4900             :     std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
    4901             :     MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
    4902             :     unsigned &nFeaturesInTile)
    4903             : {
    4904        1705 :     size_t nUncompressedSize = 0;
    4905             :     void *pCompressed =
    4906        1705 :         CPLZLibInflate(pabyBlob, nBlobSize, nullptr, 0, &nUncompressedSize);
    4907        1705 :     GByte *pabyUncompressed = static_cast<GByte *>(pCompressed);
    4908             : 
    4909        3410 :     MVTTileLayer oSrcTileLayer;
    4910        1705 :     if (nUncompressedSize &&
    4911        1705 :         oSrcTileLayer.read(pabyUncompressed,
    4912        1705 :                            pabyUncompressed + nUncompressedSize))
    4913             :     {
    4914        1705 :         const auto &srcFeatures = oSrcTileLayer.getFeatures();
    4915        1705 :         if (srcFeatures.size() == 1)  // should always be true !
    4916             :         {
    4917        1705 :             const auto &poSrcFeature = srcFeatures[0];
    4918             :             std::shared_ptr<MVTTileLayerFeature> poFeature(
    4919        3410 :                 new MVTTileLayerFeature());
    4920             : 
    4921        1705 :             if (poSrcFeature->hasId())
    4922          53 :                 poFeature->setId(poSrcFeature->getId());
    4923        1705 :             poFeature->setType(poSrcFeature->getType());
    4924        1705 :             if (poLayerProperties)
    4925             :             {
    4926        1526 :                 poLayerProperties->m_oCountGeomType[poSrcFeature->getType()]++;
    4927             :             }
    4928        1705 :             bool bOK = true;
    4929        1705 :             if (nExtent < m_nExtent)
    4930             :             {
    4931             : #ifdef for_debugging
    4932             :                 const auto &srcKeys = oSrcTileLayer.getKeys();
    4933             :                 const auto &srcValues = oSrcTileLayer.getValues();
    4934             :                 const auto &anSrcTags = poSrcFeature->getTags();
    4935             :                 for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
    4936             :                 {
    4937             :                     GUInt32 nSrcIdxKey = anSrcTags[i];
    4938             :                     GUInt32 nSrcIdxValue = anSrcTags[i + 1];
    4939             :                     if (nSrcIdxKey < srcKeys.size() &&
    4940             :                         nSrcIdxValue < srcValues.size())
    4941             :                     {
    4942             :                         auto &osKey = srcKeys[nSrcIdxKey];
    4943             :                         auto &oValue = srcValues[nSrcIdxValue];
    4944             :                         if (osKey == "tunnus" &&
    4945             :                             oValue.getUIntValue() == 28799760)
    4946             :                         {
    4947             :                             printf("foo\n"); /* ok */
    4948             :                             break;
    4949             :                         }
    4950             :                     }
    4951             :                 }
    4952             : #endif
    4953             : 
    4954         167 :                 poFeature->setGeometry(GetReducedPrecisionGeometry(
    4955             :                     poSrcFeature->getType(), poSrcFeature->getGeometry(),
    4956             :                     m_nExtent, nExtent));
    4957         167 :                 if (poFeature->getGeometry().empty())
    4958             :                 {
    4959          23 :                     bOK = false;
    4960             :                 }
    4961             :             }
    4962             :             else
    4963             :             {
    4964        1538 :                 poFeature->setGeometry(poSrcFeature->getGeometry());
    4965             :             }
    4966        1705 :             if (bOK)
    4967             :             {
    4968        1682 :                 const auto &srcKeys = oSrcTileLayer.getKeys();
    4969        6125 :                 for (const auto &osKey : srcKeys)
    4970             :                 {
    4971        4443 :                     auto oIter = oMapKeyToIdx.find(osKey);
    4972        4443 :                     if (oIter == oMapKeyToIdx.end())
    4973             :                     {
    4974        3645 :                         oMapKeyToIdx[osKey] = poTargetLayer->addKey(osKey);
    4975             :                     }
    4976             :                 }
    4977             : 
    4978        1682 :                 const auto &srcValues = oSrcTileLayer.getValues();
    4979        6125 :                 for (const auto &oValue : srcValues)
    4980             :                 {
    4981        4443 :                     auto oIter = oMapValueToIdx.find(oValue);
    4982        4443 :                     if (oIter == oMapValueToIdx.end())
    4983             :                     {
    4984        3771 :                         oMapValueToIdx[oValue] =
    4985        3771 :                             poTargetLayer->addValue(oValue);
    4986             :                     }
    4987             :                 }
    4988             : 
    4989        1682 :                 const auto &anSrcTags = poSrcFeature->getTags();
    4990        6125 :                 for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
    4991             :                 {
    4992        4443 :                     GUInt32 nSrcIdxKey = anSrcTags[i];
    4993        4443 :                     GUInt32 nSrcIdxValue = anSrcTags[i + 1];
    4994        8886 :                     if (nSrcIdxKey < srcKeys.size() &&
    4995        4443 :                         nSrcIdxValue < srcValues.size())
    4996             :                     {
    4997        4443 :                         const auto &osKey = srcKeys[nSrcIdxKey];
    4998        4443 :                         const auto &oValue = srcValues[nSrcIdxValue];
    4999             : 
    5000        4443 :                         if (poLayerProperties)
    5001             :                         {
    5002        4353 :                             UpdateLayerProperties(poLayerProperties, osKey,
    5003             :                                                   oValue);
    5004             :                         }
    5005             : 
    5006        4443 :                         poFeature->addTag(oMapKeyToIdx[osKey]);
    5007        4443 :                         poFeature->addTag(oMapValueToIdx[oValue]);
    5008             :                     }
    5009             :                 }
    5010             : 
    5011        1682 :                 nFeaturesInTile++;
    5012        1682 :                 poTargetLayer->addFeature(std::move(poFeature));
    5013             :             }
    5014             :         }
    5015             :     }
    5016             :     else
    5017             :     {
    5018             :         // Shouldn't fail
    5019           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Deserialization failure");
    5020             :     }
    5021             : 
    5022        1705 :     CPLFree(pabyUncompressed);
    5023        1705 : }
    5024             : 
    5025             : /************************************************************************/
    5026             : /*                            EncodeTile()                              */
    5027             : /************************************************************************/
    5028             : 
    5029         870 : std::string OGRMVTWriterDataset::EncodeTile(
    5030             :     int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer, sqlite3_stmt *hStmtRows,
    5031             :     std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
    5032             :     std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead)
    5033             : {
    5034        1740 :     MVTTile oTargetTile;
    5035             : 
    5036         870 :     sqlite3_bind_int(hStmtLayer, 1, nZ);
    5037         870 :     sqlite3_bind_int(hStmtLayer, 2, nX);
    5038         870 :     sqlite3_bind_int(hStmtLayer, 3, nY);
    5039             : 
    5040         870 :     unsigned nFeaturesInTile = 0;
    5041             :     const GIntBig nProgressStep =
    5042         870 :         std::max(static_cast<GIntBig>(1), m_nTempTiles / 10);
    5043             : 
    5044        4350 :     while (nFeaturesInTile < m_nMaxFeatures &&
    5045        2169 :            sqlite3_step(hStmtLayer) == SQLITE_ROW)
    5046             :     {
    5047             :         const char *pszLayerName =
    5048        1311 :             reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
    5049        1311 :         sqlite3_bind_int(hStmtRows, 1, nZ);
    5050        1311 :         sqlite3_bind_int(hStmtRows, 2, nX);
    5051        1311 :         sqlite3_bind_int(hStmtRows, 3, nY);
    5052        1311 :         sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
    5053             : 
    5054        1311 :         auto oIterMapLayerProps = oMapLayerProps.find(pszLayerName);
    5055        1311 :         MVTLayerProperties *poLayerProperties = nullptr;
    5056        1311 :         if (oIterMapLayerProps == oMapLayerProps.end())
    5057             :         {
    5058          76 :             if (oSetLayers.size() < knMAX_COUNT_LAYERS)
    5059             :             {
    5060          76 :                 oSetLayers.insert(pszLayerName);
    5061          76 :                 if (oMapLayerProps.size() < knMAX_REPORT_LAYERS)
    5062             :                 {
    5063          76 :                     MVTLayerProperties props;
    5064          76 :                     props.m_nMinZoom = nZ;
    5065          76 :                     props.m_nMaxZoom = nZ;
    5066          76 :                     oMapLayerProps[pszLayerName] = std::move(props);
    5067          76 :                     poLayerProperties = &(oMapLayerProps[pszLayerName]);
    5068             :                 }
    5069             :             }
    5070             :         }
    5071             :         else
    5072             :         {
    5073        1235 :             poLayerProperties = &(oIterMapLayerProps->second);
    5074             :         }
    5075        1311 :         if (poLayerProperties)
    5076             :         {
    5077        1311 :             poLayerProperties->m_nMinZoom =
    5078        1311 :                 std::min(nZ, poLayerProperties->m_nMinZoom);
    5079        1311 :             poLayerProperties->m_nMaxZoom =
    5080        1311 :                 std::max(nZ, poLayerProperties->m_nMaxZoom);
    5081             :         }
    5082             : 
    5083        2622 :         std::shared_ptr<MVTTileLayer> poTargetLayer(new MVTTileLayer());
    5084        1311 :         oTargetTile.addLayer(poTargetLayer);
    5085        1311 :         poTargetLayer->setName(pszLayerName);
    5086        1311 :         poTargetLayer->setVersion(m_nMVTVersion);
    5087        1311 :         poTargetLayer->setExtent(m_nExtent);
    5088             : 
    5089        2622 :         std::map<CPLString, GUInt32> oMapKeyToIdx;
    5090        2622 :         std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
    5091             : 
    5092        5662 :         while (nFeaturesInTile < m_nMaxFeatures &&
    5093        2825 :                sqlite3_step(hStmtRows) == SQLITE_ROW)
    5094             :         {
    5095        1526 :             int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
    5096        1526 :             const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
    5097             : 
    5098        1526 :             EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
    5099             :                           oMapValueToIdx, poLayerProperties, m_nExtent,
    5100             :                           nFeaturesInTile);
    5101             : 
    5102        1526 :             nTempTilesRead++;
    5103        1526 :             if (nTempTilesRead == m_nTempTiles ||
    5104        1474 :                 (nTempTilesRead % nProgressStep) == 0)
    5105             :             {
    5106         534 :                 const int nPct =
    5107         534 :                     static_cast<int>((100 * nTempTilesRead) / m_nTempTiles);
    5108         534 :                 CPLDebug("MVT", "%d%%...", nPct);
    5109             :             }
    5110             :         }
    5111        1311 :         sqlite3_reset(hStmtRows);
    5112             :     }
    5113             : 
    5114         870 :     sqlite3_reset(hStmtLayer);
    5115             : 
    5116        1740 :     std::string oTileBuffer(oTargetTile.write());
    5117         870 :     size_t nSizeBefore = oTileBuffer.size();
    5118         870 :     if (m_bGZip)
    5119         870 :         GZIPCompress(oTileBuffer);
    5120         870 :     const size_t nSizeAfter = oTileBuffer.size();
    5121         870 :     const double dfCompressionRatio =
    5122         870 :         static_cast<double>(nSizeAfter) / nSizeBefore;
    5123             : 
    5124         870 :     const bool bTooManyFeatures = nFeaturesInTile >= m_nMaxFeatures;
    5125         870 :     if (bTooManyFeatures && !m_bMaxFeaturesOptSpecified)
    5126             :     {
    5127           1 :         m_bMaxFeaturesOptSpecified = true;
    5128           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    5129             :                  "At least one tile exceeded the default maximum number of "
    5130             :                  "features per tile (%u) and was truncated to satisfy it.",
    5131             :                  m_nMaxFeatures);
    5132             :     }
    5133             : 
    5134             :     // If the tile size is above the allowed values or there are too many
    5135             :     // features, then sort by descending area / length until we get to the
    5136             :     // limit.
    5137         870 :     bool bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
    5138         870 :     if (bTooBigTile && !m_bMaxTileSizeOptSpecified)
    5139             :     {
    5140           1 :         m_bMaxTileSizeOptSpecified = true;
    5141           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    5142             :                  "At least one tile exceeded the default maximum tile size of "
    5143             :                  "%u bytes and was encoded at lower resolution",
    5144             :                  m_nMaxTileSize);
    5145             :     }
    5146             : 
    5147         870 :     GUInt32 nExtent = m_nExtent;
    5148         952 :     while (bTooBigTile && !bTooManyFeatures && nExtent >= 256)
    5149             :     {
    5150          82 :         nExtent /= 2;
    5151          82 :         nSizeBefore = oTileBuffer.size();
    5152         164 :         oTileBuffer = RecodeTileLowerResolution(nZ, nX, nY, nExtent, hStmtLayer,
    5153          82 :                                                 hStmtRows);
    5154          82 :         bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
    5155          82 :         CPLDebug("MVT",
    5156             :                  "Recoding tile %d/%d/%d with extent = %u. "
    5157             :                  "From %u to %u bytes",
    5158             :                  nZ, nX, nY, nExtent, static_cast<unsigned>(nSizeBefore),
    5159          82 :                  static_cast<unsigned>(oTileBuffer.size()));
    5160             :     }
    5161             : 
    5162         870 :     if (bTooBigTile || bTooManyFeatures)
    5163             :     {
    5164          25 :         if (bTooBigTile)
    5165             :         {
    5166          13 :             CPLDebug("MVT", "For tile %d/%d/%d, tile size is %u > %u", nZ, nX,
    5167          13 :                      nY, static_cast<unsigned>(oTileBuffer.size()),
    5168             :                      m_nMaxTileSize);
    5169             :         }
    5170          25 :         if (bTooManyFeatures)
    5171             :         {
    5172          12 :             CPLDebug("MVT",
    5173             :                      "For tile %d/%d/%d, feature count limit of %u is reached",
    5174             :                      nZ, nX, nY, m_nMaxFeatures);
    5175             :         }
    5176             : 
    5177          25 :         oTargetTile.clear();
    5178             : 
    5179             :         const unsigned nTotalFeaturesInTile =
    5180          25 :             std::min(m_nMaxFeatures, nFeaturesInTile);
    5181             :         char *pszSQL =
    5182          25 :             sqlite3_mprintf("SELECT layer, feature FROM temp "
    5183             :                             "WHERE z = %d AND x = %d AND y = %d ORDER BY "
    5184             :                             "area_or_length DESC LIMIT %d",
    5185             :                             nZ, nX, nY, nTotalFeaturesInTile);
    5186          25 :         sqlite3_stmt *hTmpStmt = nullptr;
    5187          25 :         CPL_IGNORE_RET_VAL(
    5188          25 :             sqlite3_prepare_v2(m_hDB, pszSQL, -1, &hTmpStmt, nullptr));
    5189          25 :         sqlite3_free(pszSQL);
    5190          25 :         if (!hTmpStmt)
    5191           0 :             return std::string();
    5192             : 
    5193             :         class TargetTileLayerProps
    5194             :         {
    5195             :           public:
    5196             :             std::shared_ptr<MVTTileLayer> m_poLayer;
    5197             :             std::map<CPLString, GUInt32> m_oMapKeyToIdx;
    5198             :             std::map<MVTTileLayerValue, GUInt32> m_oMapValueToIdx;
    5199             :         };
    5200             : 
    5201          50 :         std::map<std::string, TargetTileLayerProps> oMapLayerNameToTargetLayer;
    5202             : 
    5203          25 :         nFeaturesInTile = 0;
    5204          25 :         const unsigned nCheckStep = std::max(1U, nTotalFeaturesInTile / 100);
    5205          49 :         while (sqlite3_step(hTmpStmt) == SQLITE_ROW)
    5206             :         {
    5207             :             const char *pszLayerName = reinterpret_cast<const char *>(
    5208          37 :                 sqlite3_column_text(hTmpStmt, 0));
    5209          37 :             int nBlobSize = sqlite3_column_bytes(hTmpStmt, 1);
    5210          37 :             const void *pabyBlob = sqlite3_column_blob(hTmpStmt, 1);
    5211             : 
    5212           0 :             std::shared_ptr<MVTTileLayer> poTargetLayer;
    5213             :             std::map<CPLString, GUInt32> *poMapKeyToIdx;
    5214             :             std::map<MVTTileLayerValue, GUInt32> *poMapValueToIdx;
    5215          37 :             auto oIter = oMapLayerNameToTargetLayer.find(pszLayerName);
    5216          37 :             if (oIter == oMapLayerNameToTargetLayer.end())
    5217             :             {
    5218             :                 poTargetLayer =
    5219          25 :                     std::shared_ptr<MVTTileLayer>(new MVTTileLayer());
    5220          25 :                 TargetTileLayerProps props;
    5221          25 :                 props.m_poLayer = poTargetLayer;
    5222          25 :                 oTargetTile.addLayer(poTargetLayer);
    5223          25 :                 poTargetLayer->setName(pszLayerName);
    5224          25 :                 poTargetLayer->setVersion(m_nMVTVersion);
    5225          25 :                 poTargetLayer->setExtent(nExtent);
    5226          25 :                 oMapLayerNameToTargetLayer[pszLayerName] = std::move(props);
    5227          25 :                 poMapKeyToIdx =
    5228          25 :                     &oMapLayerNameToTargetLayer[pszLayerName].m_oMapKeyToIdx;
    5229          25 :                 poMapValueToIdx =
    5230          25 :                     &oMapLayerNameToTargetLayer[pszLayerName].m_oMapValueToIdx;
    5231             :             }
    5232             :             else
    5233             :             {
    5234          12 :                 poTargetLayer = oIter->second.m_poLayer;
    5235          12 :                 poMapKeyToIdx = &oIter->second.m_oMapKeyToIdx;
    5236          12 :                 poMapValueToIdx = &oIter->second.m_oMapValueToIdx;
    5237             :             }
    5238             : 
    5239          37 :             EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, *poMapKeyToIdx,
    5240             :                           *poMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
    5241             : 
    5242          37 :             if (nFeaturesInTile == nTotalFeaturesInTile ||
    5243          18 :                 (bTooBigTile && (nFeaturesInTile % nCheckStep == 0)))
    5244             :             {
    5245          37 :                 if (oTargetTile.getSize() * dfCompressionRatio > m_nMaxTileSize)
    5246             :                 {
    5247          13 :                     break;
    5248             :                 }
    5249             :             }
    5250             :         }
    5251             : 
    5252          25 :         oTileBuffer = oTargetTile.write();
    5253          25 :         if (m_bGZip)
    5254          25 :             GZIPCompress(oTileBuffer);
    5255             : 
    5256          25 :         if (bTooBigTile)
    5257             :         {
    5258          13 :             CPLDebug("MVT", "For tile %d/%d/%d, final tile size is %u", nZ, nX,
    5259          13 :                      nY, static_cast<unsigned>(oTileBuffer.size()));
    5260             :         }
    5261             : 
    5262          25 :         sqlite3_finalize(hTmpStmt);
    5263             :     }
    5264             : 
    5265         870 :     return oTileBuffer;
    5266             : }
    5267             : 
    5268             : /************************************************************************/
    5269             : /*                    RecodeTileLowerResolution()                       */
    5270             : /************************************************************************/
    5271             : 
    5272          82 : std::string OGRMVTWriterDataset::RecodeTileLowerResolution(
    5273             :     int nZ, int nX, int nY, int nExtent, sqlite3_stmt *hStmtLayer,
    5274             :     sqlite3_stmt *hStmtRows)
    5275             : {
    5276         164 :     MVTTile oTargetTile;
    5277             : 
    5278          82 :     sqlite3_bind_int(hStmtLayer, 1, nZ);
    5279          82 :     sqlite3_bind_int(hStmtLayer, 2, nX);
    5280          82 :     sqlite3_bind_int(hStmtLayer, 3, nY);
    5281             : 
    5282          82 :     unsigned nFeaturesInTile = 0;
    5283         328 :     while (nFeaturesInTile < m_nMaxFeatures &&
    5284         164 :            sqlite3_step(hStmtLayer) == SQLITE_ROW)
    5285             :     {
    5286             :         const char *pszLayerName =
    5287          82 :             reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
    5288          82 :         sqlite3_bind_int(hStmtRows, 1, nZ);
    5289          82 :         sqlite3_bind_int(hStmtRows, 2, nX);
    5290          82 :         sqlite3_bind_int(hStmtRows, 3, nY);
    5291          82 :         sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
    5292             : 
    5293         164 :         std::shared_ptr<MVTTileLayer> poTargetLayer(new MVTTileLayer());
    5294          82 :         oTargetTile.addLayer(poTargetLayer);
    5295          82 :         poTargetLayer->setName(pszLayerName);
    5296          82 :         poTargetLayer->setVersion(m_nMVTVersion);
    5297          82 :         poTargetLayer->setExtent(nExtent);
    5298             : 
    5299         164 :         std::map<CPLString, GUInt32> oMapKeyToIdx;
    5300         164 :         std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
    5301             : 
    5302         448 :         while (nFeaturesInTile < m_nMaxFeatures &&
    5303         224 :                sqlite3_step(hStmtRows) == SQLITE_ROW)
    5304             :         {
    5305         142 :             int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
    5306         142 :             const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
    5307             : 
    5308         142 :             EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
    5309             :                           oMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
    5310             :         }
    5311          82 :         sqlite3_reset(hStmtRows);
    5312             :     }
    5313             : 
    5314          82 :     sqlite3_reset(hStmtLayer);
    5315             : 
    5316          82 :     std::string oTileBuffer(oTargetTile.write());
    5317          82 :     if (m_bGZip)
    5318          82 :         GZIPCompress(oTileBuffer);
    5319             : 
    5320         164 :     return oTileBuffer;
    5321             : }
    5322             : 
    5323             : /************************************************************************/
    5324             : /*                            CreateOutput()                            */
    5325             : /************************************************************************/
    5326             : 
    5327         123 : bool OGRMVTWriterDataset::CreateOutput()
    5328             : {
    5329         123 :     if (m_bThreadPoolOK)
    5330         120 :         m_oThreadPool.WaitCompletion();
    5331             : 
    5332         246 :     std::map<CPLString, MVTLayerProperties> oMapLayerProps;
    5333         246 :     std::set<CPLString> oSetLayers;
    5334             : 
    5335         123 :     if (!m_oEnvelope.IsInit())
    5336             :     {
    5337          50 :         return GenerateMetadata(0, oMapLayerProps);
    5338             :     }
    5339             : 
    5340          73 :     CPLDebug("MVT", "Building output file from temporary database...");
    5341             : 
    5342          73 :     sqlite3_stmt *hStmtZXY = nullptr;
    5343          73 :     CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    5344             :         m_hDB, "SELECT DISTINCT z, x, y FROM temp ORDER BY z, x, y", -1,
    5345             :         &hStmtZXY, nullptr));
    5346          73 :     if (hStmtZXY == nullptr)
    5347             :     {
    5348           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5349           2 :         return false;
    5350             :     }
    5351             : 
    5352          71 :     sqlite3_stmt *hStmtLayer = nullptr;
    5353          71 :     CPL_IGNORE_RET_VAL(
    5354          71 :         sqlite3_prepare_v2(m_hDB,
    5355             :                            "SELECT DISTINCT layer FROM temp "
    5356             :                            "WHERE z = ? AND x = ? AND y = ? ORDER BY layer",
    5357             :                            -1, &hStmtLayer, nullptr));
    5358          71 :     if (hStmtLayer == nullptr)
    5359             :     {
    5360           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5361           0 :         sqlite3_finalize(hStmtZXY);
    5362           0 :         return false;
    5363             :     }
    5364          71 :     sqlite3_stmt *hStmtRows = nullptr;
    5365          71 :     CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    5366             :         m_hDB,
    5367             :         "SELECT feature FROM temp "
    5368             :         "WHERE z = ? AND x = ? AND y = ? AND layer = ? ORDER BY idx",
    5369             :         -1, &hStmtRows, nullptr));
    5370          71 :     if (hStmtRows == nullptr)
    5371             :     {
    5372           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5373           0 :         sqlite3_finalize(hStmtZXY);
    5374           0 :         sqlite3_finalize(hStmtLayer);
    5375           0 :         return false;
    5376             :     }
    5377             : 
    5378          71 :     sqlite3_stmt *hInsertStmt = nullptr;
    5379          71 :     if (m_hDBMBTILES)
    5380             :     {
    5381          43 :         CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    5382             :             m_hDBMBTILES,
    5383             :             "INSERT INTO tiles(zoom_level, tile_column, tile_row, "
    5384             :             "tile_data) VALUES (?,?,?,?)",
    5385             :             -1, &hInsertStmt, nullptr));
    5386          43 :         if (hInsertStmt == nullptr)
    5387             :         {
    5388           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
    5389           0 :             sqlite3_finalize(hStmtZXY);
    5390           0 :             sqlite3_finalize(hStmtLayer);
    5391           0 :             sqlite3_finalize(hStmtRows);
    5392           0 :             return false;
    5393             :         }
    5394             :     }
    5395             : 
    5396          71 :     int nLastZ = -1;
    5397          71 :     int nLastX = -1;
    5398          71 :     bool bRet = true;
    5399          71 :     GIntBig nTempTilesRead = 0;
    5400             : 
    5401         940 :     while (sqlite3_step(hStmtZXY) == SQLITE_ROW)
    5402             :     {
    5403         870 :         int nZ = sqlite3_column_int(hStmtZXY, 0);
    5404         870 :         int nX = sqlite3_column_int(hStmtZXY, 1);
    5405         870 :         int nY = sqlite3_column_int(hStmtZXY, 2);
    5406             : 
    5407             :         std::string oTileBuffer(EncodeTile(nZ, nX, nY, hStmtLayer, hStmtRows,
    5408             :                                            oMapLayerProps, oSetLayers,
    5409         870 :                                            nTempTilesRead));
    5410             : 
    5411         870 :         if (oTileBuffer.empty())
    5412             :         {
    5413           0 :             bRet = false;
    5414             :         }
    5415         870 :         else if (hInsertStmt)
    5416             :         {
    5417         531 :             sqlite3_bind_int(hInsertStmt, 1, nZ);
    5418         531 :             sqlite3_bind_int(hInsertStmt, 2, nX);
    5419         531 :             sqlite3_bind_int(hInsertStmt, 3, (1 << nZ) - 1 - nY);
    5420         531 :             sqlite3_bind_blob(hInsertStmt, 4, oTileBuffer.data(),
    5421         531 :                               static_cast<int>(oTileBuffer.size()),
    5422             :                               SQLITE_STATIC);
    5423         531 :             const int rc = sqlite3_step(hInsertStmt);
    5424         531 :             bRet = (rc == SQLITE_OK || rc == SQLITE_DONE);
    5425         531 :             sqlite3_reset(hInsertStmt);
    5426             :         }
    5427             :         else
    5428             :         {
    5429             :             const std::string osZDirname(CPLFormFilenameSafe(
    5430         678 :                 GetDescription(), CPLSPrintf("%d", nZ), nullptr));
    5431             :             const std::string osXDirname(CPLFormFilenameSafe(
    5432         678 :                 osZDirname.c_str(), CPLSPrintf("%d", nX), nullptr));
    5433         339 :             if (nZ != nLastZ)
    5434             :             {
    5435         114 :                 VSIMkdir(osZDirname.c_str(), 0755);
    5436         114 :                 nLastZ = nZ;
    5437         114 :                 nLastX = -1;
    5438             :             }
    5439         339 :             if (nX != nLastX)
    5440             :             {
    5441         194 :                 VSIMkdir(osXDirname.c_str(), 0755);
    5442         194 :                 nLastX = nX;
    5443             :             }
    5444             :             const std::string osTileFilename(
    5445             :                 CPLFormFilenameSafe(osXDirname.c_str(), CPLSPrintf("%d", nY),
    5446         678 :                                     m_osExtension.c_str()));
    5447         339 :             VSILFILE *fpOut = VSIFOpenL(osTileFilename.c_str(), "wb");
    5448         339 :             if (fpOut)
    5449             :             {
    5450         338 :                 const size_t nRet = VSIFWriteL(oTileBuffer.data(), 1,
    5451             :                                                oTileBuffer.size(), fpOut);
    5452         338 :                 bRet = (nRet == oTileBuffer.size());
    5453         338 :                 VSIFCloseL(fpOut);
    5454             :             }
    5455             :             else
    5456             :             {
    5457           1 :                 bRet = false;
    5458             :             }
    5459             :         }
    5460             : 
    5461         870 :         if (!bRet)
    5462             :         {
    5463           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    5464             :                      "Error while writing tile %d/%d/%d", nZ, nX, nY);
    5465           1 :             break;
    5466             :         }
    5467             :     }
    5468          71 :     sqlite3_finalize(hStmtZXY);
    5469          71 :     sqlite3_finalize(hStmtLayer);
    5470          71 :     sqlite3_finalize(hStmtRows);
    5471          71 :     if (hInsertStmt)
    5472          43 :         sqlite3_finalize(hInsertStmt);
    5473             : 
    5474          71 :     bRet &= GenerateMetadata(oSetLayers.size(), oMapLayerProps);
    5475             : 
    5476          71 :     return bRet;
    5477             : }
    5478             : 
    5479             : /************************************************************************/
    5480             : /*                     SphericalMercatorToLongLat()                     */
    5481             : /************************************************************************/
    5482             : 
    5483         234 : static void SphericalMercatorToLongLat(double *x, double *y)
    5484             : {
    5485         234 :     double lng = *x / kmSPHERICAL_RADIUS / M_PI * 180;
    5486             :     double lat =
    5487         234 :         2 * (atan(exp(*y / kmSPHERICAL_RADIUS)) - M_PI / 4) / M_PI * 180;
    5488         234 :     *x = lng;
    5489         234 :     *y = lat;
    5490         234 : }
    5491             : 
    5492             : /************************************************************************/
    5493             : /*                          WriteMetadataItem()                         */
    5494             : /************************************************************************/
    5495             : 
    5496             : template <class T>
    5497        1309 : static bool WriteMetadataItemT(const char *pszKey, T value,
    5498             :                                const char *pszValueFormat, sqlite3 *hDBMBTILES,
    5499             :                                CPLJSONObject &oRoot)
    5500             : {
    5501        1309 :     if (hDBMBTILES)
    5502             :     {
    5503             :         char *pszSQL;
    5504             : 
    5505         825 :         pszSQL = sqlite3_mprintf(
    5506             :             CPLSPrintf("INSERT INTO metadata(name, value) VALUES('%%q', '%s')",
    5507             :                        pszValueFormat),
    5508             :             pszKey, value);
    5509         825 :         OGRErr eErr = SQLCommand(hDBMBTILES, pszSQL);
    5510         825 :         sqlite3_free(pszSQL);
    5511         825 :         return eErr == OGRERR_NONE;
    5512             :     }
    5513             :     else
    5514             :     {
    5515         484 :         oRoot.Add(pszKey, value);
    5516         484 :         return true;
    5517             :     }
    5518             : }
    5519             : 
    5520             : /************************************************************************/
    5521             : /*                          WriteMetadataItem()                         */
    5522             : /************************************************************************/
    5523             : 
    5524         926 : static bool WriteMetadataItem(const char *pszKey, const char *pszValue,
    5525             :                               sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
    5526             : {
    5527         926 :     return WriteMetadataItemT(pszKey, pszValue, "%q", hDBMBTILES, oRoot);
    5528             : }
    5529             : 
    5530             : /************************************************************************/
    5531             : /*                          WriteMetadataItem()                         */
    5532             : /************************************************************************/
    5533             : 
    5534         371 : static bool WriteMetadataItem(const char *pszKey, int nValue,
    5535             :                               sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
    5536             : {
    5537         371 :     return WriteMetadataItemT(pszKey, nValue, "%d", hDBMBTILES, oRoot);
    5538             : }
    5539             : 
    5540             : /************************************************************************/
    5541             : /*                          WriteMetadataItem()                         */
    5542             : /************************************************************************/
    5543             : 
    5544          12 : static bool WriteMetadataItem(const char *pszKey, double dfValue,
    5545             :                               sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
    5546             : {
    5547          12 :     return WriteMetadataItemT(pszKey, dfValue, "%.17g", hDBMBTILES, oRoot);
    5548             : }
    5549             : 
    5550             : /************************************************************************/
    5551             : /*                          GenerateMetadata()                          */
    5552             : /************************************************************************/
    5553             : 
    5554         121 : bool OGRMVTWriterDataset::GenerateMetadata(
    5555             :     size_t nLayers, const std::map<CPLString, MVTLayerProperties> &oMap)
    5556             : {
    5557         242 :     CPLJSONDocument oDoc;
    5558         242 :     CPLJSONObject oRoot = oDoc.GetRoot();
    5559             : 
    5560         242 :     OGRSpatialReference oSRS_EPSG3857;
    5561             :     double dfTopXWebMercator;
    5562             :     double dfTopYWebMercator;
    5563             :     double dfTileDim0WebMercator;
    5564         121 :     InitWebMercatorTilingScheme(&oSRS_EPSG3857, dfTopXWebMercator,
    5565             :                                 dfTopYWebMercator, dfTileDim0WebMercator);
    5566             :     const bool bIsStandardTilingScheme =
    5567         238 :         m_poSRS->IsSame(&oSRS_EPSG3857) && m_dfTopX == dfTopXWebMercator &&
    5568         238 :         m_dfTopY == dfTopYWebMercator && m_dfTileDim0 == dfTileDim0WebMercator;
    5569         121 :     if (bIsStandardTilingScheme)
    5570             :     {
    5571         117 :         SphericalMercatorToLongLat(&(m_oEnvelope.MinX), &(m_oEnvelope.MinY));
    5572         117 :         SphericalMercatorToLongLat(&(m_oEnvelope.MaxX), &(m_oEnvelope.MaxY));
    5573         117 :         m_oEnvelope.MinY = std::max(-85.0, m_oEnvelope.MinY);
    5574         117 :         m_oEnvelope.MaxY = std::min(85.0, m_oEnvelope.MaxY);
    5575             :     }
    5576             :     else
    5577             :     {
    5578           8 :         OGRSpatialReference oSRS_EPSG4326;
    5579           4 :         oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
    5580           4 :         oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    5581             :         OGRCoordinateTransformation *poCT =
    5582           4 :             OGRCreateCoordinateTransformation(m_poSRS, &oSRS_EPSG4326);
    5583           4 :         if (poCT)
    5584             :         {
    5585           8 :             OGRPoint oPoint1(m_oEnvelope.MinX, m_oEnvelope.MinY);
    5586           4 :             oPoint1.transform(poCT);
    5587           8 :             OGRPoint oPoint2(m_oEnvelope.MinX, m_oEnvelope.MaxY);
    5588           4 :             oPoint2.transform(poCT);
    5589           8 :             OGRPoint oPoint3(m_oEnvelope.MaxX, m_oEnvelope.MaxY);
    5590           4 :             oPoint3.transform(poCT);
    5591           8 :             OGRPoint oPoint4(m_oEnvelope.MaxX, m_oEnvelope.MinY);
    5592           4 :             oPoint4.transform(poCT);
    5593           4 :             m_oEnvelope.MinX =
    5594           4 :                 std::min(std::min(oPoint1.getX(), oPoint2.getX()),
    5595           8 :                          std::min(oPoint3.getX(), oPoint4.getX()));
    5596           4 :             m_oEnvelope.MinY =
    5597           4 :                 std::min(std::min(oPoint1.getY(), oPoint2.getY()),
    5598           8 :                          std::min(oPoint3.getY(), oPoint4.getY()));
    5599           4 :             m_oEnvelope.MaxX =
    5600           4 :                 std::max(std::max(oPoint1.getX(), oPoint2.getX()),
    5601           8 :                          std::max(oPoint3.getX(), oPoint4.getX()));
    5602           4 :             m_oEnvelope.MaxY =
    5603           4 :                 std::max(std::max(oPoint1.getY(), oPoint2.getY()),
    5604           8 :                          std::max(oPoint3.getY(), oPoint4.getY()));
    5605           4 :             delete poCT;
    5606             :         }
    5607             :     }
    5608         121 :     const double dfCenterX = (m_oEnvelope.MinX + m_oEnvelope.MaxX) / 2;
    5609         121 :     const double dfCenterY = (m_oEnvelope.MinY + m_oEnvelope.MaxY) / 2;
    5610             :     CPLString osCenter(
    5611         242 :         CPLSPrintf("%.7f,%.7f,%d", dfCenterX, dfCenterY, m_nMinZoom));
    5612             :     CPLString osBounds(CPLSPrintf("%.7f,%.7f,%.7f,%.7f", m_oEnvelope.MinX,
    5613             :                                   m_oEnvelope.MinY, m_oEnvelope.MaxX,
    5614         242 :                                   m_oEnvelope.MaxY));
    5615             : 
    5616         121 :     WriteMetadataItem("name", m_osName, m_hDBMBTILES, oRoot);
    5617         121 :     WriteMetadataItem("description", m_osDescription, m_hDBMBTILES, oRoot);
    5618         121 :     WriteMetadataItem("version", m_nMetadataVersion, m_hDBMBTILES, oRoot);
    5619         121 :     WriteMetadataItem("minzoom", m_nMinZoom, m_hDBMBTILES, oRoot);
    5620         121 :     WriteMetadataItem("maxzoom", m_nMaxZoom, m_hDBMBTILES, oRoot);
    5621         121 :     WriteMetadataItem("center", !m_osCenter.empty() ? m_osCenter : osCenter,
    5622             :                       m_hDBMBTILES, oRoot);
    5623         121 :     WriteMetadataItem("bounds", !m_osBounds.empty() ? m_osBounds : osBounds,
    5624             :                       m_hDBMBTILES, oRoot);
    5625         121 :     WriteMetadataItem("type", m_osType, m_hDBMBTILES, oRoot);
    5626         121 :     WriteMetadataItem("format", "pbf", m_hDBMBTILES, oRoot);
    5627         121 :     if (m_hDBMBTILES)
    5628             :     {
    5629          75 :         WriteMetadataItem("scheme", "tms", m_hDBMBTILES, oRoot);
    5630             :     }
    5631             : 
    5632             :     // GDAL extension for custom tiling schemes
    5633         121 :     if (!bIsStandardTilingScheme)
    5634             :     {
    5635           4 :         const char *pszAuthName = m_poSRS->GetAuthorityName(nullptr);
    5636           4 :         const char *pszAuthCode = m_poSRS->GetAuthorityCode(nullptr);
    5637           4 :         if (pszAuthName && pszAuthCode)
    5638             :         {
    5639           4 :             WriteMetadataItem("crs",
    5640             :                               CPLSPrintf("%s:%s", pszAuthName, pszAuthCode),
    5641             :                               m_hDBMBTILES, oRoot);
    5642             :         }
    5643             :         else
    5644             :         {
    5645           0 :             char *pszWKT = nullptr;
    5646           0 :             m_poSRS->exportToWkt(&pszWKT);
    5647           0 :             WriteMetadataItem("crs", pszWKT, m_hDBMBTILES, oRoot);
    5648           0 :             CPLFree(pszWKT);
    5649             :         }
    5650           4 :         WriteMetadataItem("tile_origin_upper_left_x", m_dfTopX, m_hDBMBTILES,
    5651             :                           oRoot);
    5652           4 :         WriteMetadataItem("tile_origin_upper_left_y", m_dfTopY, m_hDBMBTILES,
    5653             :                           oRoot);
    5654           4 :         WriteMetadataItem("tile_dimension_zoom_0", m_dfTileDim0, m_hDBMBTILES,
    5655             :                           oRoot);
    5656           4 :         WriteMetadataItem("tile_matrix_width_zoom_0", m_nTileMatrixWidth0,
    5657             :                           m_hDBMBTILES, oRoot);
    5658           4 :         WriteMetadataItem("tile_matrix_height_zoom_0", m_nTileMatrixHeight0,
    5659             :                           m_hDBMBTILES, oRoot);
    5660             :     }
    5661             : 
    5662         242 :     CPLJSONDocument oJsonDoc;
    5663         242 :     CPLJSONObject oJsonRoot = oJsonDoc.GetRoot();
    5664             : 
    5665         242 :     CPLJSONArray oVectorLayers;
    5666         121 :     oJsonRoot.Add("vector_layers", oVectorLayers);
    5667         242 :     std::set<std::string> oAlreadyVisited;
    5668         289 :     for (const auto &poLayer : m_apoLayers)
    5669             :     {
    5670         168 :         auto oIter = oMap.find(poLayer->m_osTargetName);
    5671         244 :         if (oIter != oMap.end() &&
    5672          76 :             oAlreadyVisited.find(poLayer->m_osTargetName) ==
    5673         244 :                 oAlreadyVisited.end())
    5674             :         {
    5675          76 :             oAlreadyVisited.insert(poLayer->m_osTargetName);
    5676             : 
    5677         152 :             CPLJSONObject oLayerObj;
    5678          76 :             oLayerObj.Add("id", poLayer->m_osTargetName);
    5679          76 :             oLayerObj.Add("description",
    5680          76 :                           m_oMapLayerNameToDesc[poLayer->m_osTargetName]);
    5681          76 :             oLayerObj.Add("minzoom", oIter->second.m_nMinZoom);
    5682          76 :             oLayerObj.Add("maxzoom", oIter->second.m_nMaxZoom);
    5683             : 
    5684         152 :             CPLJSONObject oFields;
    5685          76 :             oLayerObj.Add("fields", oFields);
    5686          76 :             auto poFDefn = poLayer->GetLayerDefn();
    5687         280 :             for (int i = 0; i < poFDefn->GetFieldCount(); i++)
    5688             :             {
    5689         204 :                 auto poFieldDefn = poFDefn->GetFieldDefn(i);
    5690         204 :                 auto eType = poFieldDefn->GetType();
    5691         239 :                 if (eType == OFTInteger &&
    5692          35 :                     poFieldDefn->GetSubType() == OFSTBoolean)
    5693             :                 {
    5694           1 :                     oFields.Add(poFieldDefn->GetNameRef(), "Boolean");
    5695             :                 }
    5696         203 :                 else if (eType == OFTInteger || eType == OFTInteger64 ||
    5697             :                          eType == OFTReal)
    5698             :                 {
    5699          73 :                     oFields.Add(poFieldDefn->GetNameRef(), "Number");
    5700             :                 }
    5701             :                 else
    5702             :                 {
    5703         130 :                     oFields.Add(poFieldDefn->GetNameRef(), "String");
    5704             :                 }
    5705             :             }
    5706             : 
    5707          76 :             oVectorLayers.Add(oLayerObj);
    5708             :         }
    5709             :     }
    5710             : 
    5711         242 :     CPLJSONObject oTileStats;
    5712         121 :     oJsonRoot.Add("tilestats", oTileStats);
    5713         121 :     oTileStats.Add("layerCount", static_cast<int>(nLayers));
    5714         242 :     CPLJSONArray oTileStatsLayers;
    5715         121 :     oTileStats.Add("layers", oTileStatsLayers);
    5716         121 :     oAlreadyVisited.clear();
    5717         289 :     for (const auto &poLayer : m_apoLayers)
    5718             :     {
    5719         168 :         auto oIter = oMap.find(poLayer->m_osTargetName);
    5720         244 :         if (oIter != oMap.end() &&
    5721          76 :             oAlreadyVisited.find(poLayer->m_osTargetName) ==
    5722         244 :                 oAlreadyVisited.end())
    5723             :         {
    5724          76 :             oAlreadyVisited.insert(poLayer->m_osTargetName);
    5725          76 :             auto &oLayerProps = oIter->second;
    5726         152 :             CPLJSONObject oLayerObj;
    5727             : 
    5728         152 :             std::string osName(poLayer->m_osTargetName);
    5729          76 :             osName.resize(std::min(knMAX_LAYER_NAME_LENGTH, osName.size()));
    5730          76 :             oLayerObj.Add("layer", osName);
    5731          76 :             oLayerObj.Add(
    5732             :                 "count",
    5733          76 :                 m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]);
    5734             : 
    5735             :             // Find majority geometry type
    5736          76 :             MVTTileLayerFeature::GeomType eMaxGeomType =
    5737             :                 MVTTileLayerFeature::GeomType::UNKNOWN;
    5738          76 :             GIntBig nMaxCountGeom = 0;
    5739         304 :             for (int i = static_cast<int>(MVTTileLayerFeature::GeomType::POINT);
    5740         304 :                  i <= static_cast<int>(MVTTileLayerFeature::GeomType::POLYGON);
    5741             :                  i++)
    5742             :             {
    5743         228 :                 MVTTileLayerFeature::GeomType eGeomType =
    5744         228 :                     static_cast<MVTTileLayerFeature::GeomType>(i);
    5745             :                 auto oIterCountGeom =
    5746         228 :                     oLayerProps.m_oCountGeomType.find(eGeomType);
    5747         228 :                 if (oIterCountGeom != oLayerProps.m_oCountGeomType.end())
    5748             :                 {
    5749          80 :                     if (oIterCountGeom->second >= nMaxCountGeom)
    5750             :                     {
    5751          79 :                         eMaxGeomType = eGeomType;
    5752          79 :                         nMaxCountGeom = oIterCountGeom->second;
    5753             :                     }
    5754             :                 }
    5755             :             }
    5756          76 :             if (eMaxGeomType == MVTTileLayerFeature::GeomType::POINT)
    5757          63 :                 oLayerObj.Add("geometry", "Point");
    5758          13 :             else if (eMaxGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
    5759           6 :                 oLayerObj.Add("geometry", "LineString");
    5760           7 :             else if (eMaxGeomType == MVTTileLayerFeature::GeomType::POLYGON)
    5761           7 :                 oLayerObj.Add("geometry", "Polygon");
    5762             : 
    5763          76 :             oLayerObj.Add("attributeCount",
    5764          76 :                           static_cast<int>(oLayerProps.m_oSetFields.size()));
    5765         152 :             CPLJSONArray oAttributes;
    5766          76 :             oLayerObj.Add("attributes", oAttributes);
    5767         257 :             for (const auto &oFieldProps : oLayerProps.m_aoFields)
    5768             :             {
    5769         362 :                 CPLJSONObject oFieldObj;
    5770         181 :                 oAttributes.Add(oFieldObj);
    5771         362 :                 std::string osFieldNameTruncated(oFieldProps.m_osName);
    5772         181 :                 osFieldNameTruncated.resize(std::min(
    5773         181 :                     knMAX_FIELD_NAME_LENGTH, osFieldNameTruncated.size()));
    5774         181 :                 oFieldObj.Add("attribute", osFieldNameTruncated);
    5775         181 :                 oFieldObj.Add("count", static_cast<int>(
    5776         181 :                                            oFieldProps.m_oSetAllValues.size()));
    5777         181 :                 oFieldObj.Add("type",
    5778         181 :                               oFieldProps.m_eType ==
    5779             :                                       MVTTileLayerValue::ValueType::DOUBLE
    5780             :                                   ? "number"
    5781         108 :                               : oFieldProps.m_eType ==
    5782             :                                       MVTTileLayerValue::ValueType::STRING
    5783         108 :                                   ? "string"
    5784             :                                   : "boolean");
    5785             : 
    5786         362 :                 CPLJSONArray oValues;
    5787         181 :                 oFieldObj.Add("values", oValues);
    5788         408 :                 for (const auto &oIterValue : oFieldProps.m_oSetValues)
    5789             :                 {
    5790         227 :                     if (oIterValue.getType() ==
    5791             :                         MVTTileLayerValue::ValueType::BOOL)
    5792             :                     {
    5793           1 :                         oValues.Add(oIterValue.getBoolValue());
    5794             :                     }
    5795         226 :                     else if (oIterValue.isNumeric())
    5796             :                     {
    5797         104 :                         if (oFieldProps.m_bAllInt)
    5798             :                         {
    5799          53 :                             oValues.Add(static_cast<GInt64>(
    5800          53 :                                 oIterValue.getNumericValue()));
    5801             :                         }
    5802             :                         else
    5803             :                         {
    5804          51 :                             oValues.Add(oIterValue.getNumericValue());
    5805             :                         }
    5806             :                     }
    5807         122 :                     else if (oIterValue.isString())
    5808             :                     {
    5809         122 :                         oValues.Add(oIterValue.getStringValue());
    5810             :                     }
    5811             :                 }
    5812             : 
    5813         181 :                 if (oFieldProps.m_eType == MVTTileLayerValue::ValueType::DOUBLE)
    5814             :                 {
    5815          73 :                     if (oFieldProps.m_bAllInt)
    5816             :                     {
    5817          37 :                         oFieldObj.Add(
    5818          37 :                             "min", static_cast<GInt64>(oFieldProps.m_dfMinVal));
    5819          37 :                         oFieldObj.Add(
    5820          37 :                             "max", static_cast<GInt64>(oFieldProps.m_dfMaxVal));
    5821             :                     }
    5822             :                     else
    5823             :                     {
    5824          36 :                         oFieldObj.Add("min", oFieldProps.m_dfMinVal);
    5825          36 :                         oFieldObj.Add("max", oFieldProps.m_dfMaxVal);
    5826             :                     }
    5827             :                 }
    5828             :             }
    5829             : 
    5830          76 :             oTileStatsLayers.Add(oLayerObj);
    5831             :         }
    5832             :     }
    5833             : 
    5834         121 :     WriteMetadataItem("json", oJsonDoc.SaveAsString().c_str(), m_hDBMBTILES,
    5835             :                       oRoot);
    5836             : 
    5837         121 :     if (m_hDBMBTILES)
    5838             :     {
    5839          75 :         return true;
    5840             :     }
    5841             : 
    5842          46 :     return oDoc.Save(
    5843          92 :         CPLFormFilenameSafe(GetDescription(), "metadata.json", nullptr));
    5844             : }
    5845             : 
    5846             : /************************************************************************/
    5847             : /*                            WriteFeature()                            */
    5848             : /************************************************************************/
    5849             : 
    5850         251 : OGRErr OGRMVTWriterDataset::WriteFeature(OGRMVTWriterLayer *poLayer,
    5851             :                                          OGRFeature *poFeature, GIntBig nSerial,
    5852             :                                          OGRGeometry *poGeom)
    5853             : {
    5854         251 :     if (poFeature->GetGeometryRef() == poGeom)
    5855             :     {
    5856         193 :         m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]++;
    5857             :     }
    5858             : 
    5859         251 :     OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
    5860         251 :     if (eGeomType == wkbGeometryCollection)
    5861             :     {
    5862          21 :         OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    5863          77 :         for (int i = 0; i < poGC->getNumGeometries(); i++)
    5864             :         {
    5865          58 :             if (WriteFeature(poLayer, poFeature, nSerial,
    5866          58 :                              poGC->getGeometryRef(i)) != OGRERR_NONE)
    5867             :             {
    5868           2 :                 return OGRERR_FAILURE;
    5869             :             }
    5870             :         }
    5871          19 :         return OGRERR_NONE;
    5872             :     }
    5873             : 
    5874         230 :     OGREnvelope sExtent;
    5875         230 :     poGeom->getEnvelope(&sExtent);
    5876             : 
    5877         230 :     if (!m_oEnvelope.IsInit())
    5878             :     {
    5879          73 :         CPLDebug("MVT", "Creating temporary database...");
    5880             :     }
    5881             : 
    5882         230 :     m_oEnvelope.Merge(sExtent);
    5883             : 
    5884         230 :     if (!m_bReuseTempFile)
    5885             :     {
    5886             :         auto poFeatureContent =
    5887         229 :             std::shared_ptr<OGRMVTFeatureContent>(new OGRMVTFeatureContent());
    5888         229 :         auto poSharedGeom = std::shared_ptr<OGRGeometry>(poGeom->clone());
    5889             : 
    5890         229 :         poFeatureContent->nFID = poFeature->GetFID();
    5891             : 
    5892         229 :         const OGRFeatureDefn *poFDefn = poFeature->GetDefnRef();
    5893         996 :         for (int i = 0; i < poFeature->GetFieldCount(); i++)
    5894             :         {
    5895         767 :             if (poFeature->IsFieldSetAndNotNull(i))
    5896             :             {
    5897         666 :                 MVTTileLayerValue oValue;
    5898         666 :                 const OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(i);
    5899         666 :                 OGRFieldType eFieldType = poFieldDefn->GetType();
    5900         666 :                 if (eFieldType == OFTInteger || eFieldType == OFTInteger64)
    5901             :                 {
    5902         145 :                     if (poFieldDefn->GetSubType() == OFSTBoolean)
    5903             :                     {
    5904           2 :                         oValue.setBoolValue(poFeature->GetFieldAsInteger(i) !=
    5905             :                                             0);
    5906             :                     }
    5907             :                     else
    5908             :                     {
    5909         143 :                         oValue.setValue(poFeature->GetFieldAsInteger64(i));
    5910             :                     }
    5911             :                 }
    5912         521 :                 else if (eFieldType == OFTReal)
    5913             :                 {
    5914         140 :                     oValue.setValue(poFeature->GetFieldAsDouble(i));
    5915             :                 }
    5916         381 :                 else if (eFieldType == OFTDate || eFieldType == OFTDateTime)
    5917             :                 {
    5918             :                     int nYear, nMonth, nDay, nHour, nMin, nTZ;
    5919             :                     float fSec;
    5920         238 :                     poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay,
    5921             :                                                   &nHour, &nMin, &fSec, &nTZ);
    5922         476 :                     CPLString osFormatted;
    5923         238 :                     if (eFieldType == OFTDate)
    5924             :                     {
    5925         119 :                         osFormatted.Printf("%04d-%02d-%02d", nYear, nMonth,
    5926         119 :                                            nDay);
    5927             :                     }
    5928             :                     else
    5929             :                     {
    5930             :                         char *pszFormatted =
    5931         119 :                             OGRGetXMLDateTime(poFeature->GetRawFieldRef(i));
    5932         119 :                         osFormatted = pszFormatted;
    5933         119 :                         CPLFree(pszFormatted);
    5934             :                     }
    5935         476 :                     oValue.setStringValue(osFormatted);
    5936             :                 }
    5937             :                 else
    5938             :                 {
    5939         143 :                     oValue.setStringValue(
    5940         286 :                         std::string(poFeature->GetFieldAsString(i)));
    5941             :                 }
    5942             : 
    5943         666 :                 poFeatureContent->oValues.emplace_back(
    5944         666 :                     std::pair<std::string, MVTTileLayerValue>(
    5945        1332 :                         poFieldDefn->GetNameRef(), oValue));
    5946             :             }
    5947             :         }
    5948             : 
    5949        1559 :         for (int nZ = poLayer->m_nMinZoom; nZ <= poLayer->m_nMaxZoom; nZ++)
    5950             :         {
    5951        1332 :             double dfTileDim = m_dfTileDim0 / (1 << nZ);
    5952        1332 :             double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
    5953             :             const int nTileMinX = std::max(
    5954        2664 :                 0, static_cast<int>((sExtent.MinX - m_dfTopX - dfBuffer) /
    5955        1332 :                                     dfTileDim));
    5956             :             const int nTileMinY = std::max(
    5957        2664 :                 0, static_cast<int>((m_dfTopY - sExtent.MaxY - dfBuffer) /
    5958        1332 :                                     dfTileDim));
    5959             :             const int nTileMaxX =
    5960        2664 :                 std::min(static_cast<int>((sExtent.MaxX - m_dfTopX + dfBuffer) /
    5961             :                                           dfTileDim),
    5962        2664 :                          static_cast<int>(std::min<int64_t>(
    5963        2664 :                              INT_MAX, (static_cast<int64_t>(1) << nZ) *
    5964        2664 :                                               m_nTileMatrixWidth0 -
    5965        1332 :                                           1)));
    5966             :             const int nTileMaxY =
    5967        2664 :                 std::min(static_cast<int>((m_dfTopY - sExtent.MinY + dfBuffer) /
    5968             :                                           dfTileDim),
    5969        2664 :                          static_cast<int>(std::min<int64_t>(
    5970        2664 :                              INT_MAX, (static_cast<int64_t>(1) << nZ) *
    5971        2664 :                                               m_nTileMatrixHeight0 -
    5972        1332 :                                           1)));
    5973        3575 :             for (int iX = nTileMinX; iX <= nTileMaxX; iX++)
    5974             :             {
    5975        6279 :                 for (int iY = nTileMinY; iY <= nTileMaxY; iY++)
    5976             :                 {
    5977        8072 :                     if (PreGenerateForTile(
    5978        4036 :                             nZ, iX, iY, poLayer->m_osTargetName,
    5979        4036 :                             (nZ == poLayer->m_nMaxZoom), poFeatureContent,
    5980        4036 :                             nSerial, poSharedGeom, sExtent) != OGRERR_NONE)
    5981             :                     {
    5982           2 :                         return OGRERR_FAILURE;
    5983             :                     }
    5984             :                 }
    5985             :             }
    5986             :         }
    5987             :     }
    5988             : 
    5989         228 :     return OGRERR_NONE;
    5990             : }
    5991             : 
    5992             : /************************************************************************/
    5993             : /*                            TestCapability()                          */
    5994             : /************************************************************************/
    5995             : 
    5996         207 : int OGRMVTWriterDataset::TestCapability(const char *pszCap) const
    5997             : {
    5998         207 :     if (EQUAL(pszCap, ODsCCreateLayer))
    5999         117 :         return true;
    6000          90 :     return false;
    6001             : }
    6002             : 
    6003             : /************************************************************************/
    6004             : /*                         ValidateMinMaxZoom()                         */
    6005             : /************************************************************************/
    6006             : 
    6007         301 : static bool ValidateMinMaxZoom(int nMinZoom, int nMaxZoom)
    6008             : {
    6009         301 :     if (nMinZoom < 0 || nMinZoom > 22)
    6010             :     {
    6011           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid MINZOOM");
    6012           2 :         return false;
    6013             :     }
    6014         299 :     if (nMaxZoom < 0 || nMaxZoom > 22)
    6015             :     {
    6016           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM");
    6017           1 :         return false;
    6018             :     }
    6019         298 :     if (nMaxZoom < nMinZoom)
    6020             :     {
    6021           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM < MINZOOM");
    6022           1 :         return false;
    6023             :     }
    6024         297 :     return true;
    6025             : }
    6026             : 
    6027             : /************************************************************************/
    6028             : /*                           ICreateLayer()                             */
    6029             : /************************************************************************/
    6030             : 
    6031             : OGRLayer *
    6032         171 : OGRMVTWriterDataset::ICreateLayer(const char *pszLayerName,
    6033             :                                   const OGRGeomFieldDefn *poGeomFieldDefn,
    6034             :                                   CSLConstList papszOptions)
    6035             : {
    6036         171 :     OGRSpatialReference *poSRSClone = nullptr;
    6037             :     const auto poSRS =
    6038         171 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    6039         171 :     if (poSRS)
    6040             :     {
    6041           8 :         poSRSClone = poSRS->Clone();
    6042           8 :         poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6043             :     }
    6044             :     OGRMVTWriterLayer *poLayer =
    6045         171 :         new OGRMVTWriterLayer(this, pszLayerName, poSRSClone);
    6046         171 :     if (poSRSClone)
    6047           8 :         poSRSClone->Release();
    6048         171 :     poLayer->m_nMinZoom = m_nMinZoom;
    6049         171 :     poLayer->m_nMaxZoom = m_nMaxZoom;
    6050         171 :     poLayer->m_osTargetName = pszLayerName;
    6051             : 
    6052             :     /*
    6053             : 
    6054             :             {
    6055             :                 "src_layer":
    6056             :                     { "target_name": "",
    6057             :                       "description": "",
    6058             :                       "minzoom": 0,
    6059             :                       "maxzoom": 0
    6060             :                     }
    6061             :             }
    6062             :     */
    6063             : 
    6064         513 :     CPLJSONObject oObj = m_oConf.GetRoot().GetObj(pszLayerName);
    6065         342 :     CPLString osDescription;
    6066         171 :     if (oObj.IsValid())
    6067             :     {
    6068           4 :         std::string osTargetName = oObj.GetString("target_name");
    6069           2 :         if (!osTargetName.empty())
    6070           2 :             poLayer->m_osTargetName = std::move(osTargetName);
    6071           2 :         int nMinZoom = oObj.GetInteger("minzoom", -1);
    6072           2 :         if (nMinZoom >= 0)
    6073           2 :             poLayer->m_nMinZoom = nMinZoom;
    6074           2 :         int nMaxZoom = oObj.GetInteger("maxzoom", -1);
    6075           2 :         if (nMaxZoom >= 0)
    6076           2 :             poLayer->m_nMaxZoom = nMaxZoom;
    6077           2 :         osDescription = oObj.GetString("description");
    6078             :     }
    6079             : 
    6080         171 :     poLayer->m_nMinZoom = atoi(CSLFetchNameValueDef(
    6081             :         papszOptions, "MINZOOM", CPLSPrintf("%d", poLayer->m_nMinZoom)));
    6082         171 :     poLayer->m_nMaxZoom = atoi(CSLFetchNameValueDef(
    6083             :         papszOptions, "MAXZOOM", CPLSPrintf("%d", poLayer->m_nMaxZoom)));
    6084         171 :     if (!ValidateMinMaxZoom(poLayer->m_nMinZoom, poLayer->m_nMaxZoom))
    6085             :     {
    6086           1 :         delete poLayer;
    6087           1 :         return nullptr;
    6088             :     }
    6089             :     poLayer->m_osTargetName = CSLFetchNameValueDef(
    6090         170 :         papszOptions, "NAME", poLayer->m_osTargetName.c_str());
    6091             :     osDescription =
    6092         170 :         CSLFetchNameValueDef(papszOptions, "DESCRIPTION", osDescription);
    6093         170 :     if (!osDescription.empty())
    6094           4 :         m_oMapLayerNameToDesc[poLayer->m_osTargetName] =
    6095           2 :             std::move(osDescription);
    6096             : 
    6097         170 :     m_apoLayers.push_back(std::unique_ptr<OGRMVTWriterLayer>(poLayer));
    6098         170 :     return m_apoLayers.back().get();
    6099             : }
    6100             : 
    6101             : /************************************************************************/
    6102             : /*                                Create()                              */
    6103             : /************************************************************************/
    6104             : 
    6105         140 : GDALDataset *OGRMVTWriterDataset::Create(const char *pszFilename, int nXSize,
    6106             :                                          int nYSize, int nBandsIn,
    6107             :                                          GDALDataType eDT, char **papszOptions)
    6108             : {
    6109         140 :     if (nXSize != 0 || nYSize != 0 || nBandsIn != 0 || eDT != GDT_Unknown)
    6110             :     {
    6111           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6112             :                  "Only vector creation supported");
    6113           1 :         return nullptr;
    6114             :     }
    6115             : 
    6116         139 :     const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
    6117             :     const bool bMBTILESExt =
    6118         139 :         EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "mbtiles");
    6119         139 :     if (pszFormat == nullptr && bMBTILESExt)
    6120             :     {
    6121           4 :         pszFormat = "MBTILES";
    6122             :     }
    6123         139 :     const bool bMBTILES = pszFormat != nullptr && EQUAL(pszFormat, "MBTILES");
    6124             : 
    6125             :     // For debug only
    6126             :     bool bReuseTempFile =
    6127         139 :         CPLTestBool(CPLGetConfigOption("OGR_MVT_REUSE_TEMP_FILE", "NO"));
    6128             : 
    6129         139 :     if (bMBTILES)
    6130             :     {
    6131          80 :         if (!bMBTILESExt)
    6132             :         {
    6133           1 :             CPLError(CE_Failure, CPLE_FileIO,
    6134             :                      "%s should have mbtiles extension", pszFilename);
    6135           1 :             return nullptr;
    6136             :         }
    6137             : 
    6138          79 :         VSIUnlink(pszFilename);
    6139             :     }
    6140             :     else
    6141             :     {
    6142             :         VSIStatBufL sStat;
    6143          59 :         if (VSIStatL(pszFilename, &sStat) == 0)
    6144             :         {
    6145           3 :             CPLError(CE_Failure, CPLE_FileIO, "%s already exists", pszFilename);
    6146           5 :             return nullptr;
    6147             :         }
    6148             : 
    6149          56 :         if (VSIMkdir(pszFilename, 0755) != 0)
    6150             :         {
    6151           2 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
    6152             :                      pszFilename);
    6153           2 :             return nullptr;
    6154             :         }
    6155             :     }
    6156             : 
    6157         133 :     OGRMVTWriterDataset *poDS = new OGRMVTWriterDataset();
    6158         133 :     poDS->m_pMyVFS = OGRSQLiteCreateVFS(nullptr, poDS);
    6159         133 :     sqlite3_vfs_register(poDS->m_pMyVFS, 0);
    6160             : 
    6161         399 :     CPLString osTempDBDefault = CPLString(pszFilename) + ".temp.db";
    6162         133 :     if (STARTS_WITH(osTempDBDefault, "/vsizip/"))
    6163             :     {
    6164             :         osTempDBDefault =
    6165           0 :             CPLString(pszFilename + strlen("/vsizip/")) + ".temp.db";
    6166             :     }
    6167             :     CPLString osTempDB = CSLFetchNameValueDef(papszOptions, "TEMPORARY_DB",
    6168         266 :                                               osTempDBDefault.c_str());
    6169         133 :     if (!bReuseTempFile)
    6170         132 :         VSIUnlink(osTempDB);
    6171             : 
    6172         133 :     sqlite3 *hDB = nullptr;
    6173         133 :     if (sqlite3_open_v2(osTempDB, &hDB,
    6174             :                         SQLITE_OPEN_READWRITE |
    6175             :                             (bReuseTempFile ? 0 : SQLITE_OPEN_CREATE) |
    6176             :                             SQLITE_OPEN_NOMUTEX,
    6177         396 :                         poDS->m_pMyVFS->zName) != SQLITE_OK ||
    6178         130 :         hDB == nullptr)
    6179             :     {
    6180           3 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", osTempDB.c_str());
    6181           3 :         delete poDS;
    6182           3 :         sqlite3_close(hDB);
    6183           3 :         return nullptr;
    6184             :     }
    6185         130 :     poDS->m_osTempDB = osTempDB;
    6186         130 :     poDS->m_hDB = hDB;
    6187         130 :     poDS->m_bReuseTempFile = bReuseTempFile;
    6188             : 
    6189             :     // For Unix
    6190         259 :     if (!poDS->m_bReuseTempFile &&
    6191         129 :         CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
    6192             :     {
    6193         126 :         VSIUnlink(osTempDB);
    6194             :     }
    6195             : 
    6196         130 :     if (poDS->m_bReuseTempFile)
    6197             :     {
    6198           1 :         poDS->m_nTempTiles =
    6199           1 :             SQLGetInteger64(hDB, "SELECT COUNT(*) FROM temp", nullptr);
    6200             :     }
    6201             :     else
    6202             :     {
    6203         129 :         CPL_IGNORE_RET_VAL(SQLCommand(
    6204             :             hDB,
    6205             :             "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
    6206             :             "PRAGMA synchronous = OFF;"
    6207             :             "PRAGMA journal_mode = OFF;"
    6208             :             "PRAGMA temp_store = MEMORY;"
    6209             :             "CREATE TABLE temp(z INTEGER, x INTEGER, y INTEGER, layer TEXT, "
    6210             :             "idx INTEGER, feature BLOB, geomtype INTEGER, area_or_length "
    6211             :             "DOUBLE);"
    6212             :             "CREATE INDEX temp_index ON temp (z, x, y, layer, idx);"));
    6213             :     }
    6214             : 
    6215         130 :     sqlite3_stmt *hInsertStmt = nullptr;
    6216         130 :     CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
    6217             :         hDB,
    6218             :         "INSERT INTO temp (z,x,y,layer,idx,feature,geomtype,area_or_length) "
    6219             :         "VALUES (?,?,?,?,?,?,?,?)",
    6220             :         -1, &hInsertStmt, nullptr));
    6221         130 :     if (hInsertStmt == nullptr)
    6222             :     {
    6223           0 :         delete poDS;
    6224           0 :         return nullptr;
    6225             :     }
    6226         130 :     poDS->m_hInsertStmt = hInsertStmt;
    6227             : 
    6228         130 :     poDS->m_nMinZoom = atoi(CSLFetchNameValueDef(
    6229             :         papszOptions, "MINZOOM", CPLSPrintf("%d", poDS->m_nMinZoom)));
    6230         130 :     poDS->m_nMaxZoom = atoi(CSLFetchNameValueDef(
    6231             :         papszOptions, "MAXZOOM", CPLSPrintf("%d", poDS->m_nMaxZoom)));
    6232         130 :     if (!ValidateMinMaxZoom(poDS->m_nMinZoom, poDS->m_nMaxZoom))
    6233             :     {
    6234           3 :         delete poDS;
    6235           3 :         return nullptr;
    6236             :     }
    6237             : 
    6238         127 :     const char *pszConf = CSLFetchNameValue(papszOptions, "CONF");
    6239         127 :     if (pszConf)
    6240             :     {
    6241             :         VSIStatBufL sStat;
    6242             :         bool bSuccess;
    6243           3 :         if (VSIStatL(pszConf, &sStat) == 0)
    6244             :         {
    6245           2 :             bSuccess = poDS->m_oConf.Load(pszConf);
    6246             :         }
    6247             :         else
    6248             :         {
    6249           1 :             bSuccess = poDS->m_oConf.LoadMemory(pszConf);
    6250             :         }
    6251           3 :         if (!bSuccess)
    6252             :         {
    6253           1 :             delete poDS;
    6254           1 :             return nullptr;
    6255             :         }
    6256             :     }
    6257             : 
    6258         126 :     poDS->m_dfSimplification =
    6259         126 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION", "0"));
    6260         126 :     poDS->m_dfSimplificationMaxZoom = CPLAtof(
    6261             :         CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION_MAX_ZOOM",
    6262             :                              CPLSPrintf("%g", poDS->m_dfSimplification)));
    6263         126 :     poDS->m_nExtent = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
    6264             :         papszOptions, "EXTENT", CPLSPrintf("%u", poDS->m_nExtent))));
    6265         126 :     poDS->m_nBuffer = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
    6266         126 :         papszOptions, "BUFFER", CPLSPrintf("%u", 5 * poDS->m_nExtent / 256))));
    6267             : 
    6268             :     {
    6269         126 :         const char *pszMaxSize = CSLFetchNameValue(papszOptions, "MAX_SIZE");
    6270         126 :         poDS->m_bMaxTileSizeOptSpecified = pszMaxSize != nullptr;
    6271             :         // This is used by unit tests
    6272         126 :         pszMaxSize = CSLFetchNameValueDef(papszOptions, "@MAX_SIZE_FOR_TEST",
    6273             :                                           pszMaxSize);
    6274         126 :         if (pszMaxSize)
    6275             :         {
    6276           3 :             poDS->m_nMaxTileSize =
    6277           3 :                 std::max(100U, static_cast<unsigned>(atoi(pszMaxSize)));
    6278             :         }
    6279             :     }
    6280             : 
    6281             :     {
    6282             :         const char *pszMaxFeatures =
    6283         126 :             CSLFetchNameValue(papszOptions, "MAX_FEATURES");
    6284         126 :         poDS->m_bMaxFeaturesOptSpecified = pszMaxFeatures != nullptr;
    6285         126 :         pszMaxFeatures = CSLFetchNameValueDef(
    6286             :             // This is used by unit tests
    6287             :             papszOptions, "@MAX_FEATURES_FOR_TEST", pszMaxFeatures);
    6288         126 :         if (pszMaxFeatures)
    6289             :         {
    6290           2 :             poDS->m_nMaxFeatures =
    6291           2 :                 std::max(1U, static_cast<unsigned>(atoi(pszMaxFeatures)));
    6292             :         }
    6293             :     }
    6294             : 
    6295             :     poDS->m_osName = CSLFetchNameValueDef(
    6296         126 :         papszOptions, "NAME", CPLGetBasenameSafe(pszFilename).c_str());
    6297             :     poDS->m_osDescription = CSLFetchNameValueDef(papszOptions, "DESCRIPTION",
    6298         126 :                                                  poDS->m_osDescription.c_str());
    6299             :     poDS->m_osType =
    6300         126 :         CSLFetchNameValueDef(papszOptions, "TYPE", poDS->m_osType.c_str());
    6301         126 :     poDS->m_bGZip = CPLFetchBool(papszOptions, "COMPRESS", poDS->m_bGZip);
    6302         126 :     poDS->m_osBounds = CSLFetchNameValueDef(papszOptions, "BOUNDS", "");
    6303         126 :     poDS->m_osCenter = CSLFetchNameValueDef(papszOptions, "CENTER", "");
    6304             :     poDS->m_osExtension = CSLFetchNameValueDef(papszOptions, "TILE_EXTENSION",
    6305         126 :                                                poDS->m_osExtension);
    6306             : 
    6307             :     const char *pszTilingScheme =
    6308         126 :         CSLFetchNameValue(papszOptions, "TILING_SCHEME");
    6309         126 :     if (pszTilingScheme)
    6310             :     {
    6311           6 :         if (bMBTILES)
    6312             :         {
    6313           1 :             CPLError(CE_Failure, CPLE_NotSupported,
    6314             :                      "Custom TILING_SCHEME not supported with MBTILES output");
    6315           1 :             delete poDS;
    6316           2 :             return nullptr;
    6317             :         }
    6318             : 
    6319           5 :         const CPLStringList aoList(CSLTokenizeString2(pszTilingScheme, ",", 0));
    6320           5 :         if (aoList.Count() >= 4)
    6321             :         {
    6322           4 :             poDS->m_poSRS->SetFromUserInput(aoList[0]);
    6323           4 :             poDS->m_dfTopX = CPLAtof(aoList[1]);
    6324           4 :             poDS->m_dfTopY = CPLAtof(aoList[2]);
    6325           4 :             poDS->m_dfTileDim0 = CPLAtof(aoList[3]);
    6326           4 :             if (aoList.Count() == 6)
    6327             :             {
    6328           2 :                 poDS->m_nTileMatrixWidth0 = std::max(1, atoi(aoList[4]));
    6329           2 :                 poDS->m_nTileMatrixHeight0 = std::max(1, atoi(aoList[5]));
    6330             :             }
    6331           2 :             else if (poDS->m_dfTopX == -180 && poDS->m_dfTileDim0 == 180)
    6332             :             {
    6333             :                 // Assumes WorldCRS84Quad with 2 tiles in width
    6334             :                 // cf https://github.com/OSGeo/gdal/issues/11749
    6335           1 :                 poDS->m_nTileMatrixWidth0 = 2;
    6336             :             }
    6337             :         }
    6338             :         else
    6339             :         {
    6340           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6341             :                      "Wrong format for TILING_SCHEME. "
    6342             :                      "Expecting EPSG:XXXX,tile_origin_upper_left_x,"
    6343             :                      "tile_origin_upper_left_y,tile_dimension_zoom_0[,tile_"
    6344             :                      "matrix_width_zoom_0,tile_matrix_height_zoom_0]");
    6345           1 :             delete poDS;
    6346           1 :             return nullptr;
    6347             :         }
    6348             :     }
    6349             : 
    6350         124 :     if (bMBTILES)
    6351             :     {
    6352         228 :         if (sqlite3_open_v2(pszFilename, &poDS->m_hDBMBTILES,
    6353             :                             SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
    6354             :                                 SQLITE_OPEN_NOMUTEX,
    6355         151 :                             poDS->m_pMyVFS->zName) != SQLITE_OK ||
    6356          75 :             poDS->m_hDBMBTILES == nullptr)
    6357             :         {
    6358           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
    6359           1 :             delete poDS;
    6360           1 :             return nullptr;
    6361             :         }
    6362             : 
    6363          75 :         if (SQLCommand(
    6364             :                 poDS->m_hDBMBTILES,
    6365             :                 "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
    6366             :                 "PRAGMA synchronous = OFF;"
    6367             :                 "PRAGMA journal_mode = OFF;"
    6368             :                 "PRAGMA temp_store = MEMORY;"
    6369             :                 "CREATE TABLE metadata (name text, value text);"
    6370             :                 "CREATE TABLE tiles (zoom_level integer, tile_column integer, "
    6371             :                 "tile_row integer, tile_data blob, "
    6372          75 :                 "UNIQUE (zoom_level, tile_column, tile_row))") != OGRERR_NONE)
    6373             :         {
    6374           0 :             delete poDS;
    6375           0 :             return nullptr;
    6376             :         }
    6377             :     }
    6378             : 
    6379         123 :     int nThreads = CPLGetNumCPUs();
    6380         123 :     const char *pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
    6381         123 :     if (pszNumThreads && CPLGetValueType(pszNumThreads) == CPL_VALUE_INTEGER)
    6382             :     {
    6383           3 :         nThreads = atoi(pszNumThreads);
    6384             :     }
    6385         123 :     if (nThreads > 1)
    6386             :     {
    6387         120 :         poDS->m_bThreadPoolOK =
    6388         120 :             poDS->m_oThreadPool.Setup(nThreads, nullptr, nullptr);
    6389             :     }
    6390             : 
    6391         123 :     poDS->SetDescription(pszFilename);
    6392         123 :     poDS->poDriver = GDALDriver::FromHandle(GDALGetDriverByName("MVT"));
    6393             : 
    6394         123 :     return poDS;
    6395             : }
    6396             : 
    6397          75 : GDALDataset *OGRMVTWriterDatasetCreate(const char *pszFilename, int nXSize,
    6398             :                                        int nYSize, int nBandsIn,
    6399             :                                        GDALDataType eDT, char **papszOptions)
    6400             : {
    6401          75 :     return OGRMVTWriterDataset::Create(pszFilename, nXSize, nYSize, nBandsIn,
    6402          75 :                                        eDT, papszOptions);
    6403             : }
    6404             : 
    6405             : #endif  // HAVE_MVT_WRITE_SUPPORT
    6406             : 
    6407             : /************************************************************************/
    6408             : /*                           RegisterOGRMVT()                           */
    6409             : /************************************************************************/
    6410             : 
    6411        2040 : void RegisterOGRMVT()
    6412             : 
    6413             : {
    6414        2040 :     if (GDALGetDriverByName("MVT") != nullptr)
    6415         283 :         return;
    6416             : 
    6417        1757 :     GDALDriver *poDriver = new GDALDriver();
    6418             : 
    6419        1757 :     poDriver->SetDescription("MVT");
    6420        1757 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    6421        1757 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Mapbox Vector Tiles");
    6422        1757 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/mvt.html");
    6423        1757 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    6424        1757 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "mvt mvt.gz pbf");
    6425        1757 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
    6426             : 
    6427        1757 :     poDriver->SetMetadataItem(
    6428             :         GDAL_DMD_OPENOPTIONLIST,
    6429             :         "<OpenOptionList>"
    6430             :         "  <Option name='X' type='int' description='X coordinate of tile'/>"
    6431             :         "  <Option name='Y' type='int' description='Y coordinate of tile'/>"
    6432             :         "  <Option name='Z' type='int' description='Z coordinate of tile'/>"
    6433             :         //"  <Option name='@GEOREF_TOPX' type='float' description='X coordinate
    6434             :         // of top-left corner of tile'/>" "  <Option name='@GEOREF_TOPY'
    6435             :         // type='float' description='Y coordinate of top-left corner of tile'/>"
    6436             :         //"  <Option name='@GEOREF_TILEDIMX' type='float' description='Tile
    6437             :         // width in georeferenced units'/>" "  <Option name='@GEOREF_TILEDIMY'
    6438             :         // type='float' description='Tile height in georeferenced units'/>"
    6439             :         "  <Option name='METADATA_FILE' type='string' "
    6440             :         "description='Path to metadata.json'/>"
    6441             :         "  <Option name='CLIP' type='boolean' "
    6442             :         "description='Whether to clip geometries to tile extent' "
    6443             :         "default='YES'/>"
    6444             :         "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
    6445             :         "description="
    6446             :         "'For tilesets, extension of tiles'/>"
    6447             :         "  <Option name='TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN' type='int' "
    6448             :         "description="
    6449             :         "'For tilesets without metadata file, maximum number of tiles to use "
    6450             :         "to "
    6451             :         "establish the layer schemas' default='1000'/>"
    6452             :         "  <Option name='JSON_FIELD' type='boolean' description='For tilesets, "
    6453             :         "whether to put all attributes as a serialized JSon dictionary'/>"
    6454        1757 :         "</OpenOptionList>");
    6455             : 
    6456        1757 :     poDriver->pfnIdentify = OGRMVTDriverIdentify;
    6457        1757 :     poDriver->pfnOpen = OGRMVTDataset::Open;
    6458             : #ifdef HAVE_MVT_WRITE_SUPPORT
    6459        1757 :     poDriver->pfnCreate = OGRMVTWriterDataset::Create;
    6460        1757 :     poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    6461        1757 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
    6462        1757 :     poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
    6463        1757 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
    6464        1757 :                               "Integer Integer64 Real String");
    6465        1757 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
    6466        1757 :                               "Boolean Float32");
    6467        1757 :     poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
    6468             : 
    6469        1757 :     poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
    6470             : 
    6471        1757 :     poDriver->SetMetadataItem(
    6472             :         GDAL_DMD_CREATIONOPTIONLIST,
    6473             :         "<CreationOptionList>"
    6474             :         "  <Option name='NAME' type='string' description='Tileset name'/>"
    6475             :         "  <Option name='DESCRIPTION' type='string' "
    6476             :         "description='A description of the tileset'/>"
    6477             :         "  <Option name='TYPE' type='string-select' description='Layer type' "
    6478             :         "default='overlay'>"
    6479             :         "    <Value>overlay</Value>"
    6480             :         "    <Value>baselayer</Value>"
    6481             :         "  </Option>"
    6482             :         "  <Option name='FORMAT' type='string-select' description='Format'>"
    6483             :         "    <Value>DIRECTORY</Value>"
    6484             :         "    <Value>MBTILES</Value>"
    6485             :         "  </Option>"
    6486             :         "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
    6487             :         "description="
    6488             :         "'For tilesets as directories of files, extension of "
    6489             :         "tiles'/>" MVT_MBTILES_COMMON_DSCO
    6490             :         "  <Option name='BOUNDS' type='string' "
    6491             :         "description='Override default value for bounds metadata item'/>"
    6492             :         "  <Option name='CENTER' type='string' "
    6493             :         "description='Override default value for center metadata item'/>"
    6494             :         "  <Option name='TILING_SCHEME' type='string' "
    6495             :         "description='Custom tiling scheme with following format "
    6496             :         "\"EPSG:XXXX,tile_origin_upper_left_x,tile_origin_upper_left_y,"
    6497             :         "tile_dimension_zoom_0[,tile_matrix_width_zoom_0,tile_matrix_height_"
    6498             :         "zoom_0]\"'/>"
    6499        1757 :         "</CreationOptionList>");
    6500             : #endif  // HAVE_MVT_WRITE_SUPPORT
    6501             : 
    6502        1757 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    6503             : 
    6504        1757 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    6505             : }

Generated by: LCOV version 1.14