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

Generated by: LCOV version 1.14