• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16038479760

03 Jul 2025 12:12AM UTC coverage: 71.106% (-0.004%) from 71.11%
16038479760

Pull #12692

github

web-flow
Merge efeee3602 into b5d2a80d4
Pull Request #12692: C/C++/Python band algebra: add gdal.abs(), sqrt(), log(), log10() and pow()

80 of 87 new or added lines in 3 files covered. (91.95%)

8848 existing lines in 54 files now uncovered.

574863 of 808463 relevant lines covered (71.11%)

255001.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.65
/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp
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
static void InitWebMercatorTilingScheme(OGRSpatialReference *poSRS,
1,215✔
87
                                        double &dfTopX, double &dfTopY,
88
                                        double &dfTileDim0)
89
{
90
    constexpr double kmMAX_GM =
1,215✔
91
        kmSPHERICAL_RADIUS * M_PI;  // 20037508.342789244
92
    poSRS->SetFromUserInput(SRS_EPSG_3857);
1,215✔
93
    dfTopX = -kmMAX_GM;
1,215✔
94
    dfTopY = kmMAX_GM;
1,215✔
95
    dfTileDim0 = 2 * kmMAX_GM;
1,215✔
96
}
1,215✔
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
static unsigned GetCmdId(unsigned int nCmdCountCombined)
2,023✔
105
{
106
    return nCmdCountCombined & 0x7;
2,023✔
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
static unsigned GetCmdCount(unsigned int nCmdCountCombined)
5,688✔
116
{
117
    return nCmdCountCombined >> 3;
5,688✔
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
    virtual OGRFeatureDefn *GetLayerDefn() override
7,660✔
140
    {
141
        return m_poFeatureDefn;
7,660✔
142
    }
143

144
    DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRMVTLayerBase)
2,857✔
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 IGetExtent(int iGeomField, OGREnvelope *psExtent,
254
                      bool bForce) override;
255

256
    OGRErr ISetSpatialFilter(int iGeomField,
257
                             const OGRGeometry *poGeom) override;
258

259
    virtual OGRFeature *GetFeature(GIntBig nFID) override;
260

261
    virtual int TestCapability(const char *) override;
262

263
    GDALDataset *GetDataset() override;
264
};
265

266
/************************************************************************/
267
/*                           OGRMVTDataset                              */
268
/************************************************************************/
269

270
class OGRMVTDataset final : public GDALDataset
271
{
272
    friend class OGRMVTLayer;
273
    friend class OGRMVTDirectoryLayer;
274

275
    GByte *m_pabyData;
276
    std::vector<std::unique_ptr<OGRLayer>> m_apoLayers;
277
    bool m_bGeoreferenced = false;
278
    double m_dfTileDimX = 0.0;
279
    double m_dfTileDimY = 0.0;
280
    double m_dfTopX = 0.0;
281
    double m_dfTopY = 0.0;
282
    CPLString m_osMetadataMemFilename;
283
    bool m_bClip = true;
284
    CPLString m_osTileExtension{"pbf"};
285
    OGRSpatialReference *m_poSRS = nullptr;
286
    double m_dfTileDim0 =
287
        0.0;  // Extent (in CRS units) of a tile at zoom level 0
288
    double m_dfTopXOrigin = 0.0;  // top-left X of tile matrix scheme
289
    double m_dfTopYOrigin = 0.0;  // top-left Y of tile matrix scheme
290
    int m_nTileMatrixWidth0 =
291
        1;  // Number of tiles along X axis at zoom level 0
292
    int m_nTileMatrixHeight0 =
293
        1;  // Number of tiles along Y axis at zoom level 0
294

295
    static GDALDataset *OpenDirectory(GDALOpenInfo *);
296

297
  public:
298
    explicit OGRMVTDataset(GByte *pabyData);
299
    virtual ~OGRMVTDataset();
300

301
    virtual int GetLayerCount() override
3,338✔
302
    {
303
        return static_cast<int>(m_apoLayers.size());
3,338✔
304
    }
305

306
    virtual OGRLayer *GetLayer(int) override;
307

308
    virtual int TestCapability(const char *) override
12✔
309
    {
310
        return FALSE;
12✔
311
    }
312

313
    static GDALDataset *Open(GDALOpenInfo *);
314
    static GDALDataset *Open(GDALOpenInfo *, bool bRecurseAllowed);
315

316
    OGRSpatialReference *GetSRS()
1,004✔
317
    {
318
        return m_poSRS;
1,004✔
319
    }
320

321
    inline double GetTileDim0() const
190✔
322
    {
323
        return m_dfTileDim0;
190✔
324
    }
325

326
    inline double GetTopXOrigin() const
76✔
327
    {
328
        return m_dfTopXOrigin;
76✔
329
    }
330

331
    inline double GetTopYOrigin() const
76✔
332
    {
333
        return m_dfTopYOrigin;
76✔
334
    }
335

336
    inline int GetTileMatrixWidth0() const
85✔
337
    {
338
        return m_nTileMatrixWidth0;
85✔
339
    }
340

341
    inline int GetTileMatrixHeight0() const
85✔
342
    {
343
        return m_nTileMatrixHeight0;
85✔
344
    }
345
};
346

347
/************************************************************************/
348
/*                        ~OGRMVTLayerBase()                            */
349
/************************************************************************/
350

351
OGRMVTLayerBase::~OGRMVTLayerBase()
1,046✔
352
{
353
    m_poFeatureDefn->Release();
1,046✔
354
}
1,046✔
355

356
/************************************************************************/
357
/*                           InitFields()                               */
358
/************************************************************************/
359

360
void OGRMVTLayerBase::InitFields(const CPLJSONObject &oFields,
1,045✔
361
                                 const CPLJSONArray &oAttributesFromTileStats)
362
{
363
    OGRMVTInitFields(m_poFeatureDefn, oFields, oAttributesFromTileStats);
1,045✔
364
}
1,045✔
365

366
/************************************************************************/
367
/*                           TestCapability()                           */
368
/************************************************************************/
369

370
int OGRMVTLayerBase::TestCapability(const char *pszCap)
69✔
371
{
372
    if (EQUAL(pszCap, OLCStringsAsUTF8) || EQUAL(pszCap, OLCFastSpatialFilter))
69✔
373
    {
374
        return TRUE;
25✔
375
    }
376
    return FALSE;
44✔
377
}
378

379
/************************************************************************/
380
/*                           OGRMVTLayer()                              */
381
/************************************************************************/
382

383
OGRMVTLayer::OGRMVTLayer(OGRMVTDataset *poDS, const char *pszLayerName,
1,023✔
384
                         const GByte *pabyData, int nLayerSize,
385
                         const CPLJSONObject &oFields,
386
                         const CPLJSONArray &oAttributesFromTileStats,
387
                         OGRwkbGeometryType eGeomType)
1,023✔
388
    : m_poDS(poDS), m_pabyDataStart(pabyData),
389
      m_pabyDataEnd(pabyData + nLayerSize)
1,023✔
390
{
391
    m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
1,023✔
392
    SetDescription(m_poFeatureDefn->GetName());
1,023✔
393
    m_poFeatureDefn->SetGeomType(eGeomType);
1,023✔
394
    m_poFeatureDefn->Reference();
1,023✔
395

396
    if (m_poDS->m_bGeoreferenced)
1,023✔
397
    {
398
        m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poDS->GetSRS());
981✔
399
    }
400

401
    Init(oFields, oAttributesFromTileStats);
1,023✔
402

403
    GetXY(0, 0, m_dfTileMinX, m_dfTileMaxY);
1,023✔
404
    GetXY(m_nExtent, m_nExtent, m_dfTileMaxX, m_dfTileMinY);
1,023✔
405
    OGRLinearRing *poLR = new OGRLinearRing();
1,023✔
406
    poLR->addPoint(m_dfTileMinX, m_dfTileMinY);
1,023✔
407
    poLR->addPoint(m_dfTileMinX, m_dfTileMaxY);
1,023✔
408
    poLR->addPoint(m_dfTileMaxX, m_dfTileMaxY);
1,023✔
409
    poLR->addPoint(m_dfTileMaxX, m_dfTileMinY);
1,023✔
410
    poLR->addPoint(m_dfTileMinX, m_dfTileMinY);
1,023✔
411
    m_oClipPoly.addRingDirectly(poLR);
1,023✔
412

413
    // Config option only for tests for now. When set, it ensures that
414
    // the first ring (exterior ring) of a polygon is clockwise oriented,
415
    // as per the MVT spec.
416
    // By default, we are more tolerant and only use reversal of winding order
417
    // to detect inner rings.
418
    m_bEnforceExternalIsClockwise = CPLTestBool(
1,023✔
419
        CPLGetConfigOption("OGR_MVT_ENFORE_EXTERNAL_RING_IS_CLOCKWISE", "NO"));
420
}
1,023✔
421

422
/************************************************************************/
423
/*                          ~OGRMVTLayer()                              */
424
/************************************************************************/
425

426
OGRMVTLayer::~OGRMVTLayer()
2,046✔
427
{
428
    for (auto &sValue : m_asValues)
21,740✔
429
    {
430
        if (sValue.eType == OFTString)
20,717✔
431
        {
432
            CPLFree(sValue.sValue.String);
11,732✔
433
        }
434
    }
435
}
2,046✔
436

437
/************************************************************************/
438
/*                               Init()                                 */
439
/************************************************************************/
440

441
void OGRMVTLayer::Init(const CPLJSONObject &oFields,
1,023✔
442
                       const CPLJSONArray &oAttributesFromTileStats)
443
{
444
    // First pass to collect keys and values
445
    const GByte *pabyData = m_pabyDataStart;
1,023✔
446
    const GByte *pabyDataLimit = m_pabyDataEnd;
1,023✔
447
    unsigned int nKey = 0;
1,023✔
448
    bool bGeomTypeSet = false;
1,023✔
449
    const bool bScanFields = !oFields.IsValid();
1,023✔
450
    const bool bScanGeometries = m_poFeatureDefn->GetGeomType() == wkbUnknown;
1,023✔
451
    const bool bQuickScanFeature = bScanFields || bScanGeometries;
1,023✔
452

453
    try
454
    {
455
        while (pabyData < pabyDataLimit)
43,653✔
456
        {
457
            READ_FIELD_KEY(nKey);
42,630✔
458
            if (nKey == MAKE_KEY(knLAYER_KEYS, WT_DATA))
42,630✔
459
            {
460
                char *pszKey = nullptr;
16,294✔
461
                READ_TEXT(pabyData, pabyDataLimit, pszKey);
16,294✔
462
                m_aosKeys.push_back(pszKey);
16,294✔
463
                CPLFree(pszKey);
16,294✔
464
            }
465
            else if (nKey == MAKE_KEY(knLAYER_VALUES, WT_DATA))
26,336✔
466
            {
467
                unsigned int nValueLength = 0;
20,718✔
468
                READ_SIZE(pabyData, pabyDataLimit, nValueLength);
20,718✔
469
                const GByte *pabyDataValueEnd = pabyData + nValueLength;
20,718✔
470
                READ_VARUINT32(pabyData, pabyDataLimit, nKey);
20,718✔
471
                if (nKey == MAKE_KEY(knVALUE_STRING, WT_DATA))
20,718✔
472
                {
473
                    char *pszValue = nullptr;
11,732✔
474
                    READ_TEXT(pabyData, pabyDataLimit, pszValue);
11,732✔
475
                    Value sValue;
476
                    sValue.eType = OFTString;
11,732✔
477
                    sValue.eSubType = OFSTNone;
11,732✔
478
                    sValue.sValue.String = pszValue;
11,732✔
479
                    m_asValues.push_back(sValue);
11,732✔
480
                }
481
                else if (nKey == MAKE_KEY(knVALUE_FLOAT, WT_32BIT))
8,986✔
482
                {
483
                    Value sValue;
484
                    sValue.eType = OFTReal;
594✔
485
                    sValue.eSubType = OFSTFloat32;
594✔
486
                    sValue.sValue.Real = ReadFloat32(&pabyData, pabyDataLimit);
594✔
487
                    m_asValues.push_back(sValue);
594✔
488
                }
489
                else if (nKey == MAKE_KEY(knVALUE_DOUBLE, WT_64BIT))
8,392✔
490
                {
491
                    Value sValue;
492
                    sValue.eType = OFTReal;
665✔
493
                    sValue.eSubType = OFSTNone;
665✔
494
                    sValue.sValue.Real = ReadFloat64(&pabyData, pabyDataLimit);
665✔
495
                    m_asValues.push_back(sValue);
665✔
496
                }
497
                else if (nKey == MAKE_KEY(knVALUE_INT, WT_VARINT))
7,727✔
498
                {
499
                    GIntBig nVal = 0;
253✔
500
                    READ_VARINT64(pabyData, pabyDataLimit, nVal);
253✔
501
                    Value sValue;
502
                    sValue.eType = (nVal >= INT_MIN && nVal <= INT_MAX)
197✔
503
                                       ? OFTInteger
450✔
504
                                       : OFTInteger64;
505
                    sValue.eSubType = OFSTNone;
253✔
506
                    if (sValue.eType == OFTInteger)
253✔
507
                        sValue.sValue.Integer = static_cast<int>(nVal);
136✔
508
                    else
509
                        sValue.sValue.Integer64 = nVal;
117✔
510
                    m_asValues.push_back(sValue);
253✔
511
                }
512
                else if (nKey == MAKE_KEY(knVALUE_UINT, WT_VARINT))
7,474✔
513
                {
514
                    GUIntBig nVal = 0;
6,876✔
515
                    READ_VARUINT64(pabyData, pabyDataLimit, nVal);
6,876✔
516
                    Value sValue;
517
                    sValue.eType =
6,876✔
518
                        (nVal <= INT_MAX) ? OFTInteger : OFTInteger64;
6,876✔
519
                    sValue.eSubType = OFSTNone;
6,876✔
520
                    if (sValue.eType == OFTInteger)
6,876✔
521
                        sValue.sValue.Integer = static_cast<int>(nVal);
6,818✔
522
                    else
523
                        sValue.sValue.Integer64 = static_cast<GIntBig>(nVal);
58✔
524
                    m_asValues.push_back(sValue);
6,876✔
525
                }
526
                else if (nKey == MAKE_KEY(knVALUE_SINT, WT_VARINT))
598✔
527
                {
528
                    GIntBig nVal = 0;
472✔
529
                    READ_VARSINT64(pabyData, pabyDataLimit, nVal);
472✔
530
                    Value sValue;
531
                    sValue.eType = (nVal >= INT_MIN && nVal <= INT_MAX)
414✔
532
                                       ? OFTInteger
886✔
533
                                       : OFTInteger64;
534
                    sValue.eSubType = OFSTNone;
472✔
535
                    if (sValue.eType == OFTInteger)
472✔
536
                        sValue.sValue.Integer = static_cast<int>(nVal);
358✔
537
                    else
538
                        sValue.sValue.Integer64 = nVal;
114✔
539
                    m_asValues.push_back(sValue);
472✔
540
                }
541
                else if (nKey == MAKE_KEY(knVALUE_BOOL, WT_VARINT))
126✔
542
                {
543
                    unsigned nVal = 0;
125✔
544
                    READ_VARUINT32(pabyData, pabyDataLimit, nVal);
125✔
545
                    Value sValue;
546
                    sValue.eType = OFTInteger;
125✔
547
                    sValue.eSubType = OFSTBoolean;
125✔
548
                    sValue.sValue.Integer = static_cast<int>(nVal);
125✔
549
                    m_asValues.push_back(sValue);
125✔
550
                }
551

552
                pabyData = pabyDataValueEnd;
20,718✔
553
            }
554
            else if (nKey == MAKE_KEY(knLAYER_EXTENT, WT_VARINT))
5,618✔
555
            {
556
                GUInt32 nExtent = 0;
1,010✔
557
                READ_VARUINT32(pabyData, pabyDataLimit, nExtent);
1,010✔
558
                m_nExtent = std::max(1U, nExtent);  // to avoid divide by zero
1,010✔
559
            }
560
            else
561
            {
562
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
4,608✔
563
            }
564
        }
565

566
        InitFields(oFields, oAttributesFromTileStats);
1,023✔
567

568
        m_nFeatureCount = 0;
1,023✔
569
        pabyData = m_pabyDataStart;
1,023✔
570
        // Second pass to iterate over features to figure out the geometry type
571
        // and attribute schema
572
        while (pabyData < pabyDataLimit)
43,642✔
573
        {
574
            const GByte *pabyDataBefore = pabyData;
42,622✔
575
            READ_FIELD_KEY(nKey);
42,622✔
576
            if (nKey == MAKE_KEY(knLAYER_FEATURES, WT_DATA))
42,622✔
577
            {
578
                if (m_pabyDataFeatureStart == nullptr)
2,561✔
579
                {
580
                    m_pabyDataFeatureStart = pabyDataBefore;
1,021✔
581
                    m_pabyDataCur = pabyDataBefore;
1,021✔
582
                }
583

584
                unsigned int nFeatureLength = 0;
2,561✔
585
                READ_SIZE(pabyData, pabyDataLimit, nFeatureLength);
2,561✔
586
                const GByte *pabyDataFeatureEnd = pabyData + nFeatureLength;
2,561✔
587
                if (bQuickScanFeature)
2,561✔
588
                {
589
                    if (!QuickScanFeature(pabyData, pabyDataFeatureEnd,
507✔
590
                                          bScanFields, bScanGeometries,
591
                                          bGeomTypeSet))
592
                    {
593
                        return;
3✔
594
                    }
595
                }
596
                pabyData = pabyDataFeatureEnd;
2,558✔
597

598
                m_nFeatureCount++;
2,558✔
599
            }
600
            else
601
            {
602
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
40,061✔
603
            }
604
        }
605
    }
606
    catch (const GPBException &e)
×
607
    {
608
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
×
609
    }
610
}
611

612
/************************************************************************/
613
/*                          MergeFieldDefn()                            */
614
/************************************************************************/
615

616
static void MergeFieldDefn(OGRFieldDefn *poFieldDefn, OGRFieldType eSrcType,
11✔
617
                           OGRFieldSubType eSrcSubType)
618
{
619
    if (eSrcType == OFTString)
11✔
620
    {
621
        poFieldDefn->SetSubType(OFSTNone);
4✔
622
        poFieldDefn->SetType(OFTString);
4✔
623
    }
624
    else if (poFieldDefn->GetType() == OFTInteger && eSrcType == OFTInteger64)
7✔
625
    {
626
        poFieldDefn->SetSubType(OFSTNone);
1✔
627
        poFieldDefn->SetType(OFTInteger64);
1✔
628
    }
629
    else if ((poFieldDefn->GetType() == OFTInteger ||
10✔
630
              poFieldDefn->GetType() == OFTInteger64) &&
10✔
631
             eSrcType == OFTReal)
632
    {
633
        poFieldDefn->SetSubType(OFSTNone);
2✔
634
        poFieldDefn->SetType(OFTReal);
2✔
635
        poFieldDefn->SetSubType(eSrcSubType);
2✔
636
    }
637
    else if (poFieldDefn->GetType() == OFTReal && eSrcType == OFTReal &&
4✔
638
             eSrcSubType == OFSTNone)
639
    {
640
        poFieldDefn->SetSubType(OFSTNone);
1✔
641
    }
642
    else if (poFieldDefn->GetType() == OFTInteger && eSrcType == OFTInteger &&
3✔
643
             eSrcSubType == OFSTNone)
644
    {
645
        poFieldDefn->SetSubType(OFSTNone);
1✔
646
    }
647
}
11✔
648

649
/************************************************************************/
650
/*                         QuickScanFeature()                           */
651
/************************************************************************/
652

653
bool OGRMVTLayer::QuickScanFeature(const GByte *pabyData,
507✔
654
                                   const GByte *pabyDataFeatureEnd,
655
                                   bool bScanFields, bool bScanGeometries,
656
                                   bool &bGeomTypeSet)
657
{
658
    unsigned int nKey = 0;
507✔
659
    unsigned int nGeomType = 0;
507✔
660
    try
661
    {
662
        while (pabyData < pabyDataFeatureEnd)
1,948✔
663
        {
664
            READ_VARUINT32(pabyData, pabyDataFeatureEnd, nKey);
1,444✔
665
            if (nKey == MAKE_KEY(knFEATURE_TYPE, WT_VARINT))
1,444✔
666
            {
667
                READ_VARUINT32(pabyData, pabyDataFeatureEnd, nGeomType);
488✔
668
            }
669
            else if (nKey == MAKE_KEY(knFEATURE_TAGS, WT_DATA) && bScanFields)
956✔
670
            {
671
                unsigned int nTagsSize = 0;
52✔
672
                READ_SIZE(pabyData, pabyDataFeatureEnd, nTagsSize);
52✔
673
                const GByte *pabyDataTagsEnd = pabyData + nTagsSize;
52✔
674
                while (pabyData < pabyDataTagsEnd)
222✔
675
                {
676
                    unsigned int nKeyIdx = 0;
173✔
677
                    unsigned int nValIdx = 0;
173✔
678
                    READ_VARUINT32(pabyData, pabyDataTagsEnd, nKeyIdx);
173✔
679
                    READ_VARUINT32(pabyData, pabyDataTagsEnd, nValIdx);
173✔
680
                    if (nKeyIdx >= m_aosKeys.size())
172✔
681
                    {
682
                        CPLError(CE_Failure, CPLE_AppDefined,
1✔
683
                                 "Invalid tag key index: %u", nKeyIdx);
684
                        m_bError = true;
1✔
685
                        return false;
1✔
686
                    }
687
                    if (nValIdx >= m_asValues.size())
171✔
688
                    {
689
                        CPLError(CE_Failure, CPLE_AppDefined,
1✔
690
                                 "Invalid tag value index: %u", nValIdx);
691
                        m_bError = true;
1✔
692
                        return false;
1✔
693
                    }
694
                    const int nFieldIdx =
695
                        m_poFeatureDefn->GetFieldIndex(m_aosKeys[nKeyIdx]);
170✔
696
                    if (nFieldIdx < 0)
170✔
697
                    {
698
                        OGRFieldDefn oFieldDefn(m_aosKeys[nKeyIdx],
142✔
699
                                                m_asValues[nValIdx].eType);
284✔
700
                        oFieldDefn.SetSubType(m_asValues[nValIdx].eSubType);
142✔
701
                        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
142✔
702
                    }
703
                    else if (m_poFeatureDefn->GetFieldDefn(nFieldIdx)
56✔
704
                                     ->GetType() != m_asValues[nValIdx].eType ||
52✔
705
                             m_poFeatureDefn->GetFieldDefn(nFieldIdx)
24✔
706
                                     ->GetSubType() !=
24✔
707
                                 m_asValues[nValIdx].eSubType)
24✔
708
                    {
709
                        OGRFieldDefn *poFieldDefn =
710
                            m_poFeatureDefn->GetFieldDefn(nFieldIdx);
8✔
711
                        OGRFieldType eSrcType(m_asValues[nValIdx].eType);
8✔
712
                        OGRFieldSubType eSrcSubType(
713
                            m_asValues[nValIdx].eSubType);
8✔
714
                        MergeFieldDefn(poFieldDefn, eSrcType, eSrcSubType);
8✔
715
                    }
716
                }
49✔
717
            }
718
            else if (nKey == MAKE_KEY(knFEATURE_GEOMETRY, WT_DATA) &&
904✔
719
                     bScanGeometries && nGeomType >= knGEOM_TYPE_POINT &&
487✔
720
                     nGeomType <= knGEOM_TYPE_POLYGON)
721
            {
722
                unsigned int nGeometrySize = 0;
486✔
723
                READ_SIZE(pabyData, pabyDataFeatureEnd, nGeometrySize);
486✔
724
                const GByte *pabyDataGeometryEnd = pabyData + nGeometrySize;
486✔
725
                OGRwkbGeometryType eType = wkbUnknown;
486✔
726

727
                if (nGeomType == knGEOM_TYPE_POINT)
486✔
728
                {
729
                    eType = wkbPoint;
76✔
730
                    unsigned int nCmdCountCombined = 0;
76✔
731
                    READ_VARUINT32(pabyData, pabyDataGeometryEnd,
76✔
732
                                   nCmdCountCombined);
733
                    if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO &&
152✔
734
                        GetCmdCount(nCmdCountCombined) > 1)
76✔
735
                    {
736
                        eType = wkbMultiPoint;
×
737
                    }
738
                }
739
                else if (nGeomType == knGEOM_TYPE_LINESTRING)
410✔
740
                {
741
                    eType = wkbLineString;
16✔
742
                    for (int iIter = 0; pabyData < pabyDataGeometryEnd; iIter++)
32✔
743
                    {
744
                        if (iIter == 1)
17✔
745
                        {
746
                            eType = wkbMultiLineString;
1✔
747
                            break;
1✔
748
                        }
749
                        unsigned int nCmdCountCombined = 0;
16✔
750
                        unsigned int nLineToCount;
751
                        // Should be a moveto
752
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
16✔
753
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
16✔
754
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
16✔
755
                        READ_VARUINT32(pabyData, pabyDataGeometryEnd,
16✔
756
                                       nCmdCountCombined);
757
                        nLineToCount = GetCmdCount(nCmdCountCombined);
16✔
758
                        for (unsigned i = 0; i < 2 * nLineToCount; i++)
50✔
759
                        {
760
                            SKIP_VARINT(pabyData, pabyDataGeometryEnd);
34✔
761
                        }
762
                    }
763
                }
764
                else /* if( nGeomType == knGEOM_TYPE_POLYGON ) */
765
                {
766
                    eType = wkbPolygon;
394✔
767
                    for (int iIter = 0; pabyData < pabyDataGeometryEnd; iIter++)
788✔
768
                    {
769
                        if (iIter == 1)
428✔
770
                        {
771
                            eType = wkbMultiPolygon;
34✔
772
                            break;
34✔
773
                        }
774
                        unsigned int nCmdCountCombined = 0;
394✔
775
                        unsigned int nLineToCount;
776
                        // Should be a moveto
777
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
394✔
778
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
394✔
779
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
394✔
780
                        READ_VARUINT32(pabyData, pabyDataGeometryEnd,
394✔
781
                                       nCmdCountCombined);
782
                        nLineToCount = GetCmdCount(nCmdCountCombined);
394✔
783
                        for (unsigned i = 0; i < 2 * nLineToCount; i++)
2,528✔
784
                        {
785
                            SKIP_VARINT(pabyData, pabyDataGeometryEnd);
2,134✔
786
                        }
787
                        // Should be a closepath
788
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
394✔
789
                    }
790
                }
791

792
                if (bGeomTypeSet && m_poFeatureDefn->GetGeomType() ==
503✔
793
                                        OGR_GT_GetCollection(eType))
17✔
794
                {
795
                    // do nothing
796
                }
797
                else if (bGeomTypeSet &&
493✔
798
                         eType == OGR_GT_GetCollection(
12✔
799
                                      m_poFeatureDefn->GetGeomType()))
12✔
800
                {
801
                    m_poFeatureDefn->SetGeomType(eType);
×
802
                }
803
                else if (bGeomTypeSet &&
493✔
804
                         m_poFeatureDefn->GetGeomType() != eType)
12✔
805
                {
806
                    m_poFeatureDefn->SetGeomType(wkbUnknown);
×
807
                }
808
                else
809
                {
810
                    m_poFeatureDefn->SetGeomType(eType);
481✔
811
                }
812
                bGeomTypeSet = true;
486✔
813

814
                pabyData = pabyDataGeometryEnd;
486✔
815
            }
816
            else
817
            {
818
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataFeatureEnd, FALSE);
418✔
819
            }
820
        }
821
        return true;
504✔
822
    }
823
    catch (const GPBException &e)
1✔
824
    {
825
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
1✔
826
        return false;
1✔
827
    }
828
}
829

830
/************************************************************************/
831
/*                         GetFeatureCount()                            */
832
/************************************************************************/
833

834
GIntBig OGRMVTLayer::GetFeatureCount(int bForce)
51✔
835
{
836
    if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr &&
51✔
837
        m_nFeatureCount >= 0)
45✔
838
    {
839
        return m_nFeatureCount;
45✔
840
    }
841
    return OGRLayer::GetFeatureCount(bForce);
6✔
842
}
843

844
/************************************************************************/
845
/*                          ResetReading()                              */
846
/************************************************************************/
847

848
void OGRMVTLayer::ResetReading()
131✔
849
{
850
    m_nFID = 0;
131✔
851
    m_pabyDataCur = m_pabyDataFeatureStart;
131✔
852
}
131✔
853

854
/************************************************************************/
855
/*                              GetXY()                                 */
856
/************************************************************************/
857

858
void OGRMVTLayer::GetXY(int nX, int nY, double &dfX, double &dfY)
353,948✔
859
{
860
    if (m_poDS->m_bGeoreferenced)
353,948✔
861
    {
862
        dfX = m_poDS->m_dfTopX + nX * m_poDS->m_dfTileDimX / m_nExtent;
352,389✔
863
        dfY = m_poDS->m_dfTopY - nY * m_poDS->m_dfTileDimY / m_nExtent;
352,389✔
864
    }
865
    else
866
    {
867
        dfX = nX;
1,559✔
868
        dfY = static_cast<double>(m_nExtent) - nY;
1,559✔
869
    }
870
}
353,948✔
871

872
/************************************************************************/
873
/*                     AddWithOverflowAccepted()                        */
874
/************************************************************************/
875

876
CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
877
static int AddWithOverflowAccepted(int a, int b)
703,586✔
878
{
879
    // In fact in normal situations a+b should not overflow. That can only
880
    // happen with corrupted datasets. But we don't really want to add code
881
    // to detect that situation, so basically this is just a trick to perform
882
    // the addition without the various sanitizers to yell about the overflow.
883
    //
884
    // Assumes complement-to-two signed integer representation and that
885
    // the compiler will safely cast a big unsigned to negative integer.
886
    return static_cast<int>(static_cast<unsigned>(a) +
703,586✔
887
                            static_cast<unsigned>(b));
703,586✔
888
}
889

890
/************************************************************************/
891
/*                           ParseGeometry()                            */
892
/************************************************************************/
893

894
OGRGeometry *OGRMVTLayer::ParseGeometry(unsigned int nGeomType,
2,038✔
895
                                        const GByte *pabyDataGeometryEnd)
896
{
897
    OGRMultiPoint *poMultiPoint = nullptr;
2,038✔
898
    OGRMultiLineString *poMultiLS = nullptr;
2,038✔
899
    OGRLineString *poLine = nullptr;
2,038✔
900
    OGRMultiPolygon *poMultiPoly = nullptr;
2,038✔
901
    OGRPolygon *poPoly = nullptr;
2,038✔
902
    OGRLinearRing *poRing = nullptr;
2,038✔
903

904
    try
905
    {
906
        if (nGeomType == knGEOM_TYPE_POINT)
2,038✔
907
        {
908
            unsigned int nCmdCountCombined = 0;
112✔
909
            unsigned int nCount;
910
            READ_VARUINT32(m_pabyDataCur, pabyDataGeometryEnd,
112✔
911
                           nCmdCountCombined);
912
            nCount = GetCmdCount(nCmdCountCombined);
112✔
913
            if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO && nCount == 1)
112✔
914
            {
915
                int nX = 0;
110✔
916
                int nY = 0;
110✔
917
                READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nX);
110✔
918
                READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nY);
109✔
919
                double dfX;
920
                double dfY;
921
                GetXY(nX, nY, dfX, dfY);
109✔
922
                OGRPoint *poPoint = new OGRPoint(dfX, dfY);
109✔
923
                if (m_poFeatureDefn->GetGeomType() == wkbMultiPoint)
109✔
924
                {
925
                    poMultiPoint = new OGRMultiPoint();
8✔
926
                    poMultiPoint->addGeometryDirectly(poPoint);
8✔
927
                    return poMultiPoint;
8✔
928
                }
929
                else
930
                {
931
                    return poPoint;
101✔
932
                }
933
            }
934
            else if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO && nCount > 1)
2✔
935
            {
936
                int nX = 0;
2✔
937
                int nY = 0;
2✔
938
                poMultiPoint = new OGRMultiPoint();
2✔
939
                for (unsigned i = 0; i < nCount; i++)
6✔
940
                {
941
                    int nDX = 0;
4✔
942
                    int nDY = 0;
4✔
943
                    READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
4✔
944
                    READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
4✔
945
                    // if( nDX != 0 || nDY != 0 )
946
                    {
947
                        nX = AddWithOverflowAccepted(nX, nDX);
4✔
948
                        nY = AddWithOverflowAccepted(nY, nDY);
4✔
949
                        double dfX;
950
                        double dfY;
951
                        GetXY(nX, nY, dfX, dfY);
4✔
952
                        OGRPoint *poPoint = new OGRPoint(dfX, dfY);
4✔
953
                        if (i == 0 && nCount == 2 &&
4✔
954
                            m_pabyDataCur == pabyDataGeometryEnd)
2✔
955
                        {
956
                            // Current versions of Mapserver at time of writing
957
                            // wrongly encode a point with nCount = 2
958
                            static bool bWarned = false;
959
                            if (!bWarned)
×
960
                            {
961
                                CPLDebug(
×
962
                                    "MVT",
963
                                    "Reading likely a broken point as "
964
                                    "produced by some versions of Mapserver");
965
                                bWarned = true;
×
966
                            }
967
                            delete poMultiPoint;
×
968
                            return poPoint;
×
969
                        }
970
                        poMultiPoint->addGeometryDirectly(poPoint);
4✔
971
                    }
972
                }
973
                return poMultiPoint;
2✔
974
            }
975
        }
976
        else if (nGeomType == knGEOM_TYPE_LINESTRING)
1,926✔
977
        {
978
            int nX = 0;
23✔
979
            int nY = 0;
23✔
980
            while (m_pabyDataCur < pabyDataGeometryEnd)
51✔
981
            {
982
                unsigned int nCmdCountCombined = 0;
28✔
983
                unsigned int nLineToCount;
984
                // Should be a moveto
985
                SKIP_VARINT(m_pabyDataCur, pabyDataGeometryEnd);
28✔
986
                int nDX = 0;
28✔
987
                int nDY = 0;
28✔
988
                READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
28✔
989
                READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
28✔
990
                nX = AddWithOverflowAccepted(nX, nDX);
28✔
991
                nY = AddWithOverflowAccepted(nY, nDY);
28✔
992
                double dfX;
993
                double dfY;
994
                GetXY(nX, nY, dfX, dfY);
28✔
995
                if (poLine != nullptr)
28✔
996
                {
997
                    if (poMultiLS == nullptr)
5✔
998
                    {
999
                        poMultiLS = new OGRMultiLineString();
3✔
1000
                        poMultiLS->addGeometryDirectly(poLine);
3✔
1001
                    }
1002
                    poLine = new OGRLineString();
5✔
1003
                    poMultiLS->addGeometryDirectly(poLine);
5✔
1004
                }
1005
                else
1006
                {
1007
                    poLine = new OGRLineString();
23✔
1008
                }
1009
                poLine->addPoint(dfX, dfY);
28✔
1010
                READ_VARUINT32(m_pabyDataCur, pabyDataGeometryEnd,
28✔
1011
                               nCmdCountCombined);
1012
                nLineToCount = GetCmdCount(nCmdCountCombined);
28✔
1013
                for (unsigned i = 0; i < nLineToCount; i++)
59✔
1014
                {
1015
                    READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
31✔
1016
                    READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
31✔
1017
                    // if( nDX != 0 || nDY != 0 )
1018
                    {
1019
                        nX = AddWithOverflowAccepted(nX, nDX);
31✔
1020
                        nY = AddWithOverflowAccepted(nY, nDY);
31✔
1021
                        GetXY(nX, nY, dfX, dfY);
31✔
1022
                        poLine->addPoint(dfX, dfY);
31✔
1023
                    }
1024
                }
1025
            }
1026
            if (poMultiLS == nullptr && poLine != nullptr &&
43✔
1027
                m_poFeatureDefn->GetGeomType() == wkbMultiLineString)
20✔
1028
            {
1029
                poMultiLS = new OGRMultiLineString();
14✔
1030
                poMultiLS->addGeometryDirectly(poLine);
14✔
1031
            }
1032
            if (poMultiLS)
23✔
1033
            {
1034
                return poMultiLS;
17✔
1035
            }
1036
            else
1037
            {
1038
                return poLine;
6✔
1039
            }
1040
        }
1041
        else if (nGeomType == knGEOM_TYPE_POLYGON)
1,903✔
1042
        {
1043
            int externalIsClockwise = 0;
1,903✔
1044
            int nX = 0;
1,903✔
1045
            int nY = 0;
1,903✔
1046
            while (m_pabyDataCur < pabyDataGeometryEnd)
5,430✔
1047
            {
1048
                unsigned int nCmdCountCombined = 0;
3,527✔
1049
                unsigned int nLineToCount;
1050
                // Should be a moveto
1051
                SKIP_VARINT(m_pabyDataCur, pabyDataGeometryEnd);
3,527✔
1052
                int nDX = 0;
3,527✔
1053
                int nDY = 0;
3,527✔
1054
                READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
3,527✔
1055
                READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
3,527✔
1056
                nX = AddWithOverflowAccepted(nX, nDX);
3,527✔
1057
                nY = AddWithOverflowAccepted(nY, nDY);
3,527✔
1058
                double dfX;
1059
                double dfY;
1060
                GetXY(nX, nY, dfX, dfY);
3,527✔
1061
                poRing = new OGRLinearRing();
3,527✔
1062
                poRing->addPoint(dfX, dfY);
3,527✔
1063
                READ_VARUINT32(m_pabyDataCur, pabyDataGeometryEnd,
3,527✔
1064
                               nCmdCountCombined);
1065
                nLineToCount = GetCmdCount(nCmdCountCombined);
3,527✔
1066
                for (unsigned i = 0; i < nLineToCount; i++)
351,730✔
1067
                {
1068
                    READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDX);
348,203✔
1069
                    READ_VARSINT32(m_pabyDataCur, pabyDataGeometryEnd, nDY);
348,203✔
1070
                    // if( nDX != 0 || nDY != 0 )
1071
                    {
1072
                        nX = AddWithOverflowAccepted(nX, nDX);
348,203✔
1073
                        nY = AddWithOverflowAccepted(nY, nDY);
348,203✔
1074
                        GetXY(nX, nY, dfX, dfY);
348,203✔
1075
                        poRing->addPoint(dfX, dfY);
348,203✔
1076
                    }
1077
                }
1078
                // Should be a closepath
1079
                SKIP_VARINT(m_pabyDataCur, pabyDataGeometryEnd);
3,527✔
1080
                poRing->closeRings();
3,527✔
1081
                if (poPoly == nullptr)
3,527✔
1082
                {
1083
                    poPoly = new OGRPolygon();
1,903✔
1084
                    poPoly->addRingDirectly(poRing);
1,903✔
1085
                    externalIsClockwise = poRing->isClockwise();
1,903✔
1086
                    if (!externalIsClockwise && m_bEnforceExternalIsClockwise)
1,903✔
1087
                    {
1088
                        CPLError(CE_Failure, CPLE_AppDefined,
×
1089
                                 "Bad ring orientation detected");
1090
                        delete poPoly;
×
1091
                        delete poMultiPoly;
×
1092
                        return nullptr;
×
1093
                    }
1094
                }
1095
                else
1096
                {
1097
                    // Detect change of winding order to figure out if this is
1098
                    // an interior or exterior ring
1099
                    if (externalIsClockwise != poRing->isClockwise())
1,624✔
1100
                    {
1101
                        poPoly->addRingDirectly(poRing);
67✔
1102
                    }
1103
                    else
1104
                    {
1105
                        if (poMultiPoly == nullptr)
1,557✔
1106
                        {
1107
                            poMultiPoly = new OGRMultiPolygon();
716✔
1108
                            poMultiPoly->addGeometryDirectly(poPoly);
716✔
1109
                        }
1110

1111
                        poPoly = new OGRPolygon();
1,557✔
1112
                        poMultiPoly->addGeometryDirectly(poPoly);
1,557✔
1113
                        poPoly->addRingDirectly(poRing);
1,557✔
1114
                    }
1115
                }
1116
                poRing = nullptr;
3,527✔
1117
            }
1118
            if (poMultiPoly == nullptr && poPoly != nullptr &&
3,090✔
1119
                m_poFeatureDefn->GetGeomType() == wkbMultiPolygon)
1,187✔
1120
            {
1121
                poMultiPoly = new OGRMultiPolygon();
833✔
1122
                poMultiPoly->addGeometryDirectly(poPoly);
833✔
1123
            }
1124
            if (poMultiPoly)
1,903✔
1125
            {
1126
                return poMultiPoly;
1,549✔
1127
            }
1128
            else
1129
            {
1130
                return poPoly;
354✔
1131
            }
1132
        }
1133
    }
1134
    catch (const GPBException &e)
2✔
1135
    {
1136
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
1✔
1137
        delete poMultiPoint;
1✔
1138
        if (poMultiPoly)
1✔
1139
            delete poMultiPoly;
×
1140
        else if (poPoly)
1✔
1141
            delete poPoly;
×
1142
        if (poMultiLS)
1✔
1143
            delete poMultiLS;
×
1144
        else if (poLine)
1✔
1145
            delete poLine;
×
1146
        delete poRing;
1✔
1147
    }
1148
    return nullptr;
1✔
1149
}
1150

1151
/************************************************************************/
1152
/*                      SanitizeClippedGeometry()                       */
1153
/************************************************************************/
1154

1155
void OGRMVTLayer::SanitizeClippedGeometry(OGRGeometry *&poGeom)
636✔
1156
{
1157
    OGRwkbGeometryType eInGeomType = wkbFlatten(poGeom->getGeometryType());
636✔
1158
    const OGRwkbGeometryType eLayerGeomType = GetGeomType();
636✔
1159
    if (eLayerGeomType == wkbUnknown)
636✔
1160
    {
1161
        return;
×
1162
    }
1163

1164
    // GEOS intersection may return a mix of polygon and linestrings when
1165
    // intersection a multipolygon and a polygon
1166
    if (eInGeomType == wkbGeometryCollection)
636✔
1167
    {
1168
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
113✔
1169
        OGRGeometry *poTargetSingleGeom = nullptr;
113✔
1170
        OGRGeometryCollection *poTargetGC = nullptr;
113✔
1171
        OGRwkbGeometryType ePartGeom;
1172
        if (eLayerGeomType == wkbPoint || eLayerGeomType == wkbMultiPoint)
113✔
1173
        {
1174
            ePartGeom = wkbPoint;
×
1175
        }
1176
        else if (eLayerGeomType == wkbLineString ||
113✔
1177
                 eLayerGeomType == wkbMultiLineString)
1178
        {
1179
            ePartGeom = wkbLineString;
×
1180
        }
1181
        else
1182
        {
1183
            ePartGeom = wkbPolygon;
113✔
1184
        }
1185
        for (auto &&poSubGeom : poGC)
388✔
1186
        {
1187
            if (wkbFlatten(poSubGeom->getGeometryType()) == ePartGeom)
275✔
1188
            {
1189
                if (poTargetSingleGeom != nullptr)
162✔
1190
                {
1191
                    if (poTargetGC == nullptr)
49✔
1192
                    {
1193
                        poTargetGC = OGRGeometryFactory::createGeometry(
1194
                                         OGR_GT_GetCollection(ePartGeom))
1195
                                         ->toGeometryCollection();
49✔
1196
                        poGeom = poTargetGC;
49✔
1197
                        poTargetGC->addGeometryDirectly(poTargetSingleGeom);
49✔
1198
                    }
1199

1200
                    poTargetGC->addGeometry(poSubGeom);
49✔
1201
                }
1202
                else
1203
                {
1204
                    poTargetSingleGeom = poSubGeom->clone();
113✔
1205
                    poGeom = poTargetSingleGeom;
113✔
1206
                }
1207
            }
1208
        }
1209
        if (poGeom != poGC)
113✔
1210
        {
1211
            delete poGC;
113✔
1212
        }
1213
        eInGeomType = wkbFlatten(poGeom->getGeometryType());
113✔
1214
    }
1215

1216
    // Wrap single into multi if requested by the layer geometry type
1217
    if (OGR_GT_GetCollection(eInGeomType) == eLayerGeomType)
636✔
1218
    {
1219
        OGRGeometryCollection *poGC =
1220
            OGRGeometryFactory::createGeometry(eLayerGeomType)
1221
                ->toGeometryCollection();
383✔
1222
        poGC->addGeometryDirectly(poGeom);
383✔
1223
        poGeom = poGC;
383✔
1224
        return;
383✔
1225
    }
1226
}
1227

1228
/************************************************************************/
1229
/*                         GetNextRawFeature()                          */
1230
/************************************************************************/
1231

1232
OGRFeature *OGRMVTLayer::GetNextRawFeature()
2,794✔
1233
{
1234
    if (m_pabyDataCur == nullptr || m_pabyDataCur >= m_pabyDataEnd || m_bError)
2,794✔
1235
    {
1236
        return nullptr;
106✔
1237
    }
1238

1239
    unsigned int nKey = 0;
2,688✔
1240
    const GByte *pabyDataLimit = m_pabyDataEnd;
2,688✔
1241
    OGRFeature *poFeature = nullptr;
2,688✔
1242
    unsigned int nFeatureLength = 0;
2,688✔
1243
    unsigned int nGeomType = 0;
2,688✔
1244

1245
    try
1246
    {
1247
        while (true)
1248
        {
1249
            bool bOK = true;
2,692✔
1250

1251
            while (m_pabyDataCur < pabyDataLimit)
33,409✔
1252
            {
1253
                READ_VARUINT32(m_pabyDataCur, pabyDataLimit, nKey);
32,763✔
1254
                if (nKey == MAKE_KEY(knLAYER_FEATURES, WT_DATA))
32,763✔
1255
                {
1256
                    poFeature = new OGRFeature(m_poFeatureDefn);
2,046✔
1257
                    break;
2,046✔
1258
                }
1259
                else
1260
                {
1261
                    SKIP_UNKNOWN_FIELD(m_pabyDataCur, pabyDataLimit, FALSE);
30,717✔
1262
                }
1263
            }
1264

1265
            if (poFeature == nullptr)
2,692✔
1266
                return nullptr;
646✔
1267

1268
            READ_SIZE(m_pabyDataCur, pabyDataLimit, nFeatureLength);
2,046✔
1269
            const GByte *pabyDataFeatureEnd = m_pabyDataCur + nFeatureLength;
2,046✔
1270
            while (m_pabyDataCur < pabyDataFeatureEnd)
8,124✔
1271
            {
1272
                READ_VARUINT32(m_pabyDataCur, pabyDataFeatureEnd, nKey);
6,078✔
1273
                if (nKey == MAKE_KEY(knFEATURE_ID, WT_VARINT))
6,078✔
1274
                {
1275
                    GUIntBig nID = 0;
14✔
1276
                    READ_VARUINT64(m_pabyDataCur, pabyDataFeatureEnd, nID);
14✔
1277
                    poFeature->SetField("mvt_id", static_cast<GIntBig>(nID));
14✔
1278
                }
1279
                else if (nKey == MAKE_KEY(knFEATURE_TYPE, WT_VARINT))
6,064✔
1280
                {
1281
                    READ_VARUINT32(m_pabyDataCur, pabyDataFeatureEnd,
2,040✔
1282
                                   nGeomType);
1283
                }
1284
                else if (nKey == MAKE_KEY(knFEATURE_TAGS, WT_DATA))
4,024✔
1285
                {
1286
                    unsigned int nTagsSize = 0;
1,984✔
1287
                    READ_SIZE(m_pabyDataCur, pabyDataFeatureEnd, nTagsSize);
1,984✔
1288
                    const GByte *pabyDataTagsEnd = m_pabyDataCur + nTagsSize;
1,984✔
1289
                    while (m_pabyDataCur < pabyDataTagsEnd)
60,510✔
1290
                    {
1291
                        unsigned int nKeyIdx = 0;
58,526✔
1292
                        unsigned int nValIdx = 0;
58,526✔
1293
                        READ_VARUINT32(m_pabyDataCur, pabyDataTagsEnd, nKeyIdx);
58,526✔
1294
                        READ_VARUINT32(m_pabyDataCur, pabyDataTagsEnd, nValIdx);
58,526✔
1295
                        if (nKeyIdx < m_aosKeys.size() &&
117,052✔
1296
                            nValIdx < m_asValues.size())
58,526✔
1297
                        {
1298
                            const int nFieldIdx =
1299
                                m_poFeatureDefn->GetFieldIndex(
58,526✔
1300
                                    m_aosKeys[nKeyIdx]);
58,526✔
1301
                            if (nFieldIdx >= 0)
58,526✔
1302
                            {
1303
                                if (m_asValues[nValIdx].eType == OFTString)
58,526✔
1304
                                {
1305
                                    poFeature->SetField(
31,786✔
1306
                                        nFieldIdx,
1307
                                        m_asValues[nValIdx].sValue.String);
31,786✔
1308
                                }
1309
                                else if (m_asValues[nValIdx].eType ==
26,740✔
1310
                                         OFTInteger)
1311
                                {
1312
                                    poFeature->SetField(
25,260✔
1313
                                        nFieldIdx,
1314
                                        m_asValues[nValIdx].sValue.Integer);
25,260✔
1315
                                }
1316
                                else if (m_asValues[nValIdx].eType ==
1,480✔
1317
                                         OFTInteger64)
1318
                                {
1319
                                    poFeature->SetField(
436✔
1320
                                        nFieldIdx,
1321
                                        m_asValues[nValIdx].sValue.Integer64);
436✔
1322
                                }
1323
                                else if (m_asValues[nValIdx].eType == OFTReal)
1,044✔
1324
                                {
1325
                                    poFeature->SetField(
1,044✔
1326
                                        nFieldIdx,
1327
                                        m_asValues[nValIdx].sValue.Real);
1,044✔
1328
                                }
1329
                            }
1330
                        }
1331
                    }
1332
                }
1333
                else if (nKey == MAKE_KEY(knFEATURE_GEOMETRY, WT_DATA) &&
2,040✔
1334
                         nGeomType >= 1 && nGeomType <= 3)
2,038✔
1335
                {
1336
                    unsigned int nGeometrySize = 0;
2,038✔
1337
                    READ_SIZE(m_pabyDataCur, pabyDataFeatureEnd, nGeometrySize);
2,038✔
1338
                    const GByte *pabyDataGeometryEnd =
2,038✔
1339
                        m_pabyDataCur + nGeometrySize;
2,038✔
1340
                    OGRGeometry *poGeom =
1341
                        ParseGeometry(nGeomType, pabyDataGeometryEnd);
2,038✔
1342
                    if (poGeom)
2,038✔
1343
                    {
1344
                        // Clip geometry to tile extent if requested
1345
                        if (m_poDS->m_bClip && OGRGeometryFactory::haveGEOS())
2,037✔
1346
                        {
1347
                            OGREnvelope sEnvelope;
2,032✔
1348
                            poGeom->getEnvelope(&sEnvelope);
2,032✔
1349
                            if (sEnvelope.MinX >= m_dfTileMinX &&
2,032✔
1350
                                sEnvelope.MinY >= m_dfTileMinY &&
1,801✔
1351
                                sEnvelope.MaxX <= m_dfTileMaxX &&
1,648✔
1352
                                sEnvelope.MaxY <= m_dfTileMaxY)
1,436✔
1353
                            {
1354
                                // do nothing
1355
                            }
1356
                            else if (sEnvelope.MinX < m_dfTileMaxX &&
640✔
1357
                                     sEnvelope.MinY < m_dfTileMaxY &&
636✔
1358
                                     sEnvelope.MaxX > m_dfTileMinX &&
636✔
1359
                                     sEnvelope.MaxY > m_dfTileMinY)
636✔
1360
                            {
1361
                                OGRGeometry *poClipped =
1362
                                    poGeom->Intersection(&m_oClipPoly);
636✔
1363
                                if (poClipped)
636✔
1364
                                {
1365
                                    SanitizeClippedGeometry(poClipped);
636✔
1366
                                    if (poClipped->IsEmpty())
636✔
1367
                                    {
1368
                                        delete poClipped;
×
1369
                                        bOK = false;
×
1370
                                    }
1371
                                    else
1372
                                    {
1373
                                        poClipped->assignSpatialReference(
1,272✔
1374
                                            GetSpatialRef());
636✔
1375
                                        poFeature->SetGeometryDirectly(
636✔
1376
                                            poClipped);
1377
                                        delete poGeom;
636✔
1378
                                        poGeom = nullptr;
636✔
1379
                                    }
1380
                                }
636✔
1381
                            }
1382
                            else
1383
                            {
1384
                                bOK = false;
4✔
1385
                            }
1386
                        }
1387

1388
                        if (poGeom)
2,037✔
1389
                        {
1390
                            poGeom->assignSpatialReference(GetSpatialRef());
1,401✔
1391
                            poFeature->SetGeometryDirectly(poGeom);
1,401✔
1392
                        }
1393
                    }
1394

1395
                    m_pabyDataCur = pabyDataGeometryEnd;
2,038✔
1396
                }
1397
                else
1398
                {
1399
                    SKIP_UNKNOWN_FIELD(m_pabyDataCur, pabyDataFeatureEnd,
2✔
1400
                                       FALSE);
1401
                }
1402
            }
1403
            m_pabyDataCur = pabyDataFeatureEnd;
2,046✔
1404

1405
            if (bOK)
2,046✔
1406
            {
1407
                poFeature->SetFID(m_nFID);
2,042✔
1408
                m_nFID++;
2,042✔
1409
                return poFeature;
2,042✔
1410
            }
1411
            else
1412
            {
1413
                delete poFeature;
4✔
1414
                poFeature = nullptr;
4✔
1415
            }
1416
        }
4✔
1417
    }
1418
    catch (const GPBException &e)
×
1419
    {
1420
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
×
1421
        delete poFeature;
×
1422
        return nullptr;
×
1423
    }
1424
}
1425

1426
/************************************************************************/
1427
/*                             GetDataset()                             */
1428
/************************************************************************/
1429

1430
GDALDataset *OGRMVTLayer::GetDataset()
1✔
1431
{
1432
    return m_poDS;
1✔
1433
}
1434

1435
/************************************************************************/
1436
/*                         StripDummyEntries()                           */
1437
/************************************************************************/
1438

1439
static CPLStringList StripDummyEntries(const CPLStringList &aosInput)
98✔
1440
{
1441
    CPLStringList aosOutput;
196✔
1442
    for (int i = 0; i < aosInput.Count(); i++)
365✔
1443
    {
1444
        if (aosInput[i] != CPLString(".") && aosInput[i] != CPLString("..") &&
649✔
1445
            CPLString(aosInput[i]).find(".properties") == std::string::npos)
382✔
1446
        {
1447
            aosOutput.AddString(aosInput[i]);
115✔
1448
        }
1449
    }
1450
    return aosOutput.Sort();
196✔
1451
}
1452

1453
/************************************************************************/
1454
/*                       OGRMVTDirectoryLayer()                         */
1455
/************************************************************************/
1456

1457
OGRMVTDirectoryLayer::OGRMVTDirectoryLayer(
23✔
1458
    OGRMVTDataset *poDS, const char *pszLayerName, const char *pszDirectoryName,
1459
    const CPLJSONObject &oFields, const CPLJSONArray &oAttributesFromTileStats,
1460
    bool bJsonField, OGRwkbGeometryType eGeomType, const OGREnvelope *psExtent)
23✔
1461
    : m_poDS(poDS), m_osDirName(pszDirectoryName), m_bJsonField(bJsonField)
23✔
1462
{
1463
    m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
23✔
1464
    SetDescription(m_poFeatureDefn->GetName());
23✔
1465
    m_poFeatureDefn->SetGeomType(eGeomType);
23✔
1466
    m_poFeatureDefn->Reference();
23✔
1467

1468
    m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
23✔
1469

1470
    if (m_bJsonField)
23✔
1471
    {
1472
        OGRFieldDefn oFieldDefnId("mvt_id", OFTInteger64);
2✔
1473
        m_poFeatureDefn->AddFieldDefn(&oFieldDefnId);
1✔
1474
    }
1475
    else
1476
    {
1477
        InitFields(oFields, oAttributesFromTileStats);
22✔
1478
    }
1479

1480
    m_nZ = atoi(CPLGetFilename(m_osDirName));
23✔
1481
    SetMetadataItem("ZOOM_LEVEL", CPLSPrintf("%d", m_nZ));
23✔
1482
    m_bUseReadDir = CPLTestBool(CPLGetConfigOption(
23✔
1483
        "MVT_USE_READDIR", (!STARTS_WITH(m_osDirName, "/vsicurl") &&
23✔
1484
                            !STARTS_WITH(m_osDirName, "http://") &&
23✔
1485
                            !STARTS_WITH(m_osDirName, "https://"))
19✔
1486
                               ? "YES"
1487
                               : "NO"));
1488
    if (m_bUseReadDir)
23✔
1489
    {
1490
        m_aosDirContent = VSIReadDirEx(m_osDirName, knMAX_FILES_PER_DIR);
18✔
1491
        if (m_aosDirContent.Count() >= knMAX_FILES_PER_DIR)
18✔
1492
        {
1493
            CPLDebug("MVT", "Disabling readdir");
×
1494
            m_aosDirContent.Clear();
×
1495
            m_bUseReadDir = false;
×
1496
        }
1497
        m_aosDirContent = StripDummyEntries(m_aosDirContent);
18✔
1498
    }
1499
    OGRMVTDirectoryLayer::ResetReading();
23✔
1500

1501
    if (psExtent)
23✔
1502
    {
1503
        m_sExtent = *psExtent;
19✔
1504
    }
1505

1506
    OGRMVTDirectoryLayer::SetSpatialFilter(nullptr);
23✔
1507

1508
    // If the metadata contains an empty fields object, this may be a sign
1509
    // that it doesn't know the schema. In that case check if a tile has
1510
    // attributes, and in that case create a json field.
1511
    if (!m_bJsonField && oFields.IsValid() && oFields.GetChildren().empty())
23✔
1512
    {
1513
        m_bJsonField = true;
13✔
1514
        OpenTileIfNeeded();
13✔
1515
        m_bJsonField = false;
13✔
1516

1517
        if (m_poCurrentTile)
13✔
1518
        {
1519
            OGRLayer *poUnderlyingLayer =
1520
                m_poCurrentTile->GetLayerByName(GetName());
11✔
1521
            // There is at least the mvt_id field
1522
            if (poUnderlyingLayer->GetLayerDefn()->GetFieldCount() > 1)
11✔
1523
            {
1524
                m_bJsonField = true;
×
1525
            }
1526
        }
1527
        OGRMVTDirectoryLayer::ResetReading();
13✔
1528
    }
1529

1530
    if (m_bJsonField)
23✔
1531
    {
1532
        OGRFieldDefn oFieldDefn("json", OFTString);
2✔
1533
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
1✔
1534
    }
1535
}
23✔
1536

1537
/************************************************************************/
1538
/*                      ~OGRMVTDirectoryLayer()                         */
1539
/************************************************************************/
1540

1541
OGRMVTDirectoryLayer::~OGRMVTDirectoryLayer()
46✔
1542
{
1543
    delete m_poCurrentTile;
23✔
1544
}
46✔
1545

1546
/************************************************************************/
1547
/*                          ResetReading()                              */
1548
/************************************************************************/
1549

1550
void OGRMVTDirectoryLayer::ResetReading()
139✔
1551
{
1552
    m_bEOF = false;
139✔
1553
    m_nXIndex = -1;
139✔
1554
    m_nYIndex = -1;
139✔
1555
    delete m_poCurrentTile;
139✔
1556
    m_poCurrentTile = nullptr;
139✔
1557
}
139✔
1558

1559
/************************************************************************/
1560
/*                            IsBetween()                               */
1561
/************************************************************************/
1562

1563
static bool IsBetween(int nVal, int nMin, int nMax)
136✔
1564
{
1565
    return nVal >= nMin && nVal <= nMax;
136✔
1566
}
1567

1568
/************************************************************************/
1569
/*                          ReadNewSubDir()                             */
1570
/************************************************************************/
1571

1572
void OGRMVTDirectoryLayer::ReadNewSubDir()
115✔
1573
{
1574
    delete m_poCurrentTile;
115✔
1575
    m_poCurrentTile = nullptr;
115✔
1576
    if (m_bUseReadDir || !m_aosDirContent.empty())
115✔
1577
    {
1578
        while (
2✔
1579
            m_nXIndex < m_aosDirContent.Count() &&
173✔
1580
            (CPLGetValueType(m_aosDirContent[m_nXIndex]) != CPL_VALUE_INTEGER ||
69✔
1581
             !IsBetween(atoi(m_aosDirContent[m_nXIndex]), m_nFilterMinX,
69✔
1582
                        m_nFilterMaxX)))
1583
        {
1584
            m_nXIndex++;
2✔
1585
        }
1586
    }
1587
    else
1588
    {
1589
        if (m_nXIndex < m_nFilterMinX)
13✔
1590
            m_nXIndex = m_nFilterMinX;
×
1591
        else if (m_nXIndex > m_nFilterMaxX)
13✔
1592
            m_nXIndex = (1 << m_nZ);
4✔
1593
    }
1594
    if (m_nXIndex < ((m_bUseReadDir || !m_aosDirContent.empty())
128✔
1595
                         ? m_aosDirContent.Count()
115✔
1596
                         : (1 << m_nZ)))
13✔
1597
    {
1598
        m_aosSubDirName =
1599
            CPLFormFilenameSafe(m_osDirName,
76✔
1600
                                (m_bUseReadDir || !m_aosDirContent.empty())
76✔
1601
                                    ? m_aosDirContent[m_nXIndex]
67✔
1602
                                    : CPLSPrintf("%d", m_nXIndex),
9✔
1603
                                nullptr);
76✔
1604
        if (m_bUseReadDir)
76✔
1605
        {
1606
            m_aosSubDirContent =
1607
                VSIReadDirEx(m_aosSubDirName, knMAX_FILES_PER_DIR);
67✔
1608
            if (m_aosSubDirContent.Count() >= knMAX_FILES_PER_DIR)
67✔
1609
            {
1610
                CPLDebug("MVT", "Disabling readdir");
×
1611
                m_aosSubDirContent.Clear();
×
1612
                m_bUseReadDir = false;
×
1613
            }
1614
            m_aosSubDirContent = StripDummyEntries(m_aosSubDirContent);
67✔
1615
        }
1616
        m_nYIndex = -1;
76✔
1617
        OpenTileIfNeeded();
76✔
1618
    }
1619
    else
1620
    {
1621
        m_bEOF = true;
39✔
1622
    }
1623
}
115✔
1624

1625
/************************************************************************/
1626
/*                            OpenTile()                                */
1627
/************************************************************************/
1628

1629
void OGRMVTDirectoryLayer::OpenTile()
76✔
1630
{
1631
    delete m_poCurrentTile;
76✔
1632
    m_poCurrentTile = nullptr;
76✔
1633
    if (m_nYIndex < (m_bUseReadDir ? m_aosSubDirContent.Count() : (1 << m_nZ)))
76✔
1634
    {
1635
        CPLString osFilename = CPLFormFilenameSafe(
76✔
1636
            m_aosSubDirName,
1637
            m_bUseReadDir ? m_aosSubDirContent[m_nYIndex]
76✔
1638
                          : CPLSPrintf("%d.%s", m_nYIndex,
9✔
1639
                                       m_poDS->m_osTileExtension.c_str()),
9✔
1640
            nullptr);
152✔
1641
        GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(), GA_ReadOnly);
76✔
1642
        oOpenInfo.papszOpenOptions = CSLSetNameValue(
76✔
1643
            nullptr, "METADATA_FILE",
1644
            m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
76✔
1645
        oOpenInfo.papszOpenOptions = CSLSetNameValue(
76✔
1646
            oOpenInfo.papszOpenOptions, "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
1647
        m_poCurrentTile =
76✔
1648
            OGRMVTDataset::Open(&oOpenInfo, /* bRecurseAllowed = */ false);
76✔
1649
        CSLDestroy(oOpenInfo.papszOpenOptions);
76✔
1650

1651
        int nX = (m_bUseReadDir || !m_aosDirContent.empty())
9✔
1652
                     ? atoi(m_aosDirContent[m_nXIndex])
85✔
1653
                     : m_nXIndex;
76✔
1654
        int nY =
1655
            m_bUseReadDir ? atoi(m_aosSubDirContent[m_nYIndex]) : m_nYIndex;
76✔
1656
        m_nFIDBase = (static_cast<GIntBig>(nX) << m_nZ) | nY;
76✔
1657
    }
1658
}
76✔
1659

1660
/************************************************************************/
1661
/*                         OpenTileIfNeeded()                           */
1662
/************************************************************************/
1663

1664
void OGRMVTDirectoryLayer::OpenTileIfNeeded()
220✔
1665
{
1666
    if (m_nXIndex < 0)
220✔
1667
    {
1668
        m_nXIndex = 0;
75✔
1669
        ReadNewSubDir();
75✔
1670
    }
1671
    while ((m_poCurrentTile == nullptr && !m_bEOF) ||
556✔
1672
           (m_poCurrentTile != nullptr &&
220✔
1673
            m_poCurrentTile->GetLayerByName(GetName()) == nullptr))
176✔
1674
    {
1675
        m_nYIndex++;
116✔
1676
        if (m_bUseReadDir)
116✔
1677
        {
1678
            while (m_nYIndex < m_aosSubDirContent.Count() &&
170✔
1679
                   (CPLGetValueType(
67✔
1680
                        CPLGetBasenameSafe(m_aosSubDirContent[m_nYIndex])
170✔
1681
                            .c_str()) != CPL_VALUE_INTEGER ||
67✔
1682
                    !IsBetween(atoi(m_aosSubDirContent[m_nYIndex]),
67✔
1683
                               m_nFilterMinY, m_nFilterMaxY)))
1684
            {
1685
                m_nYIndex++;
×
1686
            }
1687
        }
1688
        else
1689
        {
1690
            if (m_nYIndex < m_nFilterMinY)
13✔
1691
                m_nYIndex = m_nFilterMinY;
×
1692
            else if (m_nYIndex > m_nFilterMaxY)
13✔
1693
                m_nYIndex = (1 << m_nZ);
4✔
1694
        }
1695
        if (m_nYIndex ==
116✔
1696
            (m_bUseReadDir ? m_aosSubDirContent.Count() : (1 << m_nZ)))
116✔
1697
        {
1698
            m_nXIndex++;
40✔
1699
            ReadNewSubDir();
40✔
1700
        }
1701
        else
1702
        {
1703
            OpenTile();
76✔
1704
        }
1705
    }
1706
}
220✔
1707

1708
/************************************************************************/
1709
/*                         GetFeatureCount()                            */
1710
/************************************************************************/
1711

1712
GIntBig OGRMVTDirectoryLayer::GetFeatureCount(int bForce)
16✔
1713
{
1714
    if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
16✔
1715
    {
1716
        GIntBig nFeatureCount = 0;
10✔
1717
        ResetReading();
10✔
1718
        while (true)
1719
        {
1720
            OpenTileIfNeeded();
21✔
1721
            if (m_poCurrentTile == nullptr)
21✔
1722
                break;
10✔
1723
            OGRLayer *poUnderlyingLayer =
1724
                m_poCurrentTile->GetLayerByName(GetName());
11✔
1725
            nFeatureCount += poUnderlyingLayer->GetFeatureCount(bForce);
11✔
1726
            delete m_poCurrentTile;
11✔
1727
            m_poCurrentTile = nullptr;
11✔
1728
        }
11✔
1729
        ResetReading();
10✔
1730
        return nFeatureCount;
10✔
1731
    }
1732
    return OGRLayer::GetFeatureCount(bForce);
6✔
1733
}
1734

1735
/************************************************************************/
1736
/*                         ISetSpatialFilter()                          */
1737
/************************************************************************/
1738

1739
OGRErr OGRMVTDirectoryLayer::ISetSpatialFilter(int iGeomField,
47✔
1740
                                               const OGRGeometry *poGeomIn)
1741
{
1742
    OGRLayer::ISetSpatialFilter(iGeomField, poGeomIn);
47✔
1743

1744
    OGREnvelope sEnvelope;
47✔
1745
    if (m_poFilterGeom != nullptr)
47✔
1746
        sEnvelope = m_sFilterEnvelope;
7✔
1747
    if (m_sExtent.IsInit())
47✔
1748
    {
1749
        if (sEnvelope.IsInit())
43✔
1750
            sEnvelope.Intersect(m_sExtent);
7✔
1751
        else
1752
            sEnvelope = m_sExtent;
36✔
1753
    }
1754

1755
    if (sEnvelope.IsInit() && sEnvelope.MinX >= -10 * m_poDS->GetTileDim0() &&
85✔
1756
        sEnvelope.MinY >= -10 * m_poDS->GetTileDim0() &&
38✔
1757
        sEnvelope.MaxX <=
38✔
1758
            10 * m_poDS->GetTileDim0() * m_poDS->GetTileMatrixWidth0() &&
123✔
1759
        sEnvelope.MaxY <=
38✔
1760
            10 * m_poDS->GetTileDim0() * m_poDS->GetTileMatrixHeight0())
38✔
1761
    {
1762
        const double dfTileDim = m_poDS->GetTileDim0() / (1 << m_nZ);
38✔
1763
        m_nFilterMinX = std::max(
38✔
1764
            0, static_cast<int>(floor(
76✔
1765
                   (sEnvelope.MinX - m_poDS->GetTopXOrigin()) / dfTileDim)));
38✔
1766
        m_nFilterMinY = std::max(
38✔
1767
            0, static_cast<int>(floor(
76✔
1768
                   (m_poDS->GetTopYOrigin() - sEnvelope.MaxY) / dfTileDim)));
38✔
1769
        m_nFilterMaxX = std::min(
38✔
1770
            static_cast<int>(
76✔
1771
                ceil((sEnvelope.MaxX - m_poDS->GetTopXOrigin()) / dfTileDim)),
76✔
1772
            static_cast<int>(std::min<int64_t>(
38✔
1773
                INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
76✔
1774
                                 m_poDS->GetTileMatrixWidth0() -
38✔
1775
                             1)));
76✔
1776
        m_nFilterMaxY = std::min(
38✔
1777
            static_cast<int>(
76✔
1778
                ceil((m_poDS->GetTopYOrigin() - sEnvelope.MinY) / dfTileDim)),
76✔
1779
            static_cast<int>(std::min<int64_t>(
38✔
1780
                INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
76✔
1781
                                 m_poDS->GetTileMatrixHeight0() -
76✔
1782
                             1)));
76✔
1783
    }
1784
    else
1785
    {
1786
        m_nFilterMinX = 0;
9✔
1787
        m_nFilterMinY = 0;
9✔
1788
        m_nFilterMaxX = static_cast<int>(
9✔
1789
            std::min<int64_t>(INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
18✔
1790
                                               m_poDS->GetTileMatrixWidth0() -
9✔
1791
                                           1));
9✔
1792
        m_nFilterMaxY = static_cast<int>(
9✔
1793
            std::min<int64_t>(INT_MAX, (static_cast<int64_t>(1) << m_nZ) *
18✔
1794
                                               m_poDS->GetTileMatrixHeight0() -
18✔
1795
                                           1));
9✔
1796
    }
1797

1798
    return OGRERR_NONE;
47✔
1799
}
1800

1801
/************************************************************************/
1802
/*                           TestCapability()                           */
1803
/************************************************************************/
1804

1805
int OGRMVTDirectoryLayer::TestCapability(const char *pszCap)
35✔
1806
{
1807
    if (EQUAL(pszCap, OLCFastGetExtent))
35✔
1808
    {
1809
        return TRUE;
2✔
1810
    }
1811
    return OGRMVTLayerBase::TestCapability(pszCap);
33✔
1812
}
1813

1814
/************************************************************************/
1815
/*                            IGetExtent()                              */
1816
/************************************************************************/
1817

1818
OGRErr OGRMVTDirectoryLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
4✔
1819
                                        bool bForce)
1820
{
1821
    if (m_sExtent.IsInit())
4✔
1822
    {
1823
        *psExtent = m_sExtent;
4✔
1824
        return OGRERR_NONE;
4✔
1825
    }
1826
    return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
×
1827
}
1828

1829
/************************************************************************/
1830
/*                         CreateFeatureFrom()                          */
1831
/************************************************************************/
1832

1833
OGRFeature *OGRMVTDirectoryLayer::CreateFeatureFrom(OGRFeature *poSrcFeature)
59✔
1834
{
1835

1836
    return OGRMVTCreateFeatureFrom(poSrcFeature, m_poFeatureDefn, m_bJsonField,
59✔
1837
                                   GetSpatialRef());
118✔
1838
}
1839

1840
/************************************************************************/
1841
/*                         GetNextRawFeature()                          */
1842
/************************************************************************/
1843

1844
OGRFeature *OGRMVTDirectoryLayer::GetNextRawFeature()
110✔
1845
{
1846
    while (true)
1847
    {
1848
        OpenTileIfNeeded();
110✔
1849
        if (m_poCurrentTile == nullptr)
110✔
1850
            return nullptr;
28✔
1851
        OGRLayer *poUnderlyingLayer =
1852
            m_poCurrentTile->GetLayerByName(GetName());
82✔
1853
        OGRFeature *poUnderlyingFeature = poUnderlyingLayer->GetNextFeature();
82✔
1854
        if (poUnderlyingFeature != nullptr)
82✔
1855
        {
1856
            OGRFeature *poFeature = CreateFeatureFrom(poUnderlyingFeature);
57✔
1857
            poFeature->SetFID(m_nFIDBase +
114✔
1858
                              (poUnderlyingFeature->GetFID() << (2 * m_nZ)));
57✔
1859
            delete poUnderlyingFeature;
57✔
1860
            return poFeature;
57✔
1861
        }
1862
        else
1863
        {
1864
            delete m_poCurrentTile;
25✔
1865
            m_poCurrentTile = nullptr;
25✔
1866
        }
1867
    }
25✔
1868
}
1869

1870
/************************************************************************/
1871
/*                           GetFeature()                               */
1872
/************************************************************************/
1873

1874
OGRFeature *OGRMVTDirectoryLayer::GetFeature(GIntBig nFID)
5✔
1875
{
1876
    const int nX = static_cast<int>(nFID & ((1 << m_nZ) - 1));
5✔
1877
    const int nY = static_cast<int>((nFID >> m_nZ) & ((1 << m_nZ) - 1));
5✔
1878
    const GIntBig nTileFID = nFID >> (2 * m_nZ);
5✔
1879
    const CPLString osFilename = CPLFormFilenameSafe(
15✔
1880
        CPLFormFilenameSafe(m_osDirName, CPLSPrintf("%d", nX), nullptr).c_str(),
5✔
1881
        CPLSPrintf("%d.%s", nY, m_poDS->m_osTileExtension.c_str()), nullptr);
15✔
1882
    GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(), GA_ReadOnly);
5✔
1883
    oOpenInfo.papszOpenOptions = CSLSetNameValue(
5✔
1884
        nullptr, "METADATA_FILE",
1885
        m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
5✔
1886
    oOpenInfo.papszOpenOptions = CSLSetNameValue(
5✔
1887
        oOpenInfo.papszOpenOptions, "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
1888
    GDALDataset *poTile =
1889
        OGRMVTDataset::Open(&oOpenInfo, /* bRecurseAllowed = */ false);
5✔
1890
    CSLDestroy(oOpenInfo.papszOpenOptions);
5✔
1891
    OGRFeature *poFeature = nullptr;
5✔
1892
    if (poTile)
5✔
1893
    {
1894
        OGRLayer *poLayer = poTile->GetLayerByName(GetName());
5✔
1895
        if (poLayer)
5✔
1896
        {
1897
            OGRFeature *poUnderlyingFeature = poLayer->GetFeature(nTileFID);
5✔
1898
            if (poUnderlyingFeature)
5✔
1899
            {
1900
                poFeature = CreateFeatureFrom(poUnderlyingFeature);
2✔
1901
                poFeature->SetFID(nFID);
2✔
1902
            }
1903
            delete poUnderlyingFeature;
5✔
1904
        }
1905
    }
1906
    delete poTile;
5✔
1907
    return poFeature;
10✔
1908
}
1909

1910
/************************************************************************/
1911
/*                             GetDataset()                             */
1912
/************************************************************************/
1913

1914
GDALDataset *OGRMVTDirectoryLayer::GetDataset()
1✔
1915
{
1916
    return m_poDS;
1✔
1917
}
1918

1919
/************************************************************************/
1920
/*                           OGRMVTDataset()                            */
1921
/************************************************************************/
1922

1923
OGRMVTDataset::OGRMVTDataset(GByte *pabyData)
963✔
1924
    : m_pabyData(pabyData), m_poSRS(new OGRSpatialReference())
963✔
1925
{
1926
    m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
963✔
1927

1928
    m_bClip = CPLTestBool(CPLGetConfigOption("OGR_MVT_CLIP", "YES"));
963✔
1929

1930
    // Default WebMercator tiling scheme
1931
    InitWebMercatorTilingScheme(m_poSRS, m_dfTopXOrigin, m_dfTopYOrigin,
963✔
1932
                                m_dfTileDim0);
963✔
1933
}
963✔
1934

1935
/************************************************************************/
1936
/*                           ~OGRMVTDataset()                           */
1937
/************************************************************************/
1938

1939
OGRMVTDataset::~OGRMVTDataset()
1,926✔
1940
{
1941
    VSIFree(m_pabyData);
963✔
1942
    if (!m_osMetadataMemFilename.empty())
963✔
1943
        VSIUnlink(m_osMetadataMemFilename);
17✔
1944
    if (m_poSRS)
963✔
1945
        m_poSRS->Release();
960✔
1946
}
1,926✔
1947

1948
/************************************************************************/
1949
/*                              GetLayer()                              */
1950
/************************************************************************/
1951

1952
OGRLayer *OGRMVTDataset::GetLayer(int iLayer)
1,666✔
1953

1954
{
1955
    if (iLayer < 0 || iLayer >= GetLayerCount())
1,666✔
1956
        return nullptr;
4✔
1957
    return m_apoLayers[iLayer].get();
1,662✔
1958
}
1959

1960
/************************************************************************/
1961
/*                             Identify()                               */
1962
/************************************************************************/
1963

1964
static int OGRMVTDriverIdentify(GDALOpenInfo *poOpenInfo)
52,871✔
1965

1966
{
1967
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
52,871✔
1968
        return TRUE;
1,780✔
1969

1970
    if (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl"))
51,091✔
1971
    {
1972
        if (CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
1✔
1973
            CPL_VALUE_INTEGER)
1974
        {
1975
            return TRUE;
×
1976
        }
1977
    }
1978

1979
    if (poOpenInfo->bIsDirectory)
51,091✔
1980
    {
1981
        if (CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
717✔
1982
            CPL_VALUE_INTEGER)
1983
        {
1984
            VSIStatBufL sStat;
1985
            CPLString osMetadataFile(CPLFormFilenameSafe(
60✔
1986
                CPLGetPathSafe(poOpenInfo->pszFilename).c_str(),
30✔
1987
                "metadata.json", nullptr));
30✔
1988
            const char *pszMetadataFile = CSLFetchNameValue(
60✔
1989
                poOpenInfo->papszOpenOptions, "METADATA_FILE");
30✔
1990
            if (pszMetadataFile)
30✔
1991
            {
1992
                osMetadataFile = pszMetadataFile;
4✔
1993
            }
1994
            if (!osMetadataFile.empty() &&
56✔
1995
                (STARTS_WITH(osMetadataFile, "http://") ||
26✔
1996
                 STARTS_WITH(osMetadataFile, "https://") ||
26✔
1997
                 VSIStatL(osMetadataFile, &sStat) == 0))
26✔
1998
            {
1999
                return TRUE;
24✔
2000
            }
2001
            if (pszMetadataFile == nullptr)
6✔
2002
            {
2003
                // tileserver-gl metadata file:
2004
                // If opening /path/to/foo/0, try looking for /path/to/foo.json
2005
                CPLString osParentDir(CPLGetPathSafe(poOpenInfo->pszFilename));
2✔
2006
                osMetadataFile =
2007
                    CPLFormFilenameSafe(CPLGetPathSafe(osParentDir).c_str(),
4✔
2008
                                        CPLGetFilename(osParentDir), "json");
2✔
2009
                if (VSIStatL(osMetadataFile, &sStat) == 0)
2✔
2010
                {
2011
                    return TRUE;
2✔
2012
                }
2013
            }
2014

2015
            // At least 3 files, to include the dummy . and ..
2016
            const CPLStringList aosDirContent = StripDummyEntries(
2017
                CPLStringList(VSIReadDirEx(poOpenInfo->pszFilename, 3)));
4✔
2018
            if (!aosDirContent.empty() &&
8✔
2019
                CPLGetValueType(aosDirContent[0]) == CPL_VALUE_INTEGER)
4✔
2020
            {
2021
                const std::string osSubDir = CPLFormFilenameSafe(
2022
                    poOpenInfo->pszFilename, aosDirContent[0], nullptr);
4✔
2023
                // At least 3 files, to include the dummy . and ..
2024
                const CPLStringList aosSubDirContent = StripDummyEntries(
2025
                    CPLStringList(VSIReadDirEx(osSubDir.c_str(), 10)));
4✔
2026
                const std::string osTileExtension(CSLFetchNameValueDef(
2027
                    poOpenInfo->papszOpenOptions, "TILE_EXTENSION", "pbf"));
4✔
2028
                for (int i = 0; i < aosSubDirContent.Count(); i++)
4✔
2029
                {
2030
                    if (CPLGetValueType(
4✔
2031
                            CPLGetBasenameSafe(aosSubDirContent[i]).c_str()) ==
8✔
2032
                        CPL_VALUE_INTEGER)
2033
                    {
2034
                        const std::string osExtension(
2035
                            CPLGetExtensionSafe(aosSubDirContent[i]));
4✔
2036
                        if (EQUAL(osExtension.c_str(),
4✔
2037
                                  osTileExtension.c_str()) ||
4✔
2038
                            EQUAL(osExtension.c_str(), "mvt"))
×
2039
                        {
2040
                            return TRUE;
4✔
2041
                        }
2042
                    }
2043
                }
2044
            }
2045
        }
2046
        return FALSE;
687✔
2047
    }
2048

2049
    if (poOpenInfo->nHeaderBytes <= 2)
50,374✔
2050
        return FALSE;
47,746✔
2051

2052
    // GZip header ?
2053
    if (poOpenInfo->pabyHeader[0] == 0x1F && poOpenInfo->pabyHeader[1] == 0x8B)
2,628✔
2054
    {
2055
        // Prevent recursion
2056
        if (STARTS_WITH(poOpenInfo->pszFilename, "/vsigzip/"))
36✔
2057
        {
2058
            return FALSE;
×
2059
        }
2060
        CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
2061
                                      false);
72✔
2062
        GDALOpenInfo oOpenInfo(
2063
            (CPLString("/vsigzip/") + poOpenInfo->pszFilename).c_str(),
72✔
2064
            GA_ReadOnly);
72✔
2065
        return OGRMVTDriverIdentify(&oOpenInfo);
36✔
2066
    }
2067

2068
    // The GPB macros assume that the buffer is nul terminated,
2069
    // which is the case
2070
    const GByte *pabyData = reinterpret_cast<GByte *>(poOpenInfo->pabyHeader);
2,592✔
2071
    const GByte *const pabyDataStart = pabyData;
2,592✔
2072
    const GByte *pabyLayerStart;
2073
    const GByte *const pabyDataLimit = pabyData + poOpenInfo->nHeaderBytes;
2,592✔
2074
    const GByte *pabyLayerEnd = pabyDataLimit;
2,592✔
2075
    int nKey = 0;
2,592✔
2076
    unsigned int nLayerLength = 0;
2,592✔
2077
    bool bLayerNameFound = false;
2,592✔
2078
    bool bKeyFound = false;
2,592✔
2079
    bool bFeatureFound = false;
2,592✔
2080
    bool bVersionFound = false;
2,592✔
2081

2082
    try
2083
    {
2084
        READ_FIELD_KEY(nKey);
2,592✔
2085
        if (nKey != MAKE_KEY(knLAYER, WT_DATA))
2,590✔
2086
            return FALSE;
2,543✔
2087
        READ_VARUINT32(pabyData, pabyDataLimit, nLayerLength);
47✔
2088
        pabyLayerStart = pabyData;
47✔
2089

2090
        // Sanity check on layer length
2091
        if (nLayerLength < static_cast<unsigned>(poOpenInfo->nHeaderBytes -
47✔
2092
                                                 (pabyData - pabyDataStart)))
47✔
2093
        {
2094
            if (pabyData[nLayerLength] != MAKE_KEY(knLAYER, WT_DATA))
5✔
2095
                return FALSE;
1✔
2096
            pabyLayerEnd = pabyData + nLayerLength;
4✔
2097
        }
2098
        else if (nLayerLength > 10 * 1024 * 1024)
42✔
2099
        {
2100
            return FALSE;
×
2101
        }
2102

2103
        // Quick scan on partial layer content to see if it seems to conform to
2104
        // the proto
2105
        while (pabyData < pabyLayerEnd)
534✔
2106
        {
2107
            READ_VARUINT32(pabyData, pabyLayerEnd, nKey);
490✔
2108
            auto nFieldNumber = GET_FIELDNUMBER(nKey);
490✔
2109
            auto nWireType = GET_WIRETYPE(nKey);
490✔
2110
            if (nFieldNumber == knLAYER_NAME)
490✔
2111
            {
2112
                if (nWireType != WT_DATA)
46✔
2113
                {
2114
                    CPLDebug("MVT", "Invalid wire type for layer_name field");
×
2115
                }
2116
                char *pszLayerName = nullptr;
46✔
2117
                unsigned int nTextSize = 0;
46✔
2118
                READ_TEXT_WITH_SIZE(pabyData, pabyLayerEnd, pszLayerName,
46✔
2119
                                    nTextSize);
2120
                if (nTextSize == 0 || !CPLIsUTF8(pszLayerName, nTextSize))
46✔
2121
                {
2122
                    CPLFree(pszLayerName);
×
2123
                    CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
×
2124
                    return FALSE;
×
2125
                }
2126
                CPLFree(pszLayerName);
46✔
2127
                bLayerNameFound = true;
46✔
2128
            }
2129
            else if (nFieldNumber == knLAYER_FEATURES)
444✔
2130
            {
2131
                if (nWireType != WT_DATA)
50✔
2132
                {
2133
                    CPLDebug("MVT",
×
2134
                             "Invalid wire type for layer_features field");
2135
                }
2136
                unsigned int nFeatureLength = 0;
50✔
2137
                unsigned int nGeomType = 0;
50✔
2138
                READ_VARUINT32(pabyData, pabyLayerEnd, nFeatureLength);
50✔
2139
                if (nFeatureLength > nLayerLength - (pabyData - pabyLayerStart))
50✔
2140
                {
2141
                    CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
×
2142
                    return FALSE;
×
2143
                }
2144
                bFeatureFound = true;
50✔
2145

2146
                const GByte *const pabyDataFeatureStart = pabyData;
50✔
2147
                const GByte *const pabyDataFeatureEnd =
2148
                    pabyDataStart +
2149
                    std::min(static_cast<int>(pabyData + nFeatureLength -
100✔
2150
                                              pabyDataStart),
2151
                             poOpenInfo->nHeaderBytes);
50✔
2152
                while (pabyData < pabyDataFeatureEnd)
162✔
2153
                {
2154
                    READ_VARUINT32(pabyData, pabyDataFeatureEnd, nKey);
114✔
2155
                    nFieldNumber = GET_FIELDNUMBER(nKey);
114✔
2156
                    nWireType = GET_WIRETYPE(nKey);
114✔
2157
                    if (nFieldNumber == knFEATURE_TYPE)
114✔
2158
                    {
2159
                        if (nWireType != WT_VARINT)
46✔
2160
                        {
2161
                            CPLDebug(
×
2162
                                "MVT",
2163
                                "Invalid wire type for feature_type field");
2164
                            return FALSE;
×
2165
                        }
2166
                        READ_VARUINT32(pabyData, pabyDataFeatureEnd, nGeomType);
46✔
2167
                        if (nGeomType > knGEOM_TYPE_POLYGON)
46✔
2168
                        {
2169
                            CPLDebug("MVT", "Protobuf error: line %d",
×
2170
                                     __LINE__);
2171
                            return FALSE;
×
2172
                        }
2173
                    }
2174
                    else if (nFieldNumber == knFEATURE_TAGS)
68✔
2175
                    {
2176
                        if (nWireType != WT_DATA)
18✔
2177
                        {
2178
                            CPLDebug(
×
2179
                                "MVT",
2180
                                "Invalid wire type for feature_tags field");
2181
                            return FALSE;
×
2182
                        }
2183
                        unsigned int nTagsSize = 0;
18✔
2184
                        READ_VARUINT32(pabyData, pabyDataFeatureEnd, nTagsSize);
18✔
2185
                        if (nTagsSize == 0 ||
18✔
2186
                            nTagsSize > nFeatureLength -
18✔
2187
                                            (pabyData - pabyDataFeatureStart))
18✔
2188
                        {
2189
                            CPLDebug("MVT", "Protobuf error: line %d",
×
2190
                                     __LINE__);
2191
                            return FALSE;
×
2192
                        }
2193
                        const GByte *const pabyDataTagsEnd =
2194
                            pabyDataStart +
2195
                            std::min(static_cast<int>(pabyData + nTagsSize -
36✔
2196
                                                      pabyDataStart),
2197
                                     poOpenInfo->nHeaderBytes);
18✔
2198
                        while (pabyData < pabyDataTagsEnd)
400✔
2199
                        {
2200
                            unsigned int nKeyIdx = 0;
382✔
2201
                            unsigned int nValIdx = 0;
382✔
2202
                            READ_VARUINT32(pabyData, pabyDataTagsEnd, nKeyIdx);
382✔
2203
                            READ_VARUINT32(pabyData, pabyDataTagsEnd, nValIdx);
382✔
2204
                            if (nKeyIdx > 10 * 1024 * 1024 ||
382✔
2205
                                nValIdx > 10 * 1024 * 1024)
2206
                            {
2207
                                CPLDebug("MVT", "Protobuf error: line %d",
×
2208
                                         __LINE__);
2209
                                return FALSE;
×
2210
                            }
2211
                        }
2212
                    }
2213
                    else if (nFieldNumber == knFEATURE_GEOMETRY &&
50✔
2214
                             nWireType != WT_DATA)
2215
                    {
2216
                        CPLDebug(
×
2217
                            "MVT",
2218
                            "Invalid wire type for feature_geometry field");
2219
                        return FALSE;
×
2220
                    }
2221
                    else if (nKey == MAKE_KEY(knFEATURE_GEOMETRY, WT_DATA) &&
50✔
2222
                             nGeomType >= knGEOM_TYPE_POINT &&
46✔
2223
                             nGeomType <= knGEOM_TYPE_POLYGON)
2224
                    {
2225
                        unsigned int nGeometrySize = 0;
46✔
2226
                        READ_VARUINT32(pabyData, pabyDataFeatureEnd,
46✔
2227
                                       nGeometrySize);
2228
                        if (nGeometrySize == 0 ||
46✔
2229
                            nGeometrySize >
46✔
2230
                                nFeatureLength -
46✔
2231
                                    (pabyData - pabyDataFeatureStart))
46✔
2232
                        {
2233
                            CPLDebug("MVT", "Protobuf error: line %d",
×
2234
                                     __LINE__);
2235
                            return FALSE;
×
2236
                        }
2237
                        const GByte *const pabyDataGeometryEnd =
2238
                            pabyDataStart +
2239
                            std::min(static_cast<int>(pabyData + nGeometrySize -
92✔
2240
                                                      pabyDataStart),
2241
                                     poOpenInfo->nHeaderBytes);
46✔
2242

2243
                        if (nGeomType == knGEOM_TYPE_POINT)
46✔
2244
                        {
2245
                            unsigned int nCmdCountCombined = 0;
12✔
2246
                            unsigned int nCount;
2247
                            READ_VARUINT32(pabyData, pabyDataGeometryEnd,
12✔
2248
                                           nCmdCountCombined);
2249
                            nCount = GetCmdCount(nCmdCountCombined);
12✔
2250
                            if (GetCmdId(nCmdCountCombined) != knCMD_MOVETO ||
24✔
2251
                                nCount == 0 || nCount > 10 * 1024 * 1024)
24✔
2252
                            {
2253
                                CPLDebug("MVT", "Protobuf error: line %d",
×
2254
                                         __LINE__);
2255
                                return FALSE;
×
2256
                            }
2257
                            for (unsigned i = 0; i < 2 * nCount; i++)
40✔
2258
                            {
2259
                                SKIP_VARINT(pabyData, pabyDataGeometryEnd);
28✔
2260
                            }
2261
                        }
2262
                        else if (nGeomType == knGEOM_TYPE_LINESTRING)
34✔
2263
                        {
2264
                            while (pabyData < pabyDataGeometryEnd)
56✔
2265
                            {
2266
                                unsigned int nCmdCountCombined = 0;
32✔
2267
                                unsigned int nLineToCount;
2268
                                // Should be a moveto
2269
                                READ_VARUINT32(pabyData, pabyDataGeometryEnd,
32✔
2270
                                               nCmdCountCombined);
2271
                                if (GetCmdId(nCmdCountCombined) !=
32✔
2272
                                        knCMD_MOVETO ||
64✔
2273
                                    GetCmdCount(nCmdCountCombined) != 1)
32✔
2274
                                {
2275
                                    CPLDebug("MVT", "Protobuf error: line %d",
×
2276
                                             __LINE__);
2277
                                    return FALSE;
×
2278
                                }
2279
                                SKIP_VARINT(pabyData, pabyDataGeometryEnd);
32✔
2280
                                SKIP_VARINT(pabyData, pabyDataGeometryEnd);
32✔
2281
                                READ_VARUINT32(pabyData, pabyDataGeometryEnd,
32✔
2282
                                               nCmdCountCombined);
2283
                                if (GetCmdId(nCmdCountCombined) != knCMD_LINETO)
32✔
2284
                                {
2285
                                    CPLDebug("MVT", "Protobuf error: line %d",
×
2286
                                             __LINE__);
2287
                                    return FALSE;
×
2288
                                }
2289
                                nLineToCount = GetCmdCount(nCmdCountCombined);
32✔
2290
                                for (unsigned i = 0; i < 2 * nLineToCount; i++)
96✔
2291
                                {
2292
                                    SKIP_VARINT(pabyData, pabyDataGeometryEnd);
64✔
2293
                                }
2294
                            }
2295
                        }
2296
                        else /* if( nGeomType == knGEOM_TYPE_POLYGON ) */
2297
                        {
2298
                            while (pabyData < pabyDataGeometryEnd)
382✔
2299
                            {
2300
                                unsigned int nCmdCountCombined = 0;
374✔
2301
                                unsigned int nLineToCount;
2302
                                // Should be a moveto
2303
                                READ_VARUINT32(pabyData, pabyDataGeometryEnd,
374✔
2304
                                               nCmdCountCombined);
2305
                                if (GetCmdId(nCmdCountCombined) !=
374✔
2306
                                        knCMD_MOVETO ||
748✔
2307
                                    GetCmdCount(nCmdCountCombined) != 1)
374✔
2308
                                {
2309
                                    CPLDebug("MVT", "Protobuf error: line %d",
×
2310
                                             __LINE__);
2311
                                    return FALSE;
×
2312
                                }
2313
                                SKIP_VARINT(pabyData, pabyDataGeometryEnd);
374✔
2314
                                SKIP_VARINT(pabyData, pabyDataGeometryEnd);
374✔
2315
                                READ_VARUINT32(pabyData, pabyDataGeometryEnd,
374✔
2316
                                               nCmdCountCombined);
2317
                                if (GetCmdId(nCmdCountCombined) != knCMD_LINETO)
374✔
2318
                                {
2319
                                    CPLDebug("MVT", "Protobuf error: line %d",
×
2320
                                             __LINE__);
2321
                                    return FALSE;
×
2322
                                }
2323
                                nLineToCount = GetCmdCount(nCmdCountCombined);
374✔
2324
                                for (unsigned i = 0; i < 2 * nLineToCount; i++)
7,020✔
2325
                                {
2326
                                    SKIP_VARINT(pabyData, pabyDataGeometryEnd);
6,648✔
2327
                                }
2328
                                // Should be a closepath
2329
                                READ_VARUINT32(pabyData, pabyDataGeometryEnd,
372✔
2330
                                               nCmdCountCombined);
2331
                                if (GetCmdId(nCmdCountCombined) !=
372✔
2332
                                        knCMD_CLOSEPATH ||
744✔
2333
                                    GetCmdCount(nCmdCountCombined) != 1)
372✔
2334
                                {
2335
                                    CPLDebug("MVT", "Protobuf error: line %d",
×
2336
                                             __LINE__);
2337
                                    return FALSE;
×
2338
                                }
2339
                            }
2340
                        }
2341

2342
                        pabyData = pabyDataGeometryEnd;
44✔
2343
                    }
2344
                    else
2345
                    {
2346
                        SKIP_UNKNOWN_FIELD(pabyData, pabyDataFeatureEnd, FALSE);
4✔
2347
                    }
2348
                }
2349

2350
                pabyData = pabyDataFeatureEnd;
48✔
2351
            }
2352
            else if (nFieldNumber == knLAYER_KEYS)
394✔
2353
            {
2354
                if (nWireType != WT_DATA)
152✔
2355
                {
2356
                    CPLDebug("MVT", "Invalid wire type for keys field");
×
2357
                    return FALSE;
×
2358
                }
2359
                char *pszKey = nullptr;
152✔
2360
                unsigned int nTextSize = 0;
152✔
2361
                READ_TEXT_WITH_SIZE(pabyData, pabyLayerEnd, pszKey, nTextSize);
152✔
2362
                if (!CPLIsUTF8(pszKey, nTextSize))
152✔
2363
                {
2364
                    CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
×
2365
                    CPLFree(pszKey);
×
2366
                    return FALSE;
×
2367
                }
2368
                CPLFree(pszKey);
152✔
2369
                bKeyFound = true;
152✔
2370
            }
2371
            else if (nFieldNumber == knLAYER_VALUES)
242✔
2372
            {
2373
                if (nWireType != WT_DATA)
156✔
2374
                {
2375
                    CPLDebug("MVT", "Invalid wire type for values field");
×
2376
                    return FALSE;
×
2377
                }
2378
                unsigned int nValueLength = 0;
156✔
2379
                READ_VARUINT32(pabyData, pabyLayerEnd, nValueLength);
156✔
2380
                if (nValueLength == 0 ||
156✔
2381
                    nValueLength > nLayerLength - (pabyData - pabyLayerStart))
156✔
2382
                {
2383
                    CPLDebug("MVT", "Protobuf error: line %d", __LINE__);
×
2384
                    return FALSE;
×
2385
                }
2386
                pabyData += nValueLength;
156✔
2387
            }
2388
            else if (GET_FIELDNUMBER(nKey) == knLAYER_EXTENT &&
86✔
2389
                     GET_WIRETYPE(nKey) != WT_VARINT)
40✔
2390
            {
2391
                CPLDebug("MVT", "Invalid wire type for extent field");
×
2392
                return FALSE;
×
2393
            }
2394
#if 0
2395
            // The check on extent is too fragile. Values of 65536 can be found
2396
            else if( nKey == MAKE_KEY(knLAYER_EXTENT, WT_VARINT) )
2397
            {
2398
                unsigned int nExtent = 0;
2399
                READ_VARUINT32(pabyData, pabyLayerEnd, nExtent);
2400
                if( nExtent < 128 || nExtent > 16834 )
2401
                {
2402
                    CPLDebug("MVT", "Invalid extent: %u", nExtent);
2403
                    return FALSE;
2404
                }
2405
            }
2406
#endif
2407
            else if (nFieldNumber == knLAYER_VERSION)
86✔
2408
            {
2409
                if (nWireType != WT_VARINT)
44✔
2410
                {
2411
                    CPLDebug("MVT", "Invalid wire type for version field");
×
2412
                    return FALSE;
×
2413
                }
2414
                unsigned int nVersion = 0;
44✔
2415
                READ_VARUINT32(pabyData, pabyLayerEnd, nVersion);
44✔
2416
                if (nVersion != 1 && nVersion != 2)
44✔
2417
                {
2418
                    CPLDebug("MVT", "Invalid version: %u", nVersion);
×
2419
                    return FALSE;
×
2420
                }
2421
                bVersionFound = true;
44✔
2422
            }
2423
            else
2424
            {
2425
                SKIP_UNKNOWN_FIELD(pabyData, pabyLayerEnd, FALSE);
42✔
2426
            }
2427
        }
2428
    }
2429
    catch (const GPBException &)
4✔
2430
    {
2431
    }
2432

2433
    return bLayerNameFound && (bKeyFound || bFeatureFound || bVersionFound);
48✔
2434
}
2435

2436
/************************************************************************/
2437
/*                     LongLatToSphericalMercator()                     */
2438
/************************************************************************/
2439

2440
static void LongLatToSphericalMercator(double *x, double *y)
28✔
2441
{
2442
    double X = kmSPHERICAL_RADIUS * (*x) / 180 * M_PI;
28✔
2443
    double Y =
2444
        kmSPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
28✔
2445
    *x = X;
28✔
2446
    *y = Y;
28✔
2447
}
28✔
2448

2449
/************************************************************************/
2450
/*                          LoadMetadata()                              */
2451
/************************************************************************/
2452

2453
static bool LoadMetadata(const CPLString &osMetadataFile,
906✔
2454
                         const CPLString &osMetadataContent,
2455
                         CPLJSONArray &oVectorLayers,
2456
                         CPLJSONArray &oTileStatLayers, CPLJSONObject &oBounds,
2457
                         OGRSpatialReference *poSRS, double &dfTopX,
2458
                         double &dfTopY, double &dfTileDim0,
2459
                         int &nTileMatrixWidth0, int &nTileMatrixHeight0,
2460
                         const CPLString &osMetadataMemFilename)
2461

2462
{
2463
    CPLJSONDocument oDoc;
1,812✔
2464

2465
    bool bLoadOK;
2466
    if (!osMetadataContent.empty())
906✔
2467
    {
2468
        bLoadOK = oDoc.LoadMemory(osMetadataContent);
3✔
2469
    }
2470
    else if (STARTS_WITH(osMetadataFile, "http://") ||
1,806✔
2471
             STARTS_WITH(osMetadataFile, "https://"))
903✔
2472
    {
2473
        bLoadOK = oDoc.LoadUrl(osMetadataFile, nullptr);
×
2474
    }
2475
    else
2476
    {
2477
        bLoadOK = oDoc.Load(osMetadataFile);
903✔
2478
    }
2479
    if (!bLoadOK)
906✔
2480
        return false;
2✔
2481

2482
    const CPLJSONObject oCrs(oDoc.GetRoot().GetObj("crs"));
2,712✔
2483
    const CPLJSONObject oTopX(
2484
        oDoc.GetRoot().GetObj("tile_origin_upper_left_x"));
2,712✔
2485
    const CPLJSONObject oTopY(
2486
        oDoc.GetRoot().GetObj("tile_origin_upper_left_y"));
2,712✔
2487
    const CPLJSONObject oTileDim0(
2488
        oDoc.GetRoot().GetObj("tile_dimension_zoom_0"));
2,712✔
2489
    nTileMatrixWidth0 = 1;
904✔
2490
    nTileMatrixHeight0 = 1;
904✔
2491
    if (oCrs.IsValid() && oTopX.IsValid() && oTopY.IsValid() &&
910✔
2492
        oTileDim0.IsValid())
6✔
2493
    {
2494
        poSRS->SetFromUserInput(oCrs.ToString().c_str());
6✔
2495
        dfTopX = oTopX.ToDouble();
6✔
2496
        dfTopY = oTopY.ToDouble();
6✔
2497
        dfTileDim0 = oTileDim0.ToDouble();
6✔
2498
        const CPLJSONObject oTMWidth0(
2499
            oDoc.GetRoot().GetObj("tile_matrix_width_zoom_0"));
18✔
2500
        if (oTMWidth0.GetType() == CPLJSONObject::Type::Integer)
6✔
2501
            nTileMatrixWidth0 = std::max(1, oTMWidth0.ToInteger());
6✔
2502

2503
        const CPLJSONObject oTMHeight0(
2504
            oDoc.GetRoot().GetObj("tile_matrix_height_zoom_0"));
18✔
2505
        if (oTMHeight0.GetType() == CPLJSONObject::Type::Integer)
6✔
2506
            nTileMatrixHeight0 = std::max(1, oTMHeight0.ToInteger());
6✔
2507

2508
        // Assumes WorldCRS84Quad with 2 tiles in width
2509
        // cf https://github.com/OSGeo/gdal/issues/11749
2510
        if (!oTMWidth0.IsValid() && dfTopX == -180 && dfTileDim0 == 180)
6✔
2511
            nTileMatrixWidth0 = 2;
×
2512
    }
2513

2514
    oVectorLayers.Deinit();
904✔
2515
    oTileStatLayers.Deinit();
904✔
2516

2517
    CPLJSONObject oJson = oDoc.GetRoot().GetObj("json");
2,712✔
2518
    if (!(oJson.IsValid() && oJson.GetType() == CPLJSONObject::Type::String))
904✔
2519
    {
2520
        oVectorLayers = oDoc.GetRoot().GetArray("vector_layers");
527✔
2521

2522
        oTileStatLayers = oDoc.GetRoot().GetArray("tilestats/layers");
527✔
2523
    }
2524
    else
2525
    {
2526
        CPLJSONDocument oJsonDoc;
377✔
2527
        if (!oJsonDoc.LoadMemory(oJson.ToString()))
377✔
2528
        {
2529
            return false;
1✔
2530
        }
2531

2532
        oVectorLayers = oJsonDoc.GetRoot().GetArray("vector_layers");
376✔
2533

2534
        oTileStatLayers = oJsonDoc.GetRoot().GetArray("tilestats/layers");
376✔
2535
    }
2536

2537
    oBounds = oDoc.GetRoot().GetObj("bounds");
903✔
2538

2539
    if (!osMetadataMemFilename.empty())
903✔
2540
    {
2541
        oDoc.Save(osMetadataMemFilename);
17✔
2542
    }
2543

2544
    return oVectorLayers.IsValid();
903✔
2545
}
2546

2547
/************************************************************************/
2548
/*                       ConvertFromWGS84()                             */
2549
/************************************************************************/
2550

2551
static void ConvertFromWGS84(OGRSpatialReference *poTargetSRS, double &dfX0,
17✔
2552
                             double &dfY0, double &dfX1, double &dfY1)
2553
{
2554
    OGRSpatialReference oSRS_EPSG3857;
34✔
2555
    oSRS_EPSG3857.SetFromUserInput(SRS_EPSG_3857);
17✔
2556

2557
    if (poTargetSRS->IsSame(&oSRS_EPSG3857))
17✔
2558
    {
2559
        LongLatToSphericalMercator(&dfX0, &dfY0);
14✔
2560
        LongLatToSphericalMercator(&dfX1, &dfY1);
14✔
2561
    }
2562
    else
2563
    {
2564
        OGRSpatialReference oSRS_EPSG4326;
6✔
2565
        oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
3✔
2566
        oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3✔
2567
        OGRCoordinateTransformation *poCT =
2568
            OGRCreateCoordinateTransformation(&oSRS_EPSG4326, poTargetSRS);
3✔
2569
        if (poCT)
3✔
2570
        {
2571
            poCT->Transform(1, &dfX0, &dfY0);
3✔
2572
            poCT->Transform(1, &dfX1, &dfY1);
3✔
2573
            delete poCT;
3✔
2574
        }
2575
    }
2576
}
17✔
2577

2578
/************************************************************************/
2579
/*                         OpenDirectory()                              */
2580
/************************************************************************/
2581

2582
GDALDataset *OGRMVTDataset::OpenDirectory(GDALOpenInfo *poOpenInfo)
23✔
2583

2584
{
2585
    const CPLString osZ(CPLGetFilename(poOpenInfo->pszFilename));
46✔
2586
    if (CPLGetValueType(osZ) != CPL_VALUE_INTEGER)
23✔
2587
        return nullptr;
1✔
2588

2589
    const int nZ = atoi(osZ);
22✔
2590
    if (nZ < 0 || nZ > 30)
22✔
2591
        return nullptr;
1✔
2592

2593
    CPLString osMetadataFile(
2594
        CPLFormFilenameSafe(CPLGetPathSafe(poOpenInfo->pszFilename).c_str(),
42✔
2595
                            "metadata.json", nullptr));
42✔
2596
    const char *pszMetadataFile =
2597
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
21✔
2598
    if (pszMetadataFile)
21✔
2599
    {
2600
        osMetadataFile = pszMetadataFile;
2✔
2601
    }
2602

2603
    CPLString osTileExtension(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
21✔
2604
                                                   "TILE_EXTENSION", "pbf"));
42✔
2605
    bool bJsonField =
2606
        CPLFetchBool(poOpenInfo->papszOpenOptions, "JSON_FIELD", false);
21✔
2607
    VSIStatBufL sStat;
2608

2609
    bool bMetadataFileExists = false;
21✔
2610
    CPLString osMetadataContent;
42✔
2611
    if (STARTS_WITH(osMetadataFile, "http://") ||
37✔
2612
        STARTS_WITH(osMetadataFile, "https://"))
16✔
2613
    {
2614
        for (int i = 0; i < 2; i++)
8✔
2615
        {
2616
            if (pszMetadataFile == nullptr)
8✔
2617
                CPLPushErrorHandler(CPLQuietErrorHandler);
8✔
2618
            CPLHTTPResult *psResult = CPLHTTPFetch(osMetadataFile, nullptr);
8✔
2619
            if (pszMetadataFile == nullptr)
8✔
2620
                CPLPopErrorHandler();
8✔
2621
            if (psResult == nullptr)
8✔
2622
            {
2623
                osMetadataFile.clear();
×
2624
            }
2625
            else if (psResult->pszErrBuf != nullptr ||
8✔
2626
                     psResult->pabyData == nullptr)
3✔
2627
            {
2628
                CPLHTTPDestroyResult(psResult);
5✔
2629
                osMetadataFile.clear();
5✔
2630

2631
                if (i == 0 && pszMetadataFile == nullptr)
5✔
2632
                {
2633
                    // tileserver-gl metadata file:
2634
                    // If opening /path/to/foo/0, try looking for
2635
                    // /path/to/foo.json
2636
                    CPLString osParentDir(
2637
                        CPLGetPathSafe(poOpenInfo->pszFilename));
6✔
2638
                    osMetadataFile = CPLFormFilenameSafe(
6✔
2639
                        CPLGetPathSafe(osParentDir).c_str(),
6✔
2640
                        CPLGetFilename(osParentDir), "json");
3✔
2641
                    continue;
3✔
2642
                }
2✔
2643
            }
2644
            else
2645
            {
2646
                bMetadataFileExists = true;
3✔
2647
                osMetadataContent =
2648
                    reinterpret_cast<const char *>(psResult->pabyData);
3✔
2649
                CPLHTTPDestroyResult(psResult);
3✔
2650
            }
2651
            break;
5✔
2652
        }
2653
    }
2654
    else if (!osMetadataFile.empty())
16✔
2655
    {
2656
        bMetadataFileExists = (VSIStatL(osMetadataFile, &sStat) == 0);
14✔
2657
        if (!bMetadataFileExists && pszMetadataFile == nullptr)
14✔
2658
        {
2659
            // tileserver-gl metadata file:
2660
            // If opening /path/to/foo/0, try looking for /path/to/foo.json
2661
            CPLString osParentDir(CPLGetPathSafe(poOpenInfo->pszFilename));
1✔
2662
            osMetadataFile =
2663
                CPLFormFilenameSafe(CPLGetPathSafe(osParentDir).c_str(),
2✔
2664
                                    CPLGetFilename(osParentDir), "json");
1✔
2665
            bMetadataFileExists = (VSIStatL(osMetadataFile, &sStat) == 0);
1✔
2666
        }
2667
    }
2668

2669
    if (!bMetadataFileExists)
21✔
2670
    {
2671
        // If we don't have a metadata file, iterate through all tiles to
2672
        // establish the layer definitions.
2673
        OGRMVTDataset *poDS = nullptr;
4✔
2674
        bool bTryToListDir =
4✔
2675
            !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl/") &&
8✔
2676
            !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl_streaming/") &&
4✔
2677
            !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl?") &&
4✔
2678
            !STARTS_WITH(poOpenInfo->pszFilename, "http://") &&
10✔
2679
            !STARTS_WITH(poOpenInfo->pszFilename, "https://");
2✔
2680
        CPLStringList aosDirContent;
4✔
2681
        if (bTryToListDir)
4✔
2682
        {
2683
            aosDirContent = VSIReadDir(poOpenInfo->pszFilename);
2✔
2684
            aosDirContent = StripDummyEntries(aosDirContent);
2✔
2685
        }
2686
        const int nMaxTiles = atoi(CSLFetchNameValueDef(
8✔
2687
            poOpenInfo->papszOpenOptions,
4✔
2688
            "TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN", "1000"));
2689
        int nCountTiles = 0;
4✔
2690
        int nFailedAttempts = 0;
4✔
2691
        for (int i = 0; i < (bTryToListDir ? aosDirContent.Count() : (1 << nZ));
9✔
2692
             i++)
2693
        {
2694
            if (bTryToListDir)
5✔
2695
            {
2696
                if (CPLGetValueType(aosDirContent[i]) != CPL_VALUE_INTEGER)
3✔
2697
                {
2698
                    continue;
×
2699
                }
2700
            }
2701
            CPLString osSubDir = CPLFormFilenameSafe(
5✔
2702
                poOpenInfo->pszFilename,
5✔
2703
                bTryToListDir ? aosDirContent[i] : CPLSPrintf("%d", i),
5✔
2704
                nullptr);
5✔
2705
            CPLStringList aosSubDirContent;
5✔
2706
            if (bTryToListDir)
5✔
2707
            {
2708
                aosSubDirContent = VSIReadDir(osSubDir);
3✔
2709
                aosSubDirContent = StripDummyEntries(aosSubDirContent);
3✔
2710
            }
2711
            for (int j = 0;
12✔
2712
                 j < (bTryToListDir ? aosSubDirContent.Count() : (1 << nZ));
12✔
2713
                 j++)
2714
            {
2715
                if (bTryToListDir)
7✔
2716
                {
2717
                    if (CPLGetValueType(
5✔
2718
                            CPLGetBasenameSafe(aosSubDirContent[j]).c_str()) !=
10✔
2719
                        CPL_VALUE_INTEGER)
2720
                    {
2721
                        continue;
×
2722
                    }
2723
                }
2724
                const std::string osFilename(CPLFormFilenameSafe(
2725
                    osSubDir,
2726
                    bTryToListDir
2727
                        ? aosSubDirContent[j]
5✔
2728
                        : CPLSPrintf("%d.%s", j, osTileExtension.c_str()),
2✔
2729
                    nullptr));
14✔
2730
                GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(),
7✔
2731
                                       GA_ReadOnly);
7✔
2732
                oOpenInfo.papszOpenOptions =
7✔
2733
                    CSLSetNameValue(nullptr, "METADATA_FILE", "");
7✔
2734
                oOpenInfo.papszOpenOptions =
7✔
2735
                    CSLSetNameValue(oOpenInfo.papszOpenOptions,
7✔
2736
                                    "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
2737
                auto poTileDS = OGRMVTDataset::Open(
7✔
2738
                    &oOpenInfo, /* bRecurseAllowed = */ false);
2739
                if (poTileDS)
7✔
2740
                {
2741
                    if (poDS == nullptr)
6✔
2742
                    {
2743
                        poDS = new OGRMVTDataset(nullptr);
3✔
2744
                        poDS->m_osTileExtension = osTileExtension;
3✔
2745
                        poDS->SetDescription(poOpenInfo->pszFilename);
3✔
2746
                        poDS->m_bClip =
3✔
2747
                            CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP",
3✔
2748
                                         poDS->m_bClip);
3✔
2749
                    }
2750

2751
                    for (int k = 0; k < poTileDS->GetLayerCount(); k++)
14✔
2752
                    {
2753
                        OGRLayer *poTileLayer = poTileDS->GetLayer(k);
8✔
2754
                        OGRFeatureDefn *poTileLDefn =
2755
                            poTileLayer->GetLayerDefn();
8✔
2756
                        OGRwkbGeometryType eTileGeomType =
2757
                            poTileLDefn->GetGeomType();
8✔
2758
                        OGRwkbGeometryType eTileGeomTypeColl =
2759
                            OGR_GT_GetCollection(eTileGeomType);
8✔
2760
                        if (eTileGeomTypeColl != wkbUnknown &&
8✔
2761
                            eTileGeomTypeColl != eTileGeomType)
2762
                        {
2763
                            eTileGeomType = eTileGeomTypeColl;
4✔
2764
                        }
2765

2766
                        OGRLayer *poLayer =
2767
                            poDS->GetLayerByName(poTileLayer->GetName());
8✔
2768
                        OGRFeatureDefn *poLDefn;
2769
                        if (poLayer == nullptr)
8✔
2770
                        {
2771
                            CPLJSONObject oFields;
8✔
2772
                            oFields.Deinit();
4✔
2773
                            poDS->m_apoLayers.push_back(
4✔
2774
                                std::unique_ptr<OGRLayer>(
8✔
2775
                                    new OGRMVTDirectoryLayer(
2776
                                        poDS, poTileLayer->GetName(),
4✔
2777
                                        poOpenInfo->pszFilename, oFields,
4✔
2778
                                        CPLJSONArray(), bJsonField, wkbUnknown,
8✔
2779
                                        nullptr)));
4✔
2780
                            poLayer = poDS->m_apoLayers.back().get();
4✔
2781
                            poLDefn = poLayer->GetLayerDefn();
4✔
2782
                            poLDefn->SetGeomType(eTileGeomType);
4✔
2783
                        }
2784
                        else
2785
                        {
2786
                            poLDefn = poLayer->GetLayerDefn();
4✔
2787
                            if (poLayer->GetGeomType() != eTileGeomType)
4✔
2788
                            {
2789
                                poLDefn->SetGeomType(wkbUnknown);
×
2790
                            }
2791
                        }
2792

2793
                        if (!bJsonField)
8✔
2794
                        {
2795
                            for (int l = 1; l < poTileLDefn->GetFieldCount();
11✔
2796
                                 l++)
2797
                            {
2798
                                OGRFieldDefn *poTileFDefn =
2799
                                    poTileLDefn->GetFieldDefn(l);
4✔
2800
                                int nFieldIdx = poLDefn->GetFieldIndex(
4✔
2801
                                    poTileFDefn->GetNameRef());
4✔
2802
                                if (nFieldIdx < 0)
4✔
2803
                                {
2804
                                    poLDefn->AddFieldDefn(poTileFDefn);
1✔
2805
                                }
2806
                                else
2807
                                {
2808
                                    MergeFieldDefn(
6✔
2809
                                        poLDefn->GetFieldDefn(nFieldIdx),
3✔
2810
                                        poTileFDefn->GetType(),
2811
                                        poTileFDefn->GetSubType());
2812
                                }
2813
                            }
2814
                        }
2815
                    }
2816
                    nCountTiles++;
6✔
2817
                }
2818
                else if (!bTryToListDir)
1✔
2819
                {
2820
                    nFailedAttempts++;
1✔
2821
                }
2822
                delete poTileDS;
7✔
2823
                CSLDestroy(oOpenInfo.papszOpenOptions);
7✔
2824

2825
                if (nFailedAttempts == 10)
7✔
2826
                    break;
×
2827
                if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
7✔
2828
                    break;
×
2829
            }
2830

2831
            if (nFailedAttempts == 10)
5✔
2832
                break;
×
2833
            if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
5✔
2834
                break;
×
2835
        }
2836
        return poDS;
4✔
2837
    }
2838

2839
    CPLJSONArray oVectorLayers;
34✔
2840
    CPLJSONArray oTileStatLayers;
34✔
2841
    CPLJSONObject oBounds;
34✔
2842

2843
    OGRMVTDataset *poDS = new OGRMVTDataset(nullptr);
17✔
2844

2845
    CPLString osMetadataMemFilename =
2846
        VSIMemGenerateHiddenFilename("mvt_metadata.json");
34✔
2847
    if (!LoadMetadata(osMetadataFile, osMetadataContent, oVectorLayers,
17✔
2848
                      oTileStatLayers, oBounds, poDS->m_poSRS,
2849
                      poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
17✔
2850
                      poDS->m_dfTileDim0, poDS->m_nTileMatrixWidth0,
17✔
2851
                      poDS->m_nTileMatrixHeight0, osMetadataMemFilename))
17✔
2852
    {
2853
        delete poDS;
×
2854
        return nullptr;
×
2855
    }
2856

2857
    OGREnvelope sExtent;
17✔
2858
    bool bExtentValid = false;
17✔
2859
    if (oBounds.IsValid() && oBounds.GetType() == CPLJSONObject::Type::String)
17✔
2860
    {
2861
        CPLStringList aosTokens(
2862
            CSLTokenizeString2(oBounds.ToString().c_str(), ",", 0));
48✔
2863
        if (aosTokens.Count() == 4)
16✔
2864
        {
2865
            double dfX0 = CPLAtof(aosTokens[0]);
16✔
2866
            double dfY0 = CPLAtof(aosTokens[1]);
16✔
2867
            double dfX1 = CPLAtof(aosTokens[2]);
16✔
2868
            double dfY1 = CPLAtof(aosTokens[3]);
16✔
2869
            ConvertFromWGS84(poDS->m_poSRS, dfX0, dfY0, dfX1, dfY1);
16✔
2870
            bExtentValid = true;
16✔
2871
            sExtent.MinX = dfX0;
16✔
2872
            sExtent.MinY = dfY0;
16✔
2873
            sExtent.MaxX = dfX1;
16✔
2874
            sExtent.MaxY = dfY1;
16✔
2875
        }
2876
    }
2877
    else if (oBounds.IsValid() &&
2✔
2878
             oBounds.GetType() == CPLJSONObject::Type::Array)
1✔
2879
    {
2880
        // Cf https://free.tilehosting.com/data/v3.json?key=THE_KEY
2881
        CPLJSONArray oBoundArray = oBounds.ToArray();
2✔
2882
        if (oBoundArray.Size() == 4)
1✔
2883
        {
2884
            bExtentValid = true;
1✔
2885
            sExtent.MinX = oBoundArray[0].ToDouble();
1✔
2886
            sExtent.MinY = oBoundArray[1].ToDouble();
1✔
2887
            sExtent.MaxX = oBoundArray[2].ToDouble();
1✔
2888
            sExtent.MaxY = oBoundArray[3].ToDouble();
1✔
2889
            ConvertFromWGS84(poDS->m_poSRS, sExtent.MinX, sExtent.MinY,
1✔
2890
                             sExtent.MaxX, sExtent.MaxY);
2891
        }
2892
    }
2893

2894
    poDS->SetDescription(poOpenInfo->pszFilename);
17✔
2895
    poDS->m_bClip =
17✔
2896
        CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
17✔
2897
    poDS->m_osTileExtension = std::move(osTileExtension);
17✔
2898
    poDS->m_osMetadataMemFilename = std::move(osMetadataMemFilename);
17✔
2899
    for (int i = 0; i < oVectorLayers.Size(); i++)
36✔
2900
    {
2901
        CPLJSONObject oId = oVectorLayers[i].GetObj("id");
57✔
2902
        if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
19✔
2903
        {
2904
            OGRwkbGeometryType eGeomType = wkbUnknown;
19✔
2905
            if (oTileStatLayers.IsValid())
19✔
2906
            {
2907
                eGeomType = OGRMVTFindGeomTypeFromTileStat(
18✔
2908
                    oTileStatLayers, oId.ToString().c_str());
36✔
2909
            }
2910

2911
            CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
57✔
2912
            CPLJSONArray oAttributesFromTileStats =
2913
                OGRMVTFindAttributesFromTileStat(oTileStatLayers,
2914
                                                 oId.ToString().c_str());
38✔
2915

2916
            poDS->m_apoLayers.push_back(
19✔
2917
                std::unique_ptr<OGRLayer>(new OGRMVTDirectoryLayer(
38✔
2918
                    poDS, oId.ToString().c_str(), poOpenInfo->pszFilename,
38✔
2919
                    oFields, oAttributesFromTileStats, bJsonField, eGeomType,
2920
                    (bExtentValid) ? &sExtent : nullptr)));
19✔
2921
        }
2922
    }
2923

2924
    return poDS;
17✔
2925
}
2926

2927
/************************************************************************/
2928
/*                                Open()                                */
2929
/************************************************************************/
2930

2931
GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo)
884✔
2932
{
2933
    return Open(poOpenInfo, true);
884✔
2934
}
2935

2936
GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo, bool bRecurseAllowed)
972✔
2937

2938
{
2939
    if (!OGRMVTDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
972✔
2940
        return nullptr;
×
2941

2942
    VSILFILE *fp = poOpenInfo->fpL;
972✔
2943
    CPLString osFilename(poOpenInfo->pszFilename);
1,944✔
2944
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
972✔
2945
    {
2946
        osFilename = poOpenInfo->pszFilename + strlen("MVT:");
934✔
2947
        if (STARTS_WITH(osFilename, "/vsigzip/http://") ||
1,868✔
2948
            STARTS_WITH(osFilename, "/vsigzip/https://"))
934✔
2949
        {
2950
            osFilename = osFilename.substr(strlen("/vsigzip/"));
×
2951
        }
2952

2953
        // If the filename has no extension and is a directory, consider
2954
        // we open a directory
2955
        VSIStatBufL sStat;
2956
        if (bRecurseAllowed && !STARTS_WITH(osFilename, "/vsigzip/") &&
846✔
2957
            strchr((CPLGetFilename(osFilename)), '.') == nullptr &&
845✔
2958
            VSIStatL(osFilename, &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
1,780✔
2959
        {
2960
            GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
3✔
2961
            oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
3✔
2962
            GDALDataset *poDS = OpenDirectory(&oOpenInfo);
3✔
2963
            if (poDS)
3✔
2964
                poDS->SetDescription(poOpenInfo->pszFilename);
1✔
2965
            return poDS;
3✔
2966
        }
2967

2968
        // For a network resource, if the filename is an integer, consider it
2969
        // is a directory and open as such
2970
        if (bRecurseAllowed &&
1,774✔
2971
            (STARTS_WITH(osFilename, "/vsicurl") ||
843✔
2972
             STARTS_WITH(osFilename, "http://") ||
843✔
2973
             STARTS_WITH(osFilename, "https://")) &&
1,774✔
2974
            CPLGetValueType(CPLGetFilename(osFilename)) == CPL_VALUE_INTEGER)
6✔
2975
        {
2976
            GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
5✔
2977
            oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
5✔
2978
            GDALDataset *poDS = OpenDirectory(&oOpenInfo);
5✔
2979
            if (poDS)
5✔
2980
                poDS->SetDescription(poOpenInfo->pszFilename);
4✔
2981
            return poDS;
5✔
2982
        }
2983

2984
        if (!STARTS_WITH(osFilename, "http://") &&
1,842✔
2985
            !STARTS_WITH(osFilename, "https://"))
916✔
2986
        {
2987
            CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES",
2988
                                          "NO", false);
1,832✔
2989
            CPLConfigOptionSetter oSetter2("CPL_VSIL_GZIP_SAVE_INFO", "NO",
2990
                                           false);
1,832✔
2991
            fp = VSIFOpenL(osFilename, "rb");
916✔
2992
            // Is it a gzipped file ?
2993
            if (fp && !STARTS_WITH(osFilename, "/vsigzip/"))
916✔
2994
            {
2995
                GByte abyHeaderBytes[2] = {0, 0};
914✔
2996
                VSIFReadL(abyHeaderBytes, 2, 1, fp);
914✔
2997
                if (abyHeaderBytes[0] == 0x1F && abyHeaderBytes[1] == 0x8B)
914✔
2998
                {
2999
                    VSIFCloseL(fp);
385✔
3000
                    fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
385✔
3001
                }
3002
            }
3003
        }
3004
    }
3005
    else if (bRecurseAllowed &&
76✔
3006
             (poOpenInfo->bIsDirectory ||
38✔
3007
              (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl") &&
23✔
3008
               CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
×
3009
                   CPL_VALUE_INTEGER)))
3010
    {
3011
        return OpenDirectory(poOpenInfo);
15✔
3012
    }
3013
    // Is it a gzipped file ?
3014
    else if (poOpenInfo->nHeaderBytes >= 2 &&
23✔
3015
             poOpenInfo->pabyHeader[0] == 0x1F &&
23✔
3016
             poOpenInfo->pabyHeader[1] == 0x8B)
18✔
3017
    {
3018
        CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
3019
                                      false);
18✔
3020
        fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
18✔
3021
    }
3022
    else
3023
    {
3024
        poOpenInfo->fpL = nullptr;
5✔
3025
    }
3026
    if (fp == nullptr && !STARTS_WITH(osFilename, "http://") &&
950✔
3027
        !STARTS_WITH(osFilename, "https://"))
1✔
3028
    {
3029
        return nullptr;
1✔
3030
    }
3031

3032
    CPLString osY = CPLGetBasenameSafe(osFilename);
1,896✔
3033
    CPLString osX = CPLGetBasenameSafe(CPLGetPathSafe(osFilename).c_str());
2,844✔
3034
    CPLString osZ = CPLGetBasenameSafe(
1,896✔
3035
        CPLGetPathSafe(CPLGetPathSafe(osFilename).c_str()).c_str());
3,792✔
3036
    size_t nPos = osY.find('.');
948✔
3037
    if (nPos != std::string::npos)
948✔
3038
        osY.resize(nPos);
×
3039

3040
    CPLString osMetadataFile;
1,896✔
3041
    if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE"))
948✔
3042
    {
3043
        osMetadataFile =
3044
            CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
927✔
3045
    }
3046
    else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
21✔
3047
             CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
33✔
3048
             CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
12✔
3049
    {
3050
        osMetadataFile = CPLFormFilenameSafe(
12✔
3051
            CPLGetPathSafe(
24✔
3052
                CPLGetPathSafe(CPLGetPathSafe(osFilename).c_str()).c_str())
24✔
3053
                .c_str(),
3054
            "metadata.json", nullptr);
12✔
3055
        if (osMetadataFile.find("/vsigzip/") == 0)
12✔
3056
        {
3057
            osMetadataFile = osMetadataFile.substr(strlen("/vsigzip/"));
2✔
3058
        }
3059
        VSIStatBufL sStat;
3060
        if (osMetadataFile.empty() || VSIStatL(osMetadataFile, &sStat) != 0)
12✔
3061
        {
3062
            osMetadataFile.clear();
1✔
3063
        }
3064
    }
3065

3066
    if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X") &&
948✔
3067
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y") &&
1,758✔
3068
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z"))
810✔
3069
    {
3070
        osX = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X");
810✔
3071
        osY = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y");
810✔
3072
        osZ = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z");
810✔
3073
    }
3074

3075
    GByte *pabyDataMod;
3076
    size_t nFileSize;
3077

3078
    if (fp == nullptr)
948✔
3079
    {
3080
        bool bSilenceErrors =
3081
            CPLFetchBool(poOpenInfo->papszOpenOptions,
10✔
3082
                         "DO_NOT_ERROR_ON_MISSING_TILE", false);
3083
        if (bSilenceErrors)
10✔
3084
            CPLPushErrorHandler(CPLQuietErrorHandler);
9✔
3085
        CPLHTTPResult *psResult = CPLHTTPFetch(osFilename, nullptr);
10✔
3086
        if (bSilenceErrors)
10✔
3087
            CPLPopErrorHandler();
9✔
3088
        if (psResult == nullptr)
10✔
3089
            return nullptr;
×
3090
        if (psResult->pszErrBuf != nullptr)
10✔
3091
        {
3092
            CPLHTTPDestroyResult(psResult);
5✔
3093
            return nullptr;
5✔
3094
        }
3095
        pabyDataMod = psResult->pabyData;
5✔
3096
        if (pabyDataMod == nullptr)
5✔
3097
        {
3098
            CPLHTTPDestroyResult(psResult);
×
3099
            return nullptr;
×
3100
        }
3101
        nFileSize = psResult->nDataLen;
5✔
3102
        psResult->pabyData = nullptr;
5✔
3103
        CPLHTTPDestroyResult(psResult);
5✔
3104

3105
        // zlib decompress if needed
3106
        if (nFileSize > 2 && pabyDataMod[0] == 0x1F && pabyDataMod[1] == 0x8B)
5✔
3107
        {
3108
            size_t nOutBytes = 0;
5✔
3109
            void *pUncompressed =
3110
                CPLZLibInflate(pabyDataMod, nFileSize, nullptr, 0, &nOutBytes);
5✔
3111
            CPLFree(pabyDataMod);
5✔
3112
            if (pUncompressed == nullptr)
5✔
3113
            {
3114
                return nullptr;
×
3115
            }
3116
            pabyDataMod = static_cast<GByte *>(pUncompressed);
5✔
3117
            nFileSize = nOutBytes;
5✔
3118
        }
3119
    }
3120
    else
3121
    {
3122
        // Check file size and ingest into memory
3123
        VSIFSeekL(fp, 0, SEEK_END);
938✔
3124
        vsi_l_offset nFileSizeL = VSIFTellL(fp);
938✔
3125
        if (nFileSizeL > 10 * 1024 * 1024)
938✔
3126
        {
3127
            VSIFCloseL(fp);
×
3128
            return nullptr;
×
3129
        }
3130
        nFileSize = static_cast<size_t>(nFileSizeL);
938✔
3131
        pabyDataMod = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nFileSize + 1));
938✔
3132
        if (pabyDataMod == nullptr)
938✔
3133
        {
3134
            VSIFCloseL(fp);
×
3135
            return nullptr;
×
3136
        }
3137
        VSIFSeekL(fp, 0, SEEK_SET);
938✔
3138
        VSIFReadL(pabyDataMod, 1, nFileSize, fp);
938✔
3139
        pabyDataMod[nFileSize] = 0;
938✔
3140
        VSIFCloseL(fp);
938✔
3141
    }
3142

3143
    const GByte *pabyData = pabyDataMod;
943✔
3144

3145
    // First scan to browse through layers
3146
    const GByte *pabyDataLimit = pabyData + nFileSize;
943✔
3147
    int nKey = 0;
943✔
3148
    OGRMVTDataset *poDS = new OGRMVTDataset(pabyDataMod);
943✔
3149
    poDS->SetDescription(poOpenInfo->pszFilename);
943✔
3150
    poDS->m_bClip =
943✔
3151
        CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
943✔
3152

3153
    if (!(CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
1,880✔
3154
          CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
937✔
3155
          CPLGetValueType(osZ) == CPL_VALUE_INTEGER))
912✔
3156
    {
3157
        // See
3158
        // https://github.com/mapbox/mvt-fixtures/tree/master/real-world/compressed
3159
        int nX = 0;
31✔
3160
        int nY = 0;
31✔
3161
        int nZ = 0;
31✔
3162
        CPLString osBasename(
3163
            CPLGetBasenameSafe(CPLGetBasenameSafe(osFilename).c_str()));
93✔
3164
        if (sscanf(osBasename, "%d-%d-%d", &nZ, &nX, &nY) == 3 ||
61✔
3165
            sscanf(osBasename, "%d_%d_%d", &nZ, &nX, &nY) == 3)
30✔
3166
        {
3167
            osX = CPLSPrintf("%d", nX);
1✔
3168
            osY = CPLSPrintf("%d", nY);
1✔
3169
            osZ = CPLSPrintf("%d", nZ);
1✔
3170
        }
3171
    }
3172

3173
    CPLJSONArray oVectorLayers;
1,886✔
3174
    oVectorLayers.Deinit();
943✔
3175

3176
    CPLJSONArray oTileStatLayers;
1,886✔
3177
    oTileStatLayers.Deinit();
943✔
3178

3179
    if (!osMetadataFile.empty())
943✔
3180
    {
3181
        CPLJSONObject oBounds;
889✔
3182
        LoadMetadata(osMetadataFile, CPLString(), oVectorLayers,
889✔
3183
                     oTileStatLayers, oBounds, poDS->m_poSRS,
3184
                     poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
889✔
3185
                     poDS->m_dfTileDim0, poDS->m_nTileMatrixWidth0,
889✔
3186
                     poDS->m_nTileMatrixHeight0, CPLString());
1,778✔
3187
    }
3188

3189
    const char *pszGeorefTopX =
3190
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPX");
943✔
3191
    const char *pszGeorefTopY =
3192
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPY");
943✔
3193
    const char *pszGeorefTileDimX =
3194
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMX");
943✔
3195
    const char *pszGeorefTileDimY =
3196
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMY");
943✔
3197
    if (pszGeorefTopX && pszGeorefTopY && pszGeorefTileDimX &&
943✔
3198
        pszGeorefTileDimY)
3199
    {
3200
        poDS->m_bGeoreferenced = true;
3✔
3201
        poDS->m_dfTileDimX = CPLAtof(pszGeorefTileDimX);
3✔
3202
        poDS->m_dfTileDimY = CPLAtof(pszGeorefTileDimY);
3✔
3203
        poDS->m_dfTopX = CPLAtof(pszGeorefTopX);
3✔
3204
        poDS->m_dfTopY = CPLAtof(pszGeorefTopY);
3✔
3205
        poDS->m_poSRS->Release();
3✔
3206
        poDS->m_poSRS = nullptr;
3✔
3207
    }
3208
    else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
940✔
3209
             CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
1,853✔
3210
             CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
913✔
3211
    {
3212
        int nX = atoi(osX);
913✔
3213
        int nY = atoi(osY);
913✔
3214
        int nZ = atoi(osZ);
913✔
3215
        if (nZ >= 0 && nZ < 30 && nX >= 0 &&
913✔
3216
            nX < (static_cast<int64_t>(1) << nZ) * poDS->m_nTileMatrixWidth0 &&
913✔
3217
            nY >= 0 &&
911✔
3218
            nY < (static_cast<int64_t>(1) << nZ) * poDS->m_nTileMatrixHeight0)
911✔
3219
        {
3220
            poDS->m_bGeoreferenced = true;
911✔
3221
            poDS->m_dfTileDimX = poDS->m_dfTileDim0 / (1 << nZ);
911✔
3222
            poDS->m_dfTileDimY = poDS->m_dfTileDimX;
911✔
3223
            poDS->m_dfTopX = poDS->m_dfTopXOrigin + nX * poDS->m_dfTileDimX;
911✔
3224
            poDS->m_dfTopY = poDS->m_dfTopYOrigin - nY * poDS->m_dfTileDimY;
911✔
3225
        }
3226
    }
3227

3228
    try
3229
    {
3230
        while (pabyData < pabyDataLimit)
1,966✔
3231
        {
3232
            READ_FIELD_KEY(nKey);
1,023✔
3233
            if (nKey == MAKE_KEY(knLAYER, WT_DATA))
1,023✔
3234
            {
3235
                unsigned int nLayerSize = 0;
1,023✔
3236
                READ_SIZE(pabyData, pabyDataLimit, nLayerSize);
1,023✔
3237
                const GByte *pabyDataLayer = pabyData;
1,023✔
3238
                const GByte *pabyDataLimitLayer = pabyData + nLayerSize;
1,023✔
3239
                while (pabyData < pabyDataLimitLayer)
1,262✔
3240
                {
3241
                    READ_VARINT32(pabyData, pabyDataLimitLayer, nKey);
1,262✔
3242
                    if (nKey == MAKE_KEY(knLAYER_NAME, WT_DATA))
1,262✔
3243
                    {
3244
                        char *pszLayerName = nullptr;
1,023✔
3245
                        READ_TEXT(pabyData, pabyDataLimitLayer, pszLayerName);
1,023✔
3246

3247
                        CPLJSONObject oFields;
2,046✔
3248
                        oFields.Deinit();
1,023✔
3249
                        if (oVectorLayers.IsValid())
1,023✔
3250
                        {
3251
                            for (int i = 0; i < oVectorLayers.Size(); i++)
1,068✔
3252
                            {
3253
                                CPLJSONObject oId =
3254
                                    oVectorLayers[i].GetObj("id");
2,136✔
3255
                                if (oId.IsValid() &&
2,136✔
3256
                                    oId.GetType() ==
1,068✔
3257
                                        CPLJSONObject::Type::String)
3258
                                {
3259
                                    if (oId.ToString() == pszLayerName)
1,068✔
3260
                                    {
3261
                                        oFields =
3262
                                            oVectorLayers[i].GetObj("fields");
944✔
3263
                                        break;
944✔
3264
                                    }
3265
                                }
3266
                            }
3267
                        }
3268

3269
                        OGRwkbGeometryType eGeomType = wkbUnknown;
1,023✔
3270
                        if (oTileStatLayers.IsValid())
1,023✔
3271
                        {
3272
                            eGeomType = OGRMVTFindGeomTypeFromTileStat(
543✔
3273
                                oTileStatLayers, pszLayerName);
3274
                        }
3275
                        CPLJSONArray oAttributesFromTileStats =
3276
                            OGRMVTFindAttributesFromTileStat(oTileStatLayers,
3277
                                                             pszLayerName);
2,046✔
3278

3279
                        poDS->m_apoLayers.push_back(
1,023✔
3280
                            std::unique_ptr<OGRLayer>(new OGRMVTLayer(
2,046✔
3281
                                poDS, pszLayerName, pabyDataLayer, nLayerSize,
3282
                                oFields, oAttributesFromTileStats, eGeomType)));
1,023✔
3283
                        CPLFree(pszLayerName);
1,023✔
3284
                        break;
1,023✔
3285
                    }
3286
                    else
3287
                    {
3288
                        SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimitLayer, FALSE);
239✔
3289
                    }
3290
                }
3291
                pabyData = pabyDataLimitLayer;
1,023✔
3292
            }
3293
            else
3294
            {
3295
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
×
3296
            }
3297
        }
3298

3299
        return poDS;
943✔
3300
    }
3301
    catch (const GPBException &e)
×
3302
    {
3303
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
×
3304
        delete poDS;
×
3305
        return nullptr;
×
3306
    }
3307
}
3308

3309
#ifdef HAVE_MVT_WRITE_SUPPORT
3310

3311
/************************************************************************/
3312
/*                           OGRMVTWriterDataset                        */
3313
/************************************************************************/
3314

3315
class OGRMVTWriterLayer;
3316

3317
struct OGRMVTFeatureContent
3318
{
3319
    std::vector<std::pair<std::string, MVTTileLayerValue>> oValues;
3320
    GIntBig nFID;
3321
};
3322

3323
class OGRMVTWriterDataset final : public GDALDataset
3324
{
3325
    class MVTFieldProperties
3326
    {
3327
      public:
3328
        CPLString m_osName;
3329
        std::set<MVTTileLayerValue> m_oSetValues;
3330
        std::set<MVTTileLayerValue> m_oSetAllValues;
3331
        double m_dfMinVal = 0;
3332
        double m_dfMaxVal = 0;
3333
        bool m_bAllInt = false;
3334
        MVTTileLayerValue::ValueType m_eType =
3335
            MVTTileLayerValue::ValueType::NONE;
3336
    };
3337

3338
    class MVTLayerProperties
3339
    {
3340
      public:
3341
        int m_nMinZoom = 0;
3342
        int m_nMaxZoom = 0;
3343
        std::map<MVTTileLayerFeature::GeomType, GIntBig> m_oCountGeomType;
3344
        std::map<CPLString, size_t> m_oMapFieldNameToIdx;
3345
        std::vector<MVTFieldProperties> m_aoFields;
3346
        std::set<CPLString> m_oSetFields;
3347
    };
3348

3349
    std::vector<std::unique_ptr<OGRMVTWriterLayer>> m_apoLayers;
3350
    CPLString m_osTempDB;
3351
    mutable std::mutex m_oDBMutex;
3352
    mutable bool m_bWriteFeatureError = false;
3353
    sqlite3_vfs *m_pMyVFS = nullptr;
3354
    sqlite3 *m_hDB = nullptr;
3355
    sqlite3_stmt *m_hInsertStmt = nullptr;
3356
    int m_nMinZoom = 0;
3357
    int m_nMaxZoom = 5;
3358
    double m_dfSimplification = 0.0;
3359
    double m_dfSimplificationMaxZoom = 0.0;
3360
    CPLJSONDocument m_oConf;
3361
    unsigned m_nExtent = knDEFAULT_EXTENT;
3362
    int m_nMetadataVersion = 2;
3363
    int m_nMVTVersion = 2;
3364
    int m_nBuffer = 5 * knDEFAULT_EXTENT / 256;
3365
    bool m_bGZip = true;
3366
    mutable CPLWorkerThreadPool m_oThreadPool;
3367
    bool m_bThreadPoolOK = false;
3368
    mutable GIntBig m_nTempTiles = 0;
3369
    CPLString m_osName;
3370
    CPLString m_osDescription;
3371
    CPLString m_osType{"overlay"};
3372
    sqlite3 *m_hDBMBTILES = nullptr;
3373
    OGREnvelope m_oEnvelope;
3374
    bool m_bMaxTileSizeOptSpecified = false;
3375
    bool m_bMaxFeaturesOptSpecified = false;
3376
    unsigned m_nMaxTileSize = 500000;
3377
    unsigned m_nMaxFeatures = 200000;
3378
    std::map<std::string, std::string> m_oMapLayerNameToDesc;
3379
    std::map<std::string, GIntBig> m_oMapLayerNameToFeatureCount;
3380
    CPLString m_osBounds;
3381
    CPLString m_osCenter;
3382
    CPLString m_osExtension{"pbf"};
3383
    OGRSpatialReference *m_poSRS = nullptr;
3384
    double m_dfTopX = 0.0;
3385
    double m_dfTopY = 0.0;
3386
    double m_dfTileDim0 = 0.0;
3387
    int m_nTileMatrixWidth0 =
3388
        1;  // Number of tiles along X axis at zoom level 0
3389
    int m_nTileMatrixHeight0 =
3390
        1;  // Number of tiles along Y axis at zoom level 0
3391
    bool m_bReuseTempFile = false;  // debug only
3392

3393
    OGRErr PreGenerateForTile(
3394
        int nZ, int nX, int nY, const CPLString &osTargetName,
3395
        bool bIsMaxZoomForLayer,
3396
        const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
3397
        GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
3398
        const OGREnvelope &sEnvelope) const;
3399

3400
    static void WriterTaskFunc(void *pParam);
3401

3402
    OGRErr PreGenerateForTileReal(int nZ, int nX, int nY,
3403
                                  const CPLString &osTargetName,
3404
                                  bool bIsMaxZoomForLayer,
3405
                                  const OGRMVTFeatureContent *poFeatureContent,
3406
                                  GIntBig nSerial, const OGRGeometry *poGeom,
3407
                                  const OGREnvelope &sEnvelope) const;
3408

3409
    void ConvertToTileCoords(double dfX, double dfY, int &nX, int &nY,
3410
                             double dfTopX, double dfTopY,
3411
                             double dfTileDim) const;
3412
    bool EncodeLineString(MVTTileLayerFeature *poGPBFeature,
3413
                          const OGRLineString *poLS, OGRLineString *poOutLS,
3414
                          bool bWriteLastPoint, bool bReverseOrder,
3415
                          GUInt32 nMinLineTo, double dfTopX, double dfTopY,
3416
                          double dfTileDim, int &nLastX, int &nLastY) const;
3417
    bool EncodePolygon(MVTTileLayerFeature *poGPBFeature,
3418
                       const OGRPolygon *poPoly, OGRPolygon *poOutPoly,
3419
                       double dfTopX, double dfTopY, double dfTileDim,
3420
                       int &nLastX, int &nLastY, double &dfArea) const;
3421
#ifdef notdef
3422
    bool EncodeRepairedOuterRing(MVTTileLayerFeature *poGPBFeature,
3423
                                 OGRPolygon &oOutPoly, int &nLastX,
3424
                                 int &nLastY) const;
3425
#endif
3426

3427
    static void UpdateLayerProperties(MVTLayerProperties *poLayerProperties,
3428
                                      const std::string &osKey,
3429
                                      const MVTTileLayerValue &oValue);
3430

3431
    void EncodeFeature(const void *pabyBlob, int nBlobSize,
3432
                       std::shared_ptr<MVTTileLayer> &poTargetLayer,
3433
                       std::map<CPLString, GUInt32> &oMapKeyToIdx,
3434
                       std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
3435
                       MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
3436
                       unsigned &nFeaturesInTile);
3437

3438
    std::string
3439
    EncodeTile(int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer,
3440
               sqlite3_stmt *hStmtRows,
3441
               std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
3442
               std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead);
3443

3444
    std::string RecodeTileLowerResolution(int nZ, int nX, int nY, int nExtent,
3445
                                          sqlite3_stmt *hStmtLayer,
3446
                                          sqlite3_stmt *hStmtRows);
3447

3448
    bool CreateOutput();
3449

3450
    bool GenerateMetadata(size_t nLayers,
3451
                          const std::map<CPLString, MVTLayerProperties> &oMap);
3452

3453
  public:
3454
    OGRMVTWriterDataset();
3455
    ~OGRMVTWriterDataset();
3456

3457
    CPLErr Close() override;
3458

3459
    OGRLayer *ICreateLayer(const char *pszName,
3460
                           const OGRGeomFieldDefn *poGeomFieldDefn,
3461
                           CSLConstList papszOptions) override;
3462

3463
    int TestCapability(const char *) override;
3464

3465
    OGRErr WriteFeature(OGRMVTWriterLayer *poLayer, OGRFeature *poFeature,
3466
                        GIntBig nSerial, OGRGeometry *poGeom);
3467

3468
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
3469
                               int nBandsIn, GDALDataType eDT,
3470
                               char **papszOptions);
3471

3472
    OGRSpatialReference *GetSRS()
179✔
3473
    {
3474
        return m_poSRS;
179✔
3475
    }
3476
};
3477

3478
/************************************************************************/
3479
/*                           OGRMVTWriterLayer                          */
3480
/************************************************************************/
3481

3482
class OGRMVTWriterLayer final : public OGRLayer
3483
{
3484
    friend class OGRMVTWriterDataset;
3485

3486
    OGRMVTWriterDataset *m_poDS = nullptr;
3487
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
3488
    OGRCoordinateTransformation *m_poCT = nullptr;
3489
    GIntBig m_nSerial = 0;
3490
    int m_nMinZoom = 0;
3491
    int m_nMaxZoom = 5;
3492
    CPLString m_osTargetName;
3493

3494
  public:
3495
    OGRMVTWriterLayer(OGRMVTWriterDataset *poDS, const char *pszLayerName,
3496
                      OGRSpatialReference *poSRS);
3497
    ~OGRMVTWriterLayer();
3498

3499
    void ResetReading() override
48✔
3500
    {
3501
    }
48✔
3502

3503
    OGRFeature *GetNextFeature() override
48✔
3504
    {
3505
        return nullptr;
48✔
3506
    }
3507

3508
    OGRFeatureDefn *GetLayerDefn() override
1,434✔
3509
    {
3510
        return m_poFeatureDefn;
1,434✔
3511
    }
3512

3513
    int TestCapability(const char *) override;
3514
    OGRErr ICreateFeature(OGRFeature *) override;
3515
    OGRErr CreateField(const OGRFieldDefn *, int) override;
3516

3517
    GDALDataset *GetDataset() override
49✔
3518
    {
3519
        return m_poDS;
49✔
3520
    }
3521
};
3522

3523
/************************************************************************/
3524
/*                          OGRMVTWriterLayer()                         */
3525
/************************************************************************/
3526

3527
OGRMVTWriterLayer::OGRMVTWriterLayer(OGRMVTWriterDataset *poDS,
170✔
3528
                                     const char *pszLayerName,
3529
                                     OGRSpatialReference *poSRSIn)
170✔
3530
{
3531
    m_poDS = poDS;
170✔
3532
    m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
170✔
3533
    SetDescription(m_poFeatureDefn->GetName());
170✔
3534
    m_poFeatureDefn->Reference();
170✔
3535

3536
    m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
170✔
3537

3538
    if (poSRSIn != nullptr && !poDS->GetSRS()->IsSame(poSRSIn))
170✔
3539
    {
3540
        m_poCT = OGRCreateCoordinateTransformation(poSRSIn, poDS->GetSRS());
2✔
3541
        if (m_poCT == nullptr)
2✔
3542
        {
3543
            // If we can't create a transformation, issue a warning - but
3544
            // continue the transformation.
3545
            CPLError(CE_Warning, CPLE_AppDefined,
1✔
3546
                     "Failed to create coordinate transformation between the "
3547
                     "input and target coordinate systems.");
3548
        }
3549
    }
3550
}
170✔
3551

3552
/************************************************************************/
3553
/*                          ~OGRMVTWriterLayer()                        */
3554
/************************************************************************/
3555

3556
OGRMVTWriterLayer::~OGRMVTWriterLayer()
340✔
3557
{
3558
    m_poFeatureDefn->Release();
170✔
3559
    delete m_poCT;
170✔
3560
}
340✔
3561

3562
/************************************************************************/
3563
/*                            TestCapability()                          */
3564
/************************************************************************/
3565

3566
int OGRMVTWriterLayer::TestCapability(const char *pszCap)
356✔
3567
{
3568

3569
    if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCCreateField))
356✔
3570
        return true;
96✔
3571
    return false;
260✔
3572
}
3573

3574
/************************************************************************/
3575
/*                            CreateField()                             */
3576
/************************************************************************/
3577

3578
OGRErr OGRMVTWriterLayer::CreateField(const OGRFieldDefn *poFieldDefn, int)
313✔
3579
{
3580
    m_poFeatureDefn->AddFieldDefn(poFieldDefn);
313✔
3581
    return OGRERR_NONE;
313✔
3582
}
3583

3584
/************************************************************************/
3585
/*                            ICreateFeature()                          */
3586
/************************************************************************/
3587

3588
OGRErr OGRMVTWriterLayer::ICreateFeature(OGRFeature *poFeature)
294✔
3589
{
3590
    OGRGeometry *poGeom = poFeature->GetGeometryRef();
294✔
3591
    if (poGeom == nullptr || poGeom->IsEmpty())
294✔
3592
        return OGRERR_NONE;
102✔
3593
    if (m_poCT)
192✔
3594
    {
3595
        poGeom->transform(m_poCT);
1✔
3596
    }
3597
    m_nSerial++;
192✔
3598
    return m_poDS->WriteFeature(this, poFeature, m_nSerial, poGeom);
192✔
3599
}
3600

3601
/************************************************************************/
3602
/*                          OGRMVTWriterDataset()                       */
3603
/************************************************************************/
3604

3605
OGRMVTWriterDataset::OGRMVTWriterDataset()
132✔
3606
{
3607
    // Default WebMercator tiling scheme
3608
    m_poSRS = new OGRSpatialReference();
132✔
3609
    m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
132✔
3610

3611
    InitWebMercatorTilingScheme(m_poSRS, m_dfTopX, m_dfTopY, m_dfTileDim0);
132✔
3612
}
132✔
3613

3614
/************************************************************************/
3615
/*                         ~OGRMVTWriterDataset()                       */
3616
/************************************************************************/
3617

3618
OGRMVTWriterDataset::~OGRMVTWriterDataset()
264✔
3619
{
3620
    OGRMVTWriterDataset::Close();
132✔
3621

3622
    if (m_pMyVFS)
132✔
3623
    {
3624
        sqlite3_vfs_unregister(m_pMyVFS);
132✔
3625
        CPLFree(m_pMyVFS->pAppData);
132✔
3626
        CPLFree(m_pMyVFS);
132✔
3627
    }
3628

3629
    m_poSRS->Release();
132✔
3630
}
264✔
3631

3632
/************************************************************************/
3633
/*                              Close()                                 */
3634
/************************************************************************/
3635

3636
CPLErr OGRMVTWriterDataset::Close()
254✔
3637
{
3638
    CPLErr eErr = CE_None;
254✔
3639
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
254✔
3640
    {
3641
        if (GetDescription()[0] != '\0')
132✔
3642
        {
3643
            if (!CreateOutput())
122✔
3644
                eErr = CE_Failure;
3✔
3645
        }
3646
        if (m_hInsertStmt != nullptr)
132✔
3647
        {
3648
            sqlite3_finalize(m_hInsertStmt);
129✔
3649
        }
3650
        if (m_hDB)
132✔
3651
        {
3652
            sqlite3_close(m_hDB);
129✔
3653
        }
3654
        if (m_hDBMBTILES)
132✔
3655
        {
3656
            sqlite3_close(m_hDBMBTILES);
76✔
3657
        }
3658
        if (!m_osTempDB.empty() && !m_bReuseTempFile &&
260✔
3659
            CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
128✔
3660
        {
3661
            VSIUnlink(m_osTempDB);
127✔
3662
        }
3663

3664
        if (GDALDataset::Close() != CE_None)
132✔
3665
            eErr = CE_Failure;
×
3666
    }
3667
    return eErr;
254✔
3668
}
3669

3670
/************************************************************************/
3671
/*                        ConvertToTileCoords()                     */
3672
/************************************************************************/
3673

3674
void OGRMVTWriterDataset::ConvertToTileCoords(double dfX, double dfY, int &nX,
12,219✔
3675
                                              int &nY, double dfTopX,
3676
                                              double dfTopY,
3677
                                              double dfTileDim) const
3678
{
3679
    if (dfTileDim == 0)
12,219✔
3680
    {
3681
        nX = static_cast<int>(dfX);
331✔
3682
        nY = static_cast<int>(dfY);
331✔
3683
    }
3684
    else
3685
    {
3686
        nX = static_cast<int>(
11,888✔
3687
            std::round((dfX - dfTopX) * m_nExtent / dfTileDim));
11,888✔
3688
        nY = static_cast<int>(
11,888✔
3689
            std::round((dfTopY - dfY) * m_nExtent / dfTileDim));
11,888✔
3690
    }
3691
}
12,219✔
3692

3693
/************************************************************************/
3694
/*                       GetCmdCountCombined()                          */
3695
/************************************************************************/
3696

3697
static unsigned GetCmdCountCombined(unsigned int nCmdId, unsigned int nCmdCount)
3,106✔
3698
{
3699
    return (nCmdId | (nCmdCount << 3));
3,106✔
3700
}
3701

3702
/************************************************************************/
3703
/*                          EncodeLineString()                          */
3704
/************************************************************************/
3705

3706
bool OGRMVTWriterDataset::EncodeLineString(
2,691✔
3707
    MVTTileLayerFeature *poGPBFeature, const OGRLineString *poLS,
3708
    OGRLineString *poOutLS, bool bWriteLastPoint, bool bReverseOrder,
3709
    GUInt32 nMinLineTo, double dfTopX, double dfTopY, double dfTileDim,
3710
    int &nLastX, int &nLastY) const
3711
{
3712
    const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
2,691✔
3713
    const int nLastXOri = nLastX;
2,683✔
3714
    const int nLastYOri = nLastY;
2,683✔
3715
    GUInt32 nLineToCount = 0;
2,683✔
3716
    const int nPoints = poLS->getNumPoints() - (bWriteLastPoint ? 0 : 1);
2,683✔
3717
    if (poOutLS)
2,682✔
3718
        poOutLS->setNumPoints(nPoints);
2,687✔
3719
    int nFirstX = 0;
2,686✔
3720
    int nFirstY = 0;
2,686✔
3721
    int nLastXValid = nLastX;
2,686✔
3722
    int nLastYValid = nLastY;
2,686✔
3723
    for (int i = 0; i < nPoints; i++)
13,602✔
3724
    {
3725
        int nX, nY;
3726
        int nSrcIdx = bReverseOrder ? poLS->getNumPoints() - 1 - i : i;
10,911✔
3727
        double dfX = poLS->getX(nSrcIdx);
10,912✔
3728
        double dfY = poLS->getY(nSrcIdx);
10,822✔
3729
        ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
10,821✔
3730
        int nDiffX = nX - nLastX;
10,895✔
3731
        int nDiffY = nY - nLastY;
10,895✔
3732
        if (i == 0 || nDiffX != 0 || nDiffY != 0)
10,895✔
3733
        {
3734
            if (i > 0)
3,740✔
3735
            {
3736
                nLineToCount++;
1,097✔
3737
                if (nLineToCount == 1)
1,097✔
3738
                {
3739
                    poGPBFeature->addGeometry(
309✔
3740
                        GetCmdCountCombined(knCMD_MOVETO, 1));
3741
                    const int nLastDiffX = nLastX - nLastXOri;
308✔
3742
                    const int nLastDiffY = nLastY - nLastYOri;
308✔
3743
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
308✔
3744
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
310✔
3745
                    if (poOutLS)
311✔
3746
                        poOutLS->setPoint(0, nLastX, nLastY);
311✔
3747

3748
                    // To be modified later
3749
                    poGPBFeature->addGeometry(
309✔
3750
                        GetCmdCountCombined(knCMD_LINETO, 0));
3751
                }
3752

3753
                poGPBFeature->addGeometry(EncodeSInt(nDiffX));
1,097✔
3754
                poGPBFeature->addGeometry(EncodeSInt(nDiffY));
1,099✔
3755
                if (poOutLS)
1,101✔
3756
                    poOutLS->setPoint(nLineToCount, nX, nY);
1,101✔
3757
            }
3758
            else
3759
            {
3760
                nFirstX = nX;
2,643✔
3761
                nFirstY = nY;
2,643✔
3762
            }
3763
            nLastXValid = nLastX;
3,761✔
3764
            nLastYValid = nLastY;
3,761✔
3765
            nLastX = nX;
3,761✔
3766
            nLastY = nY;
3,761✔
3767
        }
3768
    }
3769

3770
    // If last point of ring is identical to first one, discard it
3771
    if (nMinLineTo == 2 && nLineToCount > 0 && nFirstX == nLastX &&
2,691✔
3772
        nFirstY == nLastY)
105✔
3773
    {
3774
        poGPBFeature->resizeGeometryArray(poGPBFeature->getGeometryCount() - 2);
66✔
3775
        nLineToCount--;
38✔
3776
        nLastX = nLastXValid;
38✔
3777
        nLastY = nLastYValid;
38✔
3778
    }
3779

3780
    if (nLineToCount >= nMinLineTo)
2,663✔
3781
    {
3782
        if (poOutLS)
261✔
3783
            poOutLS->setNumPoints(1 + nLineToCount);
262✔
3784
        // Patch actual number of points in LINETO command
3785
        poGPBFeature->setGeometry(
262✔
3786
            nInitialSize + 3, GetCmdCountCombined(knCMD_LINETO, nLineToCount));
3787
        return true;
260✔
3788
    }
3789
    else
3790
    {
3791
        poGPBFeature->resizeGeometryArray(nInitialSize);
2,402✔
3792
        nLastX = nLastXOri;
2,372✔
3793
        nLastY = nLastYOri;
2,372✔
3794
        return false;
2,372✔
3795
    }
3796
}
3797

3798
#ifdef notdef
3799
/************************************************************************/
3800
/*                     EncodeRepairedOuterRing()                        */
3801
/************************************************************************/
3802

3803
bool OGRMVTWriterDataset::EncodeRepairedOuterRing(
3804
    MVTTileLayerFeature *poGPBFeature, OGRPolygon &oInPoly, int &nLastX,
3805
    int &nLastY) const
3806
{
3807
    std::unique_ptr<OGRGeometry> poFixedGeom(oInPoly.Buffer(0));
3808
    if (!poFixedGeom.get() || poFixedGeom->IsEmpty())
3809
    {
3810
        return false;
3811
    }
3812

3813
    OGRPolygon *poPoly = nullptr;
3814
    if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbMultiPolygon)
3815
    {
3816
        OGRMultiPolygon *poMP = poFixedGeom.get()->toMultiPolygon();
3817
        poPoly = poMP->getGeometryRef(0)->toPolygon();
3818
    }
3819
    else if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbPolygon)
3820
    {
3821
        poPoly = poFixedGeom.get()->toPolygon();
3822
    }
3823
    if (!poPoly)
3824
        return false;
3825

3826
    OGRLinearRing *poRing = poPoly->getExteriorRing();
3827
    const bool bReverseOrder = !poRing->isClockwise();
3828

3829
    const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
3830
    const int nLastXOri = nLastX;
3831
    const int nLastYOri = nLastY;
3832
    GUInt32 nLineToCount = 0;
3833
    const int nPoints = poRing->getNumPoints() - 1;
3834
    auto poOutLinearRing = std::make_unique<OGRLinearRing>();
3835
    poOutLinearRing->setNumPoints(nPoints);
3836
    for (int i = 0; i < nPoints; i++)
3837
    {
3838
        int nSrcIdx = bReverseOrder ? poRing->getNumPoints() - 1 - i : i;
3839
        double dfX = poRing->getX(nSrcIdx);
3840
        double dfY = poRing->getY(nSrcIdx);
3841
        int nX = static_cast<int>(std::round(dfX));
3842
        int nY = static_cast<int>(std::round(dfY));
3843
        if (nX != dfX || nY != dfY)
3844
            continue;
3845
        int nDiffX = nX - nLastX;
3846
        int nDiffY = nY - nLastY;
3847
        if (i == 0 || nDiffX != 0 || nDiffY != 0)
3848
        {
3849
            if (i > 0)
3850
            {
3851
                nLineToCount++;
3852
                if (nLineToCount == 1)
3853
                {
3854
                    poGPBFeature->addGeometry(
3855
                        GetCmdCountCombined(knCMD_MOVETO, 1));
3856
                    const int nLastDiffX = nLastX - nLastXOri;
3857
                    const int nLastDiffY = nLastY - nLastYOri;
3858
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
3859
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
3860
                    poOutLinearRing->setPoint(0, nLastX, nLastY);
3861

3862
                    // To be modified later
3863
                    poGPBFeature->addGeometry(
3864
                        GetCmdCountCombined(knCMD_LINETO, 0));
3865
                }
3866

3867
                poGPBFeature->addGeometry(EncodeSInt(nDiffX));
3868
                poGPBFeature->addGeometry(EncodeSInt(nDiffY));
3869
                poOutLinearRing->setPoint(nLineToCount, nX, nY);
3870
            }
3871
            nLastX = nX;
3872
            nLastY = nY;
3873
        }
3874
    }
3875
    if (nLineToCount >= 2)
3876
    {
3877
        poOutLinearRing->setNumPoints(1 + nLineToCount);
3878
        OGRPolygon oOutPoly;
3879
        oOutPoly.addRingDirectly(poOutLinearRing.release());
3880
        int bIsValid;
3881
        {
3882
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
3883
            bIsValid = oOutPoly.IsValid();
3884
        }
3885
        if (bIsValid)
3886
        {
3887
            // Patch actual number of points in LINETO command
3888
            poGPBFeature->setGeometry(
3889
                nInitialSize + 3,
3890
                GetCmdCountCombined(knCMD_LINETO, nLineToCount));
3891
            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
3892
            return true;
3893
        }
3894
    }
3895

3896
    poGPBFeature->resizeGeometryArray(nInitialSize);
3897
    nLastX = nLastXOri;
3898
    nLastY = nLastYOri;
3899
    return false;
3900
}
3901
#endif
3902

3903
/************************************************************************/
3904
/*                          EncodePolygon()                             */
3905
/************************************************************************/
3906

3907
bool OGRMVTWriterDataset::EncodePolygon(MVTTileLayerFeature *poGPBFeature,
1,363✔
3908
                                        const OGRPolygon *poPoly,
3909
                                        OGRPolygon *poOutPoly, double dfTopX,
3910
                                        double dfTopY, double dfTileDim,
3911
                                        int &nLastX, int &nLastY,
3912
                                        double &dfArea) const
3913
{
3914
    dfArea = 0;
1,363✔
3915
    auto poOutOuterRing = std::make_unique<OGRLinearRing>();
2,706✔
3916
    for (int i = 0; i < 1 + poPoly->getNumInteriorRings(); i++)
1,525✔
3917
    {
3918
        const OGRLinearRing *poRing = (i == 0) ? poPoly->getExteriorRing()
1,337✔
3919
                                               : poPoly->getInteriorRing(i - 1);
31✔
3920
        if (poRing->getNumPoints() < 4 ||
2,704✔
3921
            poRing->getX(0) != poRing->getX(poRing->getNumPoints() - 1) ||
2,711✔
3922
            poRing->getY(0) != poRing->getY(poRing->getNumPoints() - 1))
1,375✔
3923
        {
3924
            if (i == 0)
×
3925
                return false;
1,144✔
3926
            continue;
76✔
3927
        }
3928
        const bool bWriteLastPoint = false;
1,352✔
3929
        // If dealing with input geometry in CRS units, exterior rings must
3930
        // be clockwise oriented.
3931
        // But if re-encoding a geometry already in tile coordinates
3932
        // (dfTileDim == 0), this is the reverse.
3933
        const bool bReverseOrder = dfTileDim != 0
3934
                                       ? ((i == 0 && !poRing->isClockwise()) ||
1,459✔
3935
                                          (i > 0 && poRing->isClockwise()))
24✔
3936
                                       : ((i == 0 && poRing->isClockwise()) ||
83✔
3937
                                          (i > 0 && !poRing->isClockwise()));
1✔
3938
        const GUInt32 nMinLineTo = 2;
1,374✔
3939
        std::unique_ptr<OGRLinearRing> poOutInnerRing;
×
3940
        if (i > 0)
1,374✔
3941
            poOutInnerRing = std::make_unique<OGRLinearRing>();
25✔
3942
        OGRLinearRing *poOutRing =
3943
            poOutInnerRing.get() ? poOutInnerRing.get() : poOutOuterRing.get();
1,374✔
3944

3945
        bool bSuccess = EncodeLineString(
1,345✔
3946
            poGPBFeature, poRing, poOutRing, bWriteLastPoint, bReverseOrder,
3947
            nMinLineTo, dfTopX, dfTopY, dfTileDim, nLastX, nLastY);
3948
        if (!bSuccess)
1,346✔
3949
        {
3950
            if (i == 0)
1,143✔
3951
                return false;
1,137✔
3952
            continue;
6✔
3953
        }
3954

3955
        if (poOutPoly == nullptr)
203✔
3956
        {
3957
            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
58✔
3958
            continue;
58✔
3959
        }
3960

3961
        poOutRing->closeRings();
145✔
3962

3963
        poOutPoly->addRing(poOutRing);
133✔
3964
        if (i > 0)
134✔
3965
            dfArea -= poOutRing->get_Area();
17✔
3966
        else
3967
            dfArea = poOutRing->get_Area();
117✔
3968

3969
        poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
131✔
3970
    }
3971

3972
    return true;
199✔
3973
}
3974

3975
/************************************************************************/
3976
/*                          PreGenerateForTile()                        */
3977
/************************************************************************/
3978

3979
OGRErr OGRMVTWriterDataset::PreGenerateForTileReal(
3,997✔
3980
    int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
3981
    bool bIsMaxZoomForLayer, const OGRMVTFeatureContent *poFeatureContent,
3982
    GIntBig nSerial, const OGRGeometry *poGeom,
3983
    const OGREnvelope &sEnvelope) const
3984
{
3985
    double dfTileDim = m_dfTileDim0 / (1 << nZ);
3,997✔
3986
    double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
3,997✔
3987
    double dfTopX = m_dfTopX + nTileX * dfTileDim;
3,997✔
3988
    double dfTopY = m_dfTopY - nTileY * dfTileDim;
3,997✔
3989
    double dfBottomRightX = dfTopX + dfTileDim;
3,997✔
3990
    double dfBottomRightY = dfTopY - dfTileDim;
3,997✔
3991
    double dfIntersectTopX = dfTopX - dfBuffer;
3,997✔
3992
    double dfIntersectTopY = dfTopY + dfBuffer;
3,997✔
3993
    double dfIntersectBottomRightX = dfBottomRightX + dfBuffer;
3,997✔
3994
    double dfIntersectBottomRightY = dfBottomRightY - dfBuffer;
3,997✔
3995

3996
    const OGRGeometry *poIntersection;
3997
    std::unique_ptr<OGRGeometry> poIntersectionHolder;  // keep in that scope
3,961✔
3998
    if (sEnvelope.MinX >= dfIntersectTopX &&
3,997✔
3999
        sEnvelope.MinY >= dfIntersectBottomRightY &&
3,977✔
4000
        sEnvelope.MaxX <= dfIntersectBottomRightX &&
3,973✔
4001
        sEnvelope.MaxY <= dfIntersectTopY)
3,947✔
4002
    {
4003
        poIntersection = poGeom;
3,944✔
4004
    }
4005
    else
4006
    {
4007
        OGRLinearRing *poLR = new OGRLinearRing();
53✔
4008
        poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
49✔
4009
        poLR->addPoint(dfIntersectTopX, dfIntersectBottomRightY);
49✔
4010
        poLR->addPoint(dfIntersectBottomRightX, dfIntersectBottomRightY);
49✔
4011
        poLR->addPoint(dfIntersectBottomRightX, dfIntersectTopY);
49✔
4012
        poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
49✔
4013
        OGRPolygon oPoly;
49✔
4014
        oPoly.addRingDirectly(poLR);
49✔
4015

4016
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
49✔
4017
        auto poTmp = poGeom->Intersection(&oPoly);
49✔
4018
        poIntersection = poTmp;
47✔
4019
        poIntersectionHolder.reset(poTmp);
47✔
4020
        if (poIntersection == nullptr || poIntersection->IsEmpty())
46✔
4021
        {
4022
            return OGRERR_NONE;
2✔
4023
        }
4024
    }
4025

4026
    // Create a layer with a single feature in it
4027
    std::shared_ptr<MVTTileLayer> poLayer =
4028
        std::shared_ptr<MVTTileLayer>(new MVTTileLayer());
7,929✔
4029
    std::shared_ptr<MVTTileLayerFeature> poGPBFeature =
4030
        std::shared_ptr<MVTTileLayerFeature>(new MVTTileLayerFeature());
7,884✔
4031
    poLayer->addFeature(poGPBFeature);
3,918✔
4032

4033
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
3,954✔
4034
    if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
3,996✔
4035
        poGPBFeature->setType(MVTTileLayerFeature::GeomType::POINT);
1,388✔
4036
    else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
2,608✔
4037
        poGPBFeature->setType(MVTTileLayerFeature::GeomType::LINESTRING);
1,344✔
4038
    else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
1,264✔
4039
        poGPBFeature->setType(MVTTileLayerFeature::GeomType::POLYGON);
1,264✔
4040
    else
4041
    {
4042
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
×
4043
        return OGRERR_NONE;
×
4044
    }
4045

4046
    OGRwkbGeometryType eGeomToEncodeType =
4047
        wkbFlatten(poIntersection->getGeometryType());
3,923✔
4048

4049
    // Simplify contour if requested by user
4050
    const OGRGeometry *poGeomToEncode = poIntersection;
3,911✔
4051
    std::unique_ptr<OGRGeometry> poGeomSimplified;
3,953✔
4052
    const double dfSimplification =
3,911✔
4053
        bIsMaxZoomForLayer ? m_dfSimplificationMaxZoom : m_dfSimplification;
3,911✔
4054
    if (dfSimplification > 0 &&
3,911✔
4055
        (eGeomType == wkbLineString || eGeomType == wkbMultiLineString ||
12✔
4056
         eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon))
12✔
4057
    {
4058
        const double dfTol = dfTileDim / m_nExtent;
×
UNCOV
4059
        poGeomSimplified = std::unique_ptr<OGRGeometry>(
×
4060
            poIntersection->SimplifyPreserveTopology(dfTol * dfSimplification));
12✔
4061
        if (poGeomSimplified.get())
12✔
4062
        {
4063
            poGeomToEncode = poGeomSimplified.get();
12✔
4064
            eGeomToEncodeType = wkbFlatten(poGeomSimplified->getGeometryType());
12✔
4065
        }
4066
    }
4067

4068
    bool bGeomOK = false;
3,946✔
4069
    double dfAreaOrLength = 0.0;
3,946✔
4070

4071
    const auto EmitValidPolygon =
4072
        [this, &bGeomOK, &dfAreaOrLength,
57✔
4073
         &poGPBFeature](const OGRGeometry *poValidGeom)
182✔
4074
    {
4075
        bGeomOK = false;
57✔
4076
        dfAreaOrLength = 0;
57✔
4077
        int nLastX = 0;
57✔
4078
        int nLastY = 0;
57✔
4079

4080
        if (wkbFlatten(poValidGeom->getGeometryType()) == wkbPolygon)
57✔
4081
        {
4082
            const OGRPolygon *poPoly = poValidGeom->toPolygon();
11✔
4083
            double dfPartArea = 0.0;
11✔
4084
            bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
11✔
4085
                                    0, nLastX, nLastY, dfPartArea);
4086
            dfAreaOrLength = dfPartArea;
11✔
4087
        }
4088
        else if (OGR_GT_IsSubClassOf(poValidGeom->getGeometryType(),
46✔
4089
                                     wkbGeometryCollection))
46✔
4090
        {
4091
            for (auto &&poSubGeom : poValidGeom->toGeometryCollection())
130✔
4092
            {
4093
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
88✔
4094
                {
4095
                    const OGRPolygon *poPoly = poSubGeom->toPolygon();
35✔
4096
                    double dfPartArea = 0.0;
36✔
4097
                    bGeomOK |=
36✔
4098
                        EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
36✔
4099
                                      0, nLastX, nLastY, dfPartArea);
36✔
4100
                    dfAreaOrLength += dfPartArea;
36✔
4101
                }
4102
                else if (wkbFlatten(poSubGeom->getGeometryType()) ==
52✔
4103
                         wkbMultiPolygon)
4104
                {
4105
                    const OGRMultiPolygon *poMPoly =
4106
                        poSubGeom->toMultiPolygon();
5✔
4107
                    for (const auto *poPoly : poMPoly)
15✔
4108
                    {
4109
                        double dfPartArea = 0.0;
10✔
4110
                        bGeomOK |=
10✔
4111
                            EncodePolygon(poGPBFeature.get(), poPoly, nullptr,
10✔
4112
                                          0, 0, 0, nLastX, nLastY, dfPartArea);
10✔
4113
                        dfAreaOrLength += dfPartArea;
10✔
4114
                    }
4115
                }
4116
            }
4117
        }
4118
    };
57✔
4119

4120
    if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
3,946✔
4121
    {
4122
        if (eGeomToEncodeType == wkbPoint)
1,354✔
4123
        {
4124
            const OGRPoint *poPoint = poIntersection->toPoint();
975✔
4125
            int nX, nY;
4126
            double dfX = poPoint->getX();
973✔
4127
            double dfY = poPoint->getY();
978✔
4128
            bGeomOK = true;
979✔
4129
            ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
979✔
4130
            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_MOVETO, 1));
974✔
4131
            poGPBFeature->addGeometry(EncodeSInt(nX));
977✔
4132
            poGPBFeature->addGeometry(EncodeSInt(nY));
980✔
4133
        }
4134
        else if (eGeomToEncodeType == wkbMultiPoint ||
379✔
4135
                 eGeomToEncodeType == wkbGeometryCollection)
4136
        {
4137
            const OGRGeometryCollection *poGC =
4138
                poIntersection->toGeometryCollection();
387✔
4139
            std::set<std::pair<int, int>> oSetUniqueCoords;
744✔
4140
            poGPBFeature->addGeometry(
372✔
4141
                GetCmdCountCombined(knCMD_MOVETO, 0));  // To be modified later
4142
            int nLastX = 0;
372✔
4143
            int nLastY = 0;
372✔
4144
            for (auto &&poSubGeom : poGC)
764✔
4145
            {
4146
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPoint)
378✔
4147
                {
4148
                    const OGRPoint *poPoint = poSubGeom->toPoint();
381✔
4149
                    int nX, nY;
4150
                    double dfX = poPoint->getX();
377✔
4151
                    double dfY = poPoint->getY();
382✔
4152
                    ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY,
382✔
4153
                                        dfTileDim);
4154
                    if (oSetUniqueCoords.find(std::pair<int, int>(nX, nY)) ==
378✔
4155
                        oSetUniqueCoords.end())
758✔
4156
                    {
4157
                        oSetUniqueCoords.insert(std::pair<int, int>(nX, nY));
377✔
4158

4159
                        int nDiffX = nX - nLastX;
386✔
4160
                        int nDiffY = nY - nLastY;
386✔
4161
                        poGPBFeature->addGeometry(EncodeSInt(nDiffX));
386✔
4162
                        poGPBFeature->addGeometry(EncodeSInt(nDiffY));
382✔
4163
                        nLastX = nX;
387✔
4164
                        nLastY = nY;
387✔
4165
                    }
4166
                }
4167
            }
4168
            GUInt32 nPoints = static_cast<GUInt32>(oSetUniqueCoords.size());
379✔
4169
            bGeomOK = nPoints > 0;
371✔
4170
            poGPBFeature->setGeometry(
371✔
4171
                0, GetCmdCountCombined(knCMD_MOVETO, nPoints));
4172
        }
1,353✔
4173
    }
4174
    else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
2,592✔
4175
    {
4176
        const bool bWriteLastPoint = true;
1,321✔
4177
        const bool bReverseOrder = false;
1,321✔
4178
        const GUInt32 nMinLineTo = 1;
1,321✔
4179

4180
        if (eGeomToEncodeType == wkbLineString)
1,321✔
4181
        {
4182
            const OGRLineString *poLS = poGeomToEncode->toLineString();
915✔
4183
            int nLastX = 0;
923✔
4184
            int nLastY = 0;
923✔
4185
            OGRLineString oOutLS;
923✔
4186
            bGeomOK =
910✔
4187
                EncodeLineString(poGPBFeature.get(), poLS, &oOutLS,
924✔
4188
                                 bWriteLastPoint, bReverseOrder, nMinLineTo,
4189
                                 dfTopX, dfTopY, dfTileDim, nLastX, nLastY);
4190
            dfAreaOrLength = oOutLS.get_Length();
910✔
4191
        }
4192
        else if (eGeomToEncodeType == wkbMultiLineString ||
406✔
4193
                 eGeomToEncodeType == wkbGeometryCollection)
4194
        {
4195
            const OGRGeometryCollection *poGC =
4196
                poGeomToEncode->toGeometryCollection();
392✔
4197
            int nLastX = 0;
383✔
4198
            int nLastY = 0;
383✔
4199
            for (auto &&poSubGeom : poGC)
771✔
4200
            {
4201
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbLineString)
395✔
4202
                {
4203
                    const OGRLineString *poLS = poSubGeom->toLineString();
393✔
4204
                    OGRLineString oOutLS;
391✔
4205
                    bool bSubGeomOK = EncodeLineString(
392✔
4206
                        poGPBFeature.get(), poLS, &oOutLS, bWriteLastPoint,
4207
                        bReverseOrder, nMinLineTo, dfTopX, dfTopY, dfTileDim,
4208
                        nLastX, nLastY);
4209
                    if (bSubGeomOK)
391✔
4210
                        dfAreaOrLength += oOutLS.get_Length();
18✔
4211
                    bGeomOK |= bSubGeomOK;
391✔
4212
                }
4213
            }
4214
        }
1,312✔
4215
    }
4216
    else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
1,271✔
4217
    {
4218
        if (eGeomToEncodeType == wkbPolygon)
1,288✔
4219
        {
4220
            const OGRPolygon *poPoly = poGeomToEncode->toPolygon();
921✔
4221
            int nLastX = 0;
938✔
4222
            int nLastY = 0;
938✔
4223
            OGRPolygon oOutPoly;
1,838✔
4224
            const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
913✔
4225
            CPL_IGNORE_RET_VAL(nInitialSize);
923✔
4226
            bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, &oOutPoly,
926✔
4227
                                    dfTopX, dfTopY, dfTileDim, nLastX, nLastY,
4228
                                    dfAreaOrLength);
4229
            int bIsValid;
4230
            {
4231
                CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
930✔
4232
                bIsValid = oOutPoly.IsValid();
911✔
4233
            }
4234
            if (!bIsValid)
900✔
4235
            {
4236
                // Build a valid geometry from the initial MVT geometry and emit
4237
                // it
4238
                std::unique_ptr<OGRGeometry> poPolyValid(oOutPoly.MakeValid());
110✔
4239
                if (poPolyValid)
55✔
4240
                {
4241
                    poGPBFeature->resizeGeometryArray(nInitialSize);
55✔
4242
                    EmitValidPolygon(poPolyValid.get());
55✔
4243
                }
4244
            }
4245
        }
4246
        else if (eGeomToEncodeType == wkbMultiPolygon ||
367✔
4247
                 eGeomToEncodeType == wkbGeometryCollection)
4248
        {
4249
            const OGRGeometryCollection *poGC =
4250
                poGeomToEncode->toGeometryCollection();
306✔
4251
            int nLastX = 0;
371✔
4252
            int nLastY = 0;
371✔
4253
            OGRMultiPolygon oOutMP;
728✔
4254
            const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
368✔
4255
            CPL_IGNORE_RET_VAL(nInitialSize);
363✔
4256
            for (auto &&poSubGeom : poGC)
728✔
4257
            {
4258
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
374✔
4259
                {
4260
                    const OGRPolygon *poPoly = poSubGeom->toPolygon();
368✔
4261
                    double dfPartArea = 0.0;
367✔
4262
                    auto poOutPoly = std::make_unique<OGRPolygon>();
722✔
4263
                    bGeomOK |= EncodePolygon(
365✔
4264
                        poGPBFeature.get(), poPoly, poOutPoly.get(), dfTopX,
4265
                        dfTopY, dfTileDim, nLastX, nLastY, dfPartArea);
369✔
4266
                    dfAreaOrLength += dfPartArea;
369✔
4267
                    oOutMP.addGeometryDirectly(poOutPoly.release());
369✔
4268
                }
4269
            }
4270
            int bIsValid;
4271
            {
4272
                CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
363✔
4273
                bIsValid = oOutMP.IsValid();
358✔
4274
            }
4275
            if (!bIsValid)
357✔
4276
            {
4277
                // Build a valid geometry from the initial MVT geometry and emit
4278
                // it
4279
                std::unique_ptr<OGRGeometry> poMPValid(oOutMP.MakeValid());
4✔
4280
                if (poMPValid)
2✔
4281
                {
4282
                    poGPBFeature->resizeGeometryArray(nInitialSize);
2✔
4283
                    EmitValidPolygon(poMPValid.get());
2✔
4284
                }
4285
            }
4286
        }
4287
    }
4288
    if (!bGeomOK)
3,938✔
4289
        return OGRERR_NONE;
2,413✔
4290

4291
    for (const auto &pair : poFeatureContent->oValues)
5,807✔
4292
    {
4293
        GUInt32 nKey = poLayer->addKey(pair.first);
4,272✔
4294
        GUInt32 nVal = poLayer->addValue(pair.second);
4,309✔
4295
        poGPBFeature->addTag(nKey);
4,279✔
4296
        poGPBFeature->addTag(nVal);
4,284✔
4297
    }
4298
    if (poFeatureContent->nFID >= 0)
1,526✔
4299
    {
4300
        poGPBFeature->setId(poFeatureContent->nFID);
53✔
4301
    }
4302

4303
#ifdef notdef
4304
    {
4305
        MVTTile oTile;
4306
        poLayer->setName("x");
4307
        oTile.addLayer(poLayer);
4308

4309
        CPLString oBuffer(oTile.write());
4310

4311
        VSILFILE *fp = VSIFOpenL(
4312
            CPLSPrintf("/tmp/%d-%d-%d.pbf", nZ, nTileX, nTileY), "wb");
4313
        VSIFWriteL(oBuffer.data(), 1, oBuffer.size(), fp);
4314
        VSIFCloseL(fp);
4315
    }
4316
#endif
4317

4318
    // GPB encode the layer with our single feature
4319
    CPLString oBuffer(poLayer->write());
3,065✔
4320

4321
    // Compress buffer
4322
    size_t nCompressedSize = 0;
1,514✔
4323
    void *pCompressed = CPLZLibDeflate(oBuffer.data(), oBuffer.size(), -1,
1,514✔
4324
                                       nullptr, 0, &nCompressedSize);
4325
    oBuffer.assign(static_cast<char *>(pCompressed), nCompressedSize);
1,519✔
4326
    CPLFree(pCompressed);
1,497✔
4327

4328
    const auto InsertIntoDb = [&]()
1,540✔
4329
    {
4330
        m_nTempTiles++;
15,400✔
4331
        sqlite3_bind_int(m_hInsertStmt, 1, nZ);
1,540✔
4332
        sqlite3_bind_int(m_hInsertStmt, 2, nTileX);
1,540✔
4333
        sqlite3_bind_int(m_hInsertStmt, 3, nTileY);
1,540✔
4334
        sqlite3_bind_text(m_hInsertStmt, 4, osTargetName.c_str(), -1,
1,540✔
4335
                          SQLITE_STATIC);
4336
        sqlite3_bind_int64(m_hInsertStmt, 5, nSerial);
1,540✔
4337
        sqlite3_bind_blob(m_hInsertStmt, 6, oBuffer.data(),
1,540✔
4338
                          static_cast<int>(oBuffer.size()), SQLITE_STATIC);
1,540✔
4339
        sqlite3_bind_int(m_hInsertStmt, 7,
1,540✔
4340
                         static_cast<int>(poGPBFeature->getType()));
1,540✔
4341
        sqlite3_bind_double(m_hInsertStmt, 8, dfAreaOrLength);
1,540✔
4342
        int rc = sqlite3_step(m_hInsertStmt);
1,540✔
4343
        sqlite3_reset(m_hInsertStmt);
1,540✔
4344
        return rc;
1,540✔
4345
    };
1,506✔
4346

4347
    int rc;
4348
    if (m_bThreadPoolOK)
1,506✔
4349
    {
4350
        std::lock_guard<std::mutex> oLock(m_oDBMutex);
1,497✔
4351
        rc = InsertIntoDb();
1,515✔
4352
    }
4353
    else
4354
    {
4355
        rc = InsertIntoDb();
9✔
4356
    }
4357

4358
    if (!(rc == SQLITE_OK || rc == SQLITE_DONE))
1,540✔
4359
    {
4360
        return OGRERR_FAILURE;
8✔
4361
    }
4362

4363
    return OGRERR_NONE;
1,532✔
4364
}
4365

4366
/************************************************************************/
4367
/*                           MVTWriterTask()                            */
4368
/************************************************************************/
4369

4370
class MVTWriterTask
4371
{
4372
  public:
4373
    const OGRMVTWriterDataset *poDS;
4374
    int nZ;
4375
    int nTileX;
4376
    int nTileY;
4377
    CPLString osTargetName;
4378
    bool bIsMaxZoomForLayer;
4379
    std::shared_ptr<OGRMVTFeatureContent> poFeatureContent;
4380
    GIntBig nSerial;
4381
    std::shared_ptr<OGRGeometry> poGeom;
4382
    OGREnvelope sEnvelope;
4383
};
4384

4385
/************************************************************************/
4386
/*                          WriterTaskFunc()                            */
4387
/************************************************************************/
4388

4389
void OGRMVTWriterDataset::WriterTaskFunc(void *pParam)
3,991✔
4390
{
4391
    MVTWriterTask *poTask = static_cast<MVTWriterTask *>(pParam);
3,991✔
4392
    OGRErr eErr = poTask->poDS->PreGenerateForTileReal(
15,813✔
4393
        poTask->nZ, poTask->nTileX, poTask->nTileY, poTask->osTargetName,
3,967✔
4394
        poTask->bIsMaxZoomForLayer, poTask->poFeatureContent.get(),
3,977✔
4395
        poTask->nSerial, poTask->poGeom.get(), poTask->sEnvelope);
3,991✔
4396
    if (eErr != OGRERR_NONE)
3,878✔
4397
    {
4398
        std::lock_guard oLock(poTask->poDS->m_oDBMutex);
7✔
4399
        poTask->poDS->m_bWriteFeatureError = true;
7✔
4400
    }
4401
    delete poTask;
3,878✔
4402
}
4,027✔
4403

4404
/************************************************************************/
4405
/*                         PreGenerateForTile()                         */
4406
/************************************************************************/
4407

4408
OGRErr OGRMVTWriterDataset::PreGenerateForTile(
4,033✔
4409
    int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
4410
    bool bIsMaxZoomForLayer,
4411
    const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
4412
    GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
4413
    const OGREnvelope &sEnvelope) const
4414
{
4415
    if (!m_bThreadPoolOK)
4,033✔
4416
    {
4417
        return PreGenerateForTileReal(
25✔
4418
            nZ, nTileX, nTileY, osTargetName, bIsMaxZoomForLayer,
4419
            poFeatureContent.get(), nSerial, poGeom.get(), sEnvelope);
50✔
4420
    }
4421
    else
4422
    {
4423
        MVTWriterTask *poTask = new MVTWriterTask;
4,008✔
4424
        poTask->poDS = this;
4,008✔
4425
        poTask->nZ = nZ;
4,008✔
4426
        poTask->nTileX = nTileX;
4,008✔
4427
        poTask->nTileY = nTileY;
4,008✔
4428
        poTask->osTargetName = osTargetName;
4,008✔
4429
        poTask->bIsMaxZoomForLayer = bIsMaxZoomForLayer;
4,008✔
4430
        poTask->poFeatureContent = poFeatureContent;
4,008✔
4431
        poTask->nSerial = nSerial;
4,008✔
4432
        poTask->poGeom = poGeom;
4,008✔
4433
        poTask->sEnvelope = sEnvelope;
4,008✔
4434
        m_oThreadPool.SubmitJob(OGRMVTWriterDataset::WriterTaskFunc, poTask);
4,008✔
4435
        // Do not queue more than 1000 jobs to avoid memory exhaustion
4436
        m_oThreadPool.WaitCompletion(1000);
4,008✔
4437

4438
        std::lock_guard oLock(m_oDBMutex);
4,008✔
4439
        return m_bWriteFeatureError ? OGRERR_FAILURE : OGRERR_NONE;
4,008✔
4440
    }
4441
}
4442

4443
/************************************************************************/
4444
/*                        UpdateLayerProperties()                       */
4445
/************************************************************************/
4446

4447
void OGRMVTWriterDataset::UpdateLayerProperties(
4,353✔
4448
    MVTLayerProperties *poLayerProperties, const std::string &osKey,
4449
    const MVTTileLayerValue &oValue)
4450
{
4451
    auto oFieldIter = poLayerProperties->m_oMapFieldNameToIdx.find(osKey);
4,353✔
4452
    MVTFieldProperties *poFieldProps = nullptr;
4,353✔
4453
    if (oFieldIter == poLayerProperties->m_oMapFieldNameToIdx.end())
4,353✔
4454
    {
4455
        if (poLayerProperties->m_oSetFields.size() < knMAX_COUNT_FIELDS)
181✔
4456
        {
4457
            poLayerProperties->m_oSetFields.insert(osKey);
181✔
4458
            if (poLayerProperties->m_oMapFieldNameToIdx.size() <
181✔
4459
                knMAX_REPORT_FIELDS)
4460
            {
4461
                MVTFieldProperties oFieldProps;
362✔
4462
                oFieldProps.m_osName = osKey;
181✔
4463
                if (oValue.isNumeric())
181✔
4464
                {
4465
                    oFieldProps.m_dfMinVal = oValue.getNumericValue();
73✔
4466
                    oFieldProps.m_dfMaxVal = oValue.getNumericValue();
73✔
4467
                    oFieldProps.m_bAllInt = true;  // overridden just below
73✔
4468
                }
4469
                oFieldProps.m_eType =
181✔
4470
                    oValue.isNumeric()  ? MVTTileLayerValue::ValueType::DOUBLE
289✔
4471
                    : oValue.isString() ? MVTTileLayerValue::ValueType::STRING
108✔
4472
                                        : MVTTileLayerValue::ValueType::BOOL;
4473

4474
                poLayerProperties->m_oMapFieldNameToIdx[osKey] =
181✔
4475
                    poLayerProperties->m_aoFields.size();
181✔
4476
                poLayerProperties->m_aoFields.push_back(std::move(oFieldProps));
181✔
4477
                poFieldProps = &(poLayerProperties->m_aoFields.back());
181✔
4478
            }
4479
        }
4480
    }
4481
    else
4482
    {
4483
        poFieldProps = &(poLayerProperties->m_aoFields[oFieldIter->second]);
4,172✔
4484
    }
4485

4486
    if (poFieldProps)
4,353✔
4487
    {
4488
        if (oValue.getType() == MVTTileLayerValue::ValueType::BOOL)
4,353✔
4489
        {
4490
            MVTTileLayerValue oUniqVal;
24✔
4491
            oUniqVal.setBoolValue(oValue.getBoolValue());
12✔
4492
            poFieldProps->m_oSetAllValues.insert(oUniqVal);
12✔
4493
            poFieldProps->m_oSetValues.insert(oUniqVal);
12✔
4494
        }
4495
        else if (oValue.isNumeric())
4,341✔
4496
        {
4497
            if (poFieldProps->m_bAllInt)
1,780✔
4498
            {
4499
                poFieldProps->m_bAllInt =
935✔
4500
                    oValue.getType() == MVTTileLayerValue::ValueType::INT ||
1,870✔
4501
                    oValue.getType() == MVTTileLayerValue::ValueType::SINT ||
2,751✔
4502
                    (oValue.getType() == MVTTileLayerValue::ValueType::UINT &&
1,798✔
4503
                     oValue.getUIntValue() < GINT64_MAX);
881✔
4504
            }
4505
            double dfVal = oValue.getNumericValue();
1,780✔
4506
            poFieldProps->m_dfMinVal =
1,780✔
4507
                std::min(poFieldProps->m_dfMinVal, dfVal);
1,780✔
4508
            poFieldProps->m_dfMaxVal =
1,780✔
4509
                std::max(poFieldProps->m_dfMaxVal, dfVal);
1,780✔
4510
            if (poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
1,780✔
4511
            {
4512
                MVTTileLayerValue oUniqVal;
3,560✔
4513
                oUniqVal.setDoubleValue(dfVal);
1,780✔
4514
                poFieldProps->m_oSetAllValues.insert(oUniqVal);
1,780✔
4515
                if (poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
1,780✔
4516
                {
4517
                    poFieldProps->m_oSetValues.insert(oUniqVal);
1,780✔
4518
                }
4519
            }
4520
        }
4521
        else if (oValue.isString() &&
5,122✔
4522
                 poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
2,561✔
4523
        {
4524
            auto osVal = oValue.getStringValue();
5,122✔
4525
            MVTTileLayerValue oUniqVal;
5,122✔
4526
            oUniqVal.setStringValue(osVal);
2,561✔
4527
            poFieldProps->m_oSetAllValues.insert(oUniqVal);
2,561✔
4528
            if (osVal.size() <= knMAX_STRING_VALUE_LENGTH &&
5,122✔
4529
                poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
2,561✔
4530
            {
4531
                poFieldProps->m_oSetValues.insert(oUniqVal);
2,561✔
4532
            }
4533
        }
4534
    }
4535
}
4,353✔
4536

4537
/************************************************************************/
4538
/*                           GZIPCompress()                             */
4539
/************************************************************************/
4540

4541
static void GZIPCompress(std::string &oTileBuffer)
972✔
4542
{
4543
    if (!oTileBuffer.empty())
972✔
4544
    {
4545
        const CPLString osTmpFilename(
4546
            VSIMemGenerateHiddenFilename("mvt_temp.gz"));
1,944✔
4547
        CPLString osTmpGZipFilename("/vsigzip/" + osTmpFilename);
1,944✔
4548
        VSILFILE *fpGZip = VSIFOpenL(osTmpGZipFilename, "wb");
972✔
4549
        if (fpGZip)
972✔
4550
        {
4551
            VSIFWriteL(oTileBuffer.data(), 1, oTileBuffer.size(), fpGZip);
972✔
4552
            VSIFCloseL(fpGZip);
972✔
4553

4554
            vsi_l_offset nCompressedSize = 0;
972✔
4555
            GByte *pabyCompressed =
4556
                VSIGetMemFileBuffer(osTmpFilename, &nCompressedSize, false);
972✔
4557
            oTileBuffer.assign(reinterpret_cast<char *>(pabyCompressed),
4558
                               static_cast<size_t>(nCompressedSize));
972✔
4559
        }
4560
        VSIUnlink(osTmpFilename);
972✔
4561
    }
4562
}
972✔
4563

4564
/************************************************************************/
4565
/*                     GetReducedPrecisionGeometry()                    */
4566
/************************************************************************/
4567

4568
static std::vector<GUInt32>
4569
GetReducedPrecisionGeometry(MVTTileLayerFeature::GeomType eGeomType,
167✔
4570
                            const std::vector<GUInt32> &anSrcGeometry,
4571
                            GUInt32 nSrcExtent, GUInt32 nDstExtent)
4572
{
4573
    std::vector<GUInt32> anDstGeometry;
167✔
4574
    size_t nLastMoveToIdx = 0;
167✔
4575
    int nX = 0;
167✔
4576
    int nY = 0;
167✔
4577
    int nFirstReducedX = 0;
167✔
4578
    int nFirstReducedY = 0;
167✔
4579
    int nLastReducedX = 0;
167✔
4580
    int nLastReducedY = 0;
167✔
4581
    int nLastReducedXValid = 0;
167✔
4582
    int nLastReducedYValid = 0;
167✔
4583
    std::unique_ptr<OGRLinearRing> poInRing;
167✔
4584
    std::unique_ptr<OGRLinearRing> poOutRing;
167✔
4585
    std::unique_ptr<OGRLinearRing> poOutOuterRing;
167✔
4586
    bool bDiscardInnerRings = false;
167✔
4587
    const bool bIsPoly = eGeomType == MVTTileLayerFeature::GeomType::POLYGON;
167✔
4588
    for (size_t iSrc = 0; iSrc < anSrcGeometry.size();)
506✔
4589
    {
4590
        const unsigned nCount = GetCmdCount(anSrcGeometry[iSrc]);
339✔
4591
        switch (GetCmdId(anSrcGeometry[iSrc]))
339✔
4592
        {
4593
            case knCMD_MOVETO:
185✔
4594
            {
4595
                nLastMoveToIdx = anDstGeometry.size();
185✔
4596

4597
                anDstGeometry.push_back(anSrcGeometry[iSrc]);
185✔
4598
                iSrc++;
185✔
4599

4600
                unsigned nDstPoints = 0;
185✔
4601
                for (unsigned j = 0;
185✔
4602
                     iSrc + 1 < anSrcGeometry.size() && j < nCount;
370✔
4603
                     j++, iSrc += 2)
185✔
4604
                {
4605
                    nX += DecodeSInt(anSrcGeometry[iSrc]);
185✔
4606
                    nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
185✔
4607

4608
                    int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
185✔
4609
                                                     nDstExtent / nSrcExtent);
185✔
4610
                    int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
185✔
4611
                                                     nDstExtent / nSrcExtent);
185✔
4612
                    int nDiffX = nReducedX - nLastReducedX;
185✔
4613
                    int nDiffY = nReducedY - nLastReducedY;
185✔
4614
                    if (j == 0)
185✔
4615
                    {
4616
                        if (bIsPoly)
185✔
4617
                        {
4618
                            poInRing = std::unique_ptr<OGRLinearRing>(
82✔
4619
                                new OGRLinearRing());
82✔
4620
                            poOutRing = std::unique_ptr<OGRLinearRing>(
82✔
4621
                                new OGRLinearRing());
82✔
4622
                        }
4623
                        nFirstReducedX = nReducedX;
185✔
4624
                        nFirstReducedY = nReducedY;
185✔
4625
                    }
4626
                    if (j == 0 || nDiffX != 0 || nDiffY != 0)
185✔
4627
                    {
4628
                        if (bIsPoly)
185✔
4629
                        {
4630
                            poInRing->addPoint(nX, nY);
41✔
4631
                            poOutRing->addPoint(nReducedX, nReducedY);
41✔
4632
                        }
4633
                        nDstPoints++;
185✔
4634
                        anDstGeometry.push_back(EncodeSInt(nDiffX));
185✔
4635
                        anDstGeometry.push_back(EncodeSInt(nDiffY));
185✔
4636
                        nLastReducedX = nReducedX;
185✔
4637
                        nLastReducedY = nReducedY;
185✔
4638
                    }
4639
                }
4640
                // Patch count of MOVETO
4641
                anDstGeometry[nLastMoveToIdx] = GetCmdCountCombined(
185✔
4642
                    GetCmdId(anDstGeometry[nLastMoveToIdx]), nDstPoints);
185✔
4643
                break;
185✔
4644
            }
4645
            case knCMD_LINETO:
113✔
4646
            {
4647
                size_t nIdxToPatch = anDstGeometry.size();
113✔
4648
                anDstGeometry.push_back(anSrcGeometry[iSrc]);
113✔
4649
                iSrc++;
113✔
4650
                unsigned nDstPoints = 0;
113✔
4651
                int nLastReducedXBefore = nLastReducedX;
113✔
4652
                int nLastReducedYBefore = nLastReducedY;
113✔
4653
                for (unsigned j = 0;
113✔
4654
                     iSrc + 1 < anSrcGeometry.size() && j < nCount;
267✔
4655
                     j++, iSrc += 2)
154✔
4656
                {
4657
                    nX += DecodeSInt(anSrcGeometry[iSrc]);
154✔
4658
                    nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
154✔
4659

4660
                    int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
154✔
4661
                                                     nDstExtent / nSrcExtent);
154✔
4662
                    int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
154✔
4663
                                                     nDstExtent / nSrcExtent);
154✔
4664
                    int nDiffX = nReducedX - nLastReducedX;
154✔
4665
                    int nDiffY = nReducedY - nLastReducedY;
154✔
4666
                    if (nDiffX != 0 || nDiffY != 0)
154✔
4667
                    {
4668
                        if (bIsPoly)
114✔
4669
                        {
4670
                            CPLAssert(poInRing);
60✔
4671
                            CPLAssert(poOutRing);
60✔
4672
                            poInRing->addPoint(nX, nY);
60✔
4673
                            poOutRing->addPoint(nReducedX, nReducedY);
60✔
4674
                        }
4675
                        nDstPoints++;
114✔
4676
                        anDstGeometry.push_back(EncodeSInt(nDiffX));
114✔
4677
                        anDstGeometry.push_back(EncodeSInt(nDiffY));
114✔
4678
                        nLastReducedXBefore = nLastReducedX;
114✔
4679
                        nLastReducedYBefore = nLastReducedY;
114✔
4680
                        nLastReducedX = nReducedX;
114✔
4681
                        nLastReducedY = nReducedY;
114✔
4682
                    }
4683
                }
4684

4685
                // If last point of ring is identical to first one, discard it
4686
                if (nDstPoints > 0 && bIsPoly &&
113✔
4687
                    nLastReducedX == nFirstReducedX &&
1✔
4688
                    nLastReducedY == nFirstReducedY)
4689
                {
4690
                    nLastReducedX = nLastReducedXBefore;
×
4691
                    nLastReducedY = nLastReducedYBefore;
×
4692
                    nDstPoints -= 1;
×
4693
                    anDstGeometry.resize(anDstGeometry.size() - 2);
×
4694
                    poOutRing->setNumPoints(poOutRing->getNumPoints() - 1);
×
4695
                }
4696

4697
                // Patch count of LINETO
4698
                anDstGeometry[nIdxToPatch] = GetCmdCountCombined(
113✔
4699
                    GetCmdId(anDstGeometry[nIdxToPatch]), nDstPoints);
113✔
4700

4701
                // A valid linestring should have at least one MOVETO +
4702
                // one coord pair + one LINETO + one coord pair
4703
                if (eGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
113✔
4704
                {
4705
                    if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2)
72✔
4706
                    {
4707
                        // Remove last linestring
4708
                        nLastReducedX = nLastReducedXValid;
18✔
4709
                        nLastReducedY = nLastReducedYValid;
18✔
4710
                        anDstGeometry.resize(nLastMoveToIdx);
18✔
4711
                    }
4712
                    else
4713
                    {
4714
                        nLastReducedXValid = nLastReducedX;
54✔
4715
                        nLastReducedYValid = nLastReducedY;
54✔
4716
                    }
4717
                }
4718

4719
                break;
113✔
4720
            }
4721
            case knCMD_CLOSEPATH:
41✔
4722
            {
4723
                CPLAssert(bIsPoly);
41✔
4724
                CPLAssert(poInRing);
41✔
4725
                CPLAssert(poOutRing);
41✔
4726
                int bIsValid = true;
41✔
4727

4728
                // A valid ring should have at least one MOVETO + one
4729
                // coord pair + one LINETO + two coord pairs
4730
                if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2 * 2)
41✔
4731
                {
4732
                    // Remove ring. Normally if we remove an outer ring,
4733
                    // its inner rings should also be removed, given they are
4734
                    // smaller than the outer ring.
4735
                    bIsValid = false;
14✔
4736
                }
4737
                else
4738
                {
4739
                    poInRing->closeRings();
27✔
4740
                    poOutRing->closeRings();
27✔
4741
                    bool bIsOuterRing = !poInRing->isClockwise();
27✔
4742
                    // Normally the first ring of a polygon geometry should
4743
                    // be a outer ring, except when it is degenerate enough
4744
                    // in which case poOutOuterRing might be null.
4745
                    if (bIsOuterRing)
27✔
4746
                    {
4747
                        // if the outer ring turned out to be a inner ring
4748
                        // once reduced
4749
                        if (poOutRing->isClockwise())
18✔
4750
                        {
4751
                            bIsValid = false;
×
4752
                            bDiscardInnerRings = true;
×
4753
                        }
4754
                        else
4755
                        {
4756
                            OGRPolygon oPoly;
18✔
4757
                            oPoly.addRing(poOutRing.get());
18✔
4758
                            poOutOuterRing = std::unique_ptr<OGRLinearRing>(
36✔
4759
                                poOutRing.release());
18✔
4760
                            {
4761
                                CPLErrorStateBackuper oErrorStateBackuper(
4762
                                    CPLQuietErrorHandler);
18✔
4763
                                bIsValid = oPoly.IsValid();
18✔
4764
                            }
4765
                            bDiscardInnerRings = !bIsValid;
18✔
4766
                        }
4767
                    }
4768
                    else if (bDiscardInnerRings ||
9✔
4769
                             poOutOuterRing.get() == nullptr ||
18✔
4770
                             // if the inner ring turned out to be a outer ring
4771
                             // once reduced
4772
                             !poOutRing->isClockwise())
9✔
4773
                    {
4774
                        bIsValid = false;
×
4775
                    }
4776
                    else
4777
                    {
4778
                        OGRPolygon oPoly;
18✔
4779
                        oPoly.addRing(poOutOuterRing.get());
9✔
4780
                        oPoly.addRingDirectly(poOutRing.release());
9✔
4781
                        {
4782
                            CPLErrorStateBackuper oErrorStateBackuper(
4783
                                CPLQuietErrorHandler);
9✔
4784
                            bIsValid = oPoly.IsValid();
9✔
4785
                        }
4786
                    }
4787
                }
4788

4789
                if (bIsValid)
41✔
4790
                {
4791
                    nLastReducedXValid = nLastReducedX;
24✔
4792
                    nLastReducedYValid = nLastReducedY;
24✔
4793
                    anDstGeometry.push_back(anSrcGeometry[iSrc]);
24✔
4794
                }
4795
                else
4796
                {
4797
                    // Remove this ring
4798
                    nLastReducedX = nLastReducedXValid;
17✔
4799
                    nLastReducedY = nLastReducedYValid;
17✔
4800
                    anDstGeometry.resize(nLastMoveToIdx);
17✔
4801
                }
4802

4803
                iSrc++;
41✔
4804
                break;
41✔
4805
            }
4806
            default:
×
4807
            {
4808
                CPLAssert(false);
×
4809
                break;
4810
            }
4811
        }
4812
    }
4813

4814
    return anDstGeometry;
334✔
4815
}
4816

4817
/************************************************************************/
4818
/*                          EncodeFeature()                             */
4819
/************************************************************************/
4820

4821
void OGRMVTWriterDataset::EncodeFeature(
1,700✔
4822
    const void *pabyBlob, int nBlobSize,
4823
    std::shared_ptr<MVTTileLayer> &poTargetLayer,
4824
    std::map<CPLString, GUInt32> &oMapKeyToIdx,
4825
    std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
4826
    MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
4827
    unsigned &nFeaturesInTile)
4828
{
4829
    size_t nUncompressedSize = 0;
1,700✔
4830
    void *pCompressed =
4831
        CPLZLibInflate(pabyBlob, nBlobSize, nullptr, 0, &nUncompressedSize);
1,700✔
4832
    GByte *pabyUncompressed = static_cast<GByte *>(pCompressed);
1,700✔
4833

4834
    MVTTileLayer oSrcTileLayer;
3,400✔
4835
    if (nUncompressedSize &&
1,700✔
4836
        oSrcTileLayer.read(pabyUncompressed,
1,700✔
4837
                           pabyUncompressed + nUncompressedSize))
1,700✔
4838
    {
4839
        const auto &srcFeatures = oSrcTileLayer.getFeatures();
1,700✔
4840
        if (srcFeatures.size() == 1)  // should always be true !
1,700✔
4841
        {
4842
            const auto &poSrcFeature = srcFeatures[0];
1,700✔
4843
            std::shared_ptr<MVTTileLayerFeature> poFeature(
4844
                new MVTTileLayerFeature());
3,400✔
4845

4846
            if (poSrcFeature->hasId())
1,700✔
4847
                poFeature->setId(poSrcFeature->getId());
53✔
4848
            poFeature->setType(poSrcFeature->getType());
1,700✔
4849
            if (poLayerProperties)
1,700✔
4850
            {
4851
                poLayerProperties->m_oCountGeomType[poSrcFeature->getType()]++;
1,521✔
4852
            }
4853
            bool bOK = true;
1,700✔
4854
            if (nExtent < m_nExtent)
1,700✔
4855
            {
4856
#ifdef for_debugging
4857
                const auto &srcKeys = oSrcTileLayer.getKeys();
4858
                const auto &srcValues = oSrcTileLayer.getValues();
4859
                const auto &anSrcTags = poSrcFeature->getTags();
4860
                for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
4861
                {
4862
                    GUInt32 nSrcIdxKey = anSrcTags[i];
4863
                    GUInt32 nSrcIdxValue = anSrcTags[i + 1];
4864
                    if (nSrcIdxKey < srcKeys.size() &&
4865
                        nSrcIdxValue < srcValues.size())
4866
                    {
4867
                        auto &osKey = srcKeys[nSrcIdxKey];
4868
                        auto &oValue = srcValues[nSrcIdxValue];
4869
                        if (osKey == "tunnus" &&
4870
                            oValue.getUIntValue() == 28799760)
4871
                        {
4872
                            printf("foo\n"); /* ok */
4873
                            break;
4874
                        }
4875
                    }
4876
                }
4877
#endif
4878

4879
                poFeature->setGeometry(GetReducedPrecisionGeometry(
167✔
4880
                    poSrcFeature->getType(), poSrcFeature->getGeometry(),
4881
                    m_nExtent, nExtent));
4882
                if (poFeature->getGeometry().empty())
167✔
4883
                {
4884
                    bOK = false;
23✔
4885
                }
4886
            }
4887
            else
4888
            {
4889
                poFeature->setGeometry(poSrcFeature->getGeometry());
1,533✔
4890
            }
4891
            if (bOK)
1,700✔
4892
            {
4893
                const auto &srcKeys = oSrcTileLayer.getKeys();
1,677✔
4894
                for (const auto &osKey : srcKeys)
6,120✔
4895
                {
4896
                    auto oIter = oMapKeyToIdx.find(osKey);
4,443✔
4897
                    if (oIter == oMapKeyToIdx.end())
4,443✔
4898
                    {
4899
                        oMapKeyToIdx[osKey] = poTargetLayer->addKey(osKey);
3,645✔
4900
                    }
4901
                }
4902

4903
                const auto &srcValues = oSrcTileLayer.getValues();
1,677✔
4904
                for (const auto &oValue : srcValues)
6,120✔
4905
                {
4906
                    auto oIter = oMapValueToIdx.find(oValue);
4,443✔
4907
                    if (oIter == oMapValueToIdx.end())
4,443✔
4908
                    {
4909
                        oMapValueToIdx[oValue] =
3,771✔
4910
                            poTargetLayer->addValue(oValue);
3,771✔
4911
                    }
4912
                }
4913

4914
                const auto &anSrcTags = poSrcFeature->getTags();
1,677✔
4915
                for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
6,120✔
4916
                {
4917
                    GUInt32 nSrcIdxKey = anSrcTags[i];
4,443✔
4918
                    GUInt32 nSrcIdxValue = anSrcTags[i + 1];
4,443✔
4919
                    if (nSrcIdxKey < srcKeys.size() &&
8,886✔
4920
                        nSrcIdxValue < srcValues.size())
4,443✔
4921
                    {
4922
                        const auto &osKey = srcKeys[nSrcIdxKey];
4,443✔
4923
                        const auto &oValue = srcValues[nSrcIdxValue];
4,443✔
4924

4925
                        if (poLayerProperties)
4,443✔
4926
                        {
4927
                            UpdateLayerProperties(poLayerProperties, osKey,
4,353✔
4928
                                                  oValue);
4929
                        }
4930

4931
                        poFeature->addTag(oMapKeyToIdx[osKey]);
4,443✔
4932
                        poFeature->addTag(oMapValueToIdx[oValue]);
4,443✔
4933
                    }
4934
                }
4935

4936
                nFeaturesInTile++;
1,677✔
4937
                poTargetLayer->addFeature(std::move(poFeature));
1,677✔
4938
            }
4939
        }
4940
    }
4941
    else
4942
    {
4943
        // Shouldn't fail
4944
        CPLError(CE_Failure, CPLE_AppDefined, "Deserialization failure");
×
4945
    }
4946

4947
    CPLFree(pabyUncompressed);
1,700✔
4948
}
1,700✔
4949

4950
/************************************************************************/
4951
/*                            EncodeTile()                              */
4952
/************************************************************************/
4953

4954
std::string OGRMVTWriterDataset::EncodeTile(
865✔
4955
    int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer, sqlite3_stmt *hStmtRows,
4956
    std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
4957
    std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead)
4958
{
4959
    MVTTile oTargetTile;
1,730✔
4960

4961
    sqlite3_bind_int(hStmtLayer, 1, nZ);
865✔
4962
    sqlite3_bind_int(hStmtLayer, 2, nX);
865✔
4963
    sqlite3_bind_int(hStmtLayer, 3, nY);
865✔
4964

4965
    unsigned nFeaturesInTile = 0;
865✔
4966
    const GIntBig nProgressStep =
4967
        std::max(static_cast<GIntBig>(1), m_nTempTiles / 10);
865✔
4968

4969
    while (nFeaturesInTile < m_nMaxFeatures &&
4,330✔
4970
           sqlite3_step(hStmtLayer) == SQLITE_ROW)
2,159✔
4971
    {
4972
        const char *pszLayerName =
4973
            reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
1,306✔
4974
        sqlite3_bind_int(hStmtRows, 1, nZ);
1,306✔
4975
        sqlite3_bind_int(hStmtRows, 2, nX);
1,306✔
4976
        sqlite3_bind_int(hStmtRows, 3, nY);
1,306✔
4977
        sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
1,306✔
4978

4979
        auto oIterMapLayerProps = oMapLayerProps.find(pszLayerName);
1,306✔
4980
        MVTLayerProperties *poLayerProperties = nullptr;
1,306✔
4981
        if (oIterMapLayerProps == oMapLayerProps.end())
1,306✔
4982
        {
4983
            if (oSetLayers.size() < knMAX_COUNT_LAYERS)
75✔
4984
            {
4985
                oSetLayers.insert(pszLayerName);
75✔
4986
                if (oMapLayerProps.size() < knMAX_REPORT_LAYERS)
75✔
4987
                {
4988
                    MVTLayerProperties props;
75✔
4989
                    props.m_nMinZoom = nZ;
75✔
4990
                    props.m_nMaxZoom = nZ;
75✔
4991
                    oMapLayerProps[pszLayerName] = std::move(props);
75✔
4992
                    poLayerProperties = &(oMapLayerProps[pszLayerName]);
75✔
4993
                }
4994
            }
4995
        }
4996
        else
4997
        {
4998
            poLayerProperties = &(oIterMapLayerProps->second);
1,231✔
4999
        }
5000
        if (poLayerProperties)
1,306✔
5001
        {
5002
            poLayerProperties->m_nMinZoom =
1,306✔
5003
                std::min(nZ, poLayerProperties->m_nMinZoom);
1,306✔
5004
            poLayerProperties->m_nMaxZoom =
1,306✔
5005
                std::max(nZ, poLayerProperties->m_nMaxZoom);
1,306✔
5006
        }
5007

5008
        std::shared_ptr<MVTTileLayer> poTargetLayer(new MVTTileLayer());
2,612✔
5009
        oTargetTile.addLayer(poTargetLayer);
1,306✔
5010
        poTargetLayer->setName(pszLayerName);
1,306✔
5011
        poTargetLayer->setVersion(m_nMVTVersion);
1,306✔
5012
        poTargetLayer->setExtent(m_nExtent);
1,306✔
5013

5014
        std::map<CPLString, GUInt32> oMapKeyToIdx;
2,612✔
5015
        std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
2,612✔
5016

5017
        while (nFeaturesInTile < m_nMaxFeatures &&
5,642✔
5018
               sqlite3_step(hStmtRows) == SQLITE_ROW)
2,815✔
5019
        {
5020
            int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
1,521✔
5021
            const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
1,521✔
5022

5023
            EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
1,521✔
5024
                          oMapValueToIdx, poLayerProperties, m_nExtent,
5025
                          nFeaturesInTile);
5026

5027
            nTempTilesRead++;
1,521✔
5028
            if (nTempTilesRead == m_nTempTiles ||
1,521✔
5029
                (nTempTilesRead % nProgressStep) == 0)
1,470✔
5030
            {
5031
                const int nPct =
529✔
5032
                    static_cast<int>((100 * nTempTilesRead) / m_nTempTiles);
529✔
5033
                CPLDebug("MVT", "%d%%...", nPct);
529✔
5034
            }
5035
        }
5036
        sqlite3_reset(hStmtRows);
1,306✔
5037
    }
5038

5039
    sqlite3_reset(hStmtLayer);
865✔
5040

5041
    std::string oTileBuffer(oTargetTile.write());
1,730✔
5042
    size_t nSizeBefore = oTileBuffer.size();
865✔
5043
    if (m_bGZip)
865✔
5044
        GZIPCompress(oTileBuffer);
865✔
5045
    const size_t nSizeAfter = oTileBuffer.size();
865✔
5046
    const double dfCompressionRatio =
865✔
5047
        static_cast<double>(nSizeAfter) / nSizeBefore;
865✔
5048

5049
    const bool bTooManyFeatures = nFeaturesInTile >= m_nMaxFeatures;
865✔
5050
    if (bTooManyFeatures && !m_bMaxFeaturesOptSpecified)
865✔
5051
    {
5052
        m_bMaxFeaturesOptSpecified = true;
1✔
5053
        CPLError(CE_Warning, CPLE_AppDefined,
1✔
5054
                 "At least one tile exceeded the default maximum number of "
5055
                 "features per tile (%u) and was truncated to satisfy it.",
5056
                 m_nMaxFeatures);
5057
    }
5058

5059
    // If the tile size is above the allowed values or there are too many
5060
    // features, then sort by descending area / length until we get to the
5061
    // limit.
5062
    bool bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
865✔
5063
    if (bTooBigTile && !m_bMaxTileSizeOptSpecified)
865✔
5064
    {
5065
        m_bMaxTileSizeOptSpecified = true;
1✔
5066
        CPLError(CE_Warning, CPLE_AppDefined,
1✔
5067
                 "At least one tile exceeded the default maximum tile size of "
5068
                 "%u bytes and was encoded at lower resolution",
5069
                 m_nMaxTileSize);
5070
    }
5071

5072
    GUInt32 nExtent = m_nExtent;
865✔
5073
    while (bTooBigTile && !bTooManyFeatures && nExtent >= 256)
947✔
5074
    {
5075
        nExtent /= 2;
82✔
5076
        nSizeBefore = oTileBuffer.size();
82✔
5077
        oTileBuffer = RecodeTileLowerResolution(nZ, nX, nY, nExtent, hStmtLayer,
164✔
5078
                                                hStmtRows);
82✔
5079
        bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
82✔
5080
        CPLDebug("MVT",
82✔
5081
                 "Recoding tile %d/%d/%d with extent = %u. "
5082
                 "From %u to %u bytes",
5083
                 nZ, nX, nY, nExtent, static_cast<unsigned>(nSizeBefore),
5084
                 static_cast<unsigned>(oTileBuffer.size()));
82✔
5085
    }
5086

5087
    if (bTooBigTile || bTooManyFeatures)
865✔
5088
    {
5089
        if (bTooBigTile)
25✔
5090
        {
5091
            CPLDebug("MVT", "For tile %d/%d/%d, tile size is %u > %u", nZ, nX,
13✔
5092
                     nY, static_cast<unsigned>(oTileBuffer.size()),
13✔
5093
                     m_nMaxTileSize);
5094
        }
5095
        if (bTooManyFeatures)
25✔
5096
        {
5097
            CPLDebug("MVT",
12✔
5098
                     "For tile %d/%d/%d, feature count limit of %u is reached",
5099
                     nZ, nX, nY, m_nMaxFeatures);
5100
        }
5101

5102
        oTargetTile.clear();
25✔
5103

5104
        const unsigned nTotalFeaturesInTile =
5105
            std::min(m_nMaxFeatures, nFeaturesInTile);
25✔
5106
        char *pszSQL =
5107
            sqlite3_mprintf("SELECT layer, feature FROM temp "
25✔
5108
                            "WHERE z = %d AND x = %d AND y = %d ORDER BY "
5109
                            "area_or_length DESC LIMIT %d",
5110
                            nZ, nX, nY, nTotalFeaturesInTile);
5111
        sqlite3_stmt *hTmpStmt = nullptr;
25✔
5112
        CPL_IGNORE_RET_VAL(
25✔
5113
            sqlite3_prepare_v2(m_hDB, pszSQL, -1, &hTmpStmt, nullptr));
25✔
5114
        sqlite3_free(pszSQL);
25✔
5115
        if (!hTmpStmt)
25✔
5116
            return std::string();
×
5117

5118
        class TargetTileLayerProps
5119
        {
5120
          public:
5121
            std::shared_ptr<MVTTileLayer> m_poLayer;
5122
            std::map<CPLString, GUInt32> m_oMapKeyToIdx;
5123
            std::map<MVTTileLayerValue, GUInt32> m_oMapValueToIdx;
5124
        };
5125

5126
        std::map<std::string, TargetTileLayerProps> oMapLayerNameToTargetLayer;
50✔
5127

5128
        nFeaturesInTile = 0;
25✔
5129
        const unsigned nCheckStep = std::max(1U, nTotalFeaturesInTile / 100);
25✔
5130
        while (sqlite3_step(hTmpStmt) == SQLITE_ROW)
49✔
5131
        {
5132
            const char *pszLayerName = reinterpret_cast<const char *>(
5133
                sqlite3_column_text(hTmpStmt, 0));
37✔
5134
            int nBlobSize = sqlite3_column_bytes(hTmpStmt, 1);
37✔
5135
            const void *pabyBlob = sqlite3_column_blob(hTmpStmt, 1);
37✔
5136

5137
            std::shared_ptr<MVTTileLayer> poTargetLayer;
×
5138
            std::map<CPLString, GUInt32> *poMapKeyToIdx;
5139
            std::map<MVTTileLayerValue, GUInt32> *poMapValueToIdx;
5140
            auto oIter = oMapLayerNameToTargetLayer.find(pszLayerName);
37✔
5141
            if (oIter == oMapLayerNameToTargetLayer.end())
37✔
5142
            {
5143
                poTargetLayer =
5144
                    std::shared_ptr<MVTTileLayer>(new MVTTileLayer());
25✔
5145
                TargetTileLayerProps props;
25✔
5146
                props.m_poLayer = poTargetLayer;
25✔
5147
                oTargetTile.addLayer(poTargetLayer);
25✔
5148
                poTargetLayer->setName(pszLayerName);
25✔
5149
                poTargetLayer->setVersion(m_nMVTVersion);
25✔
5150
                poTargetLayer->setExtent(nExtent);
25✔
5151
                oMapLayerNameToTargetLayer[pszLayerName] = std::move(props);
25✔
5152
                poMapKeyToIdx =
25✔
5153
                    &oMapLayerNameToTargetLayer[pszLayerName].m_oMapKeyToIdx;
25✔
5154
                poMapValueToIdx =
25✔
5155
                    &oMapLayerNameToTargetLayer[pszLayerName].m_oMapValueToIdx;
25✔
5156
            }
5157
            else
5158
            {
5159
                poTargetLayer = oIter->second.m_poLayer;
12✔
5160
                poMapKeyToIdx = &oIter->second.m_oMapKeyToIdx;
12✔
5161
                poMapValueToIdx = &oIter->second.m_oMapValueToIdx;
12✔
5162
            }
5163

5164
            EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, *poMapKeyToIdx,
37✔
5165
                          *poMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
5166

5167
            if (nFeaturesInTile == nTotalFeaturesInTile ||
37✔
5168
                (bTooBigTile && (nFeaturesInTile % nCheckStep == 0)))
18✔
5169
            {
5170
                if (oTargetTile.getSize() * dfCompressionRatio > m_nMaxTileSize)
37✔
5171
                {
5172
                    break;
13✔
5173
                }
5174
            }
5175
        }
5176

5177
        oTileBuffer = oTargetTile.write();
25✔
5178
        if (m_bGZip)
25✔
5179
            GZIPCompress(oTileBuffer);
25✔
5180

5181
        if (bTooBigTile)
25✔
5182
        {
5183
            CPLDebug("MVT", "For tile %d/%d/%d, final tile size is %u", nZ, nX,
13✔
5184
                     nY, static_cast<unsigned>(oTileBuffer.size()));
13✔
5185
        }
5186

5187
        sqlite3_finalize(hTmpStmt);
25✔
5188
    }
5189

5190
    return oTileBuffer;
865✔
5191
}
5192

5193
/************************************************************************/
5194
/*                    RecodeTileLowerResolution()                       */
5195
/************************************************************************/
5196

5197
std::string OGRMVTWriterDataset::RecodeTileLowerResolution(
82✔
5198
    int nZ, int nX, int nY, int nExtent, sqlite3_stmt *hStmtLayer,
5199
    sqlite3_stmt *hStmtRows)
5200
{
5201
    MVTTile oTargetTile;
164✔
5202

5203
    sqlite3_bind_int(hStmtLayer, 1, nZ);
82✔
5204
    sqlite3_bind_int(hStmtLayer, 2, nX);
82✔
5205
    sqlite3_bind_int(hStmtLayer, 3, nY);
82✔
5206

5207
    unsigned nFeaturesInTile = 0;
82✔
5208
    while (nFeaturesInTile < m_nMaxFeatures &&
328✔
5209
           sqlite3_step(hStmtLayer) == SQLITE_ROW)
164✔
5210
    {
5211
        const char *pszLayerName =
5212
            reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
82✔
5213
        sqlite3_bind_int(hStmtRows, 1, nZ);
82✔
5214
        sqlite3_bind_int(hStmtRows, 2, nX);
82✔
5215
        sqlite3_bind_int(hStmtRows, 3, nY);
82✔
5216
        sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
82✔
5217

5218
        std::shared_ptr<MVTTileLayer> poTargetLayer(new MVTTileLayer());
164✔
5219
        oTargetTile.addLayer(poTargetLayer);
82✔
5220
        poTargetLayer->setName(pszLayerName);
82✔
5221
        poTargetLayer->setVersion(m_nMVTVersion);
82✔
5222
        poTargetLayer->setExtent(nExtent);
82✔
5223

5224
        std::map<CPLString, GUInt32> oMapKeyToIdx;
164✔
5225
        std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
164✔
5226

5227
        while (nFeaturesInTile < m_nMaxFeatures &&
448✔
5228
               sqlite3_step(hStmtRows) == SQLITE_ROW)
224✔
5229
        {
5230
            int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
142✔
5231
            const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
142✔
5232

5233
            EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
142✔
5234
                          oMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
5235
        }
5236
        sqlite3_reset(hStmtRows);
82✔
5237
    }
5238

5239
    sqlite3_reset(hStmtLayer);
82✔
5240

5241
    std::string oTileBuffer(oTargetTile.write());
82✔
5242
    if (m_bGZip)
82✔
5243
        GZIPCompress(oTileBuffer);
82✔
5244

5245
    return oTileBuffer;
164✔
5246
}
5247

5248
/************************************************************************/
5249
/*                            CreateOutput()                            */
5250
/************************************************************************/
5251

5252
bool OGRMVTWriterDataset::CreateOutput()
122✔
5253
{
5254
    if (m_bThreadPoolOK)
122✔
5255
        m_oThreadPool.WaitCompletion();
119✔
5256

5257
    std::map<CPLString, MVTLayerProperties> oMapLayerProps;
244✔
5258
    std::set<CPLString> oSetLayers;
244✔
5259

5260
    if (!m_oEnvelope.IsInit())
122✔
5261
    {
5262
        return GenerateMetadata(0, oMapLayerProps);
50✔
5263
    }
5264

5265
    CPLDebug("MVT", "Building output file from temporary database...");
72✔
5266

5267
    sqlite3_stmt *hStmtZXY = nullptr;
72✔
5268
    CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
72✔
5269
        m_hDB, "SELECT DISTINCT z, x, y FROM temp ORDER BY z, x, y", -1,
5270
        &hStmtZXY, nullptr));
5271
    if (hStmtZXY == nullptr)
72✔
5272
    {
5273
        CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
2✔
5274
        return false;
2✔
5275
    }
5276

5277
    sqlite3_stmt *hStmtLayer = nullptr;
70✔
5278
    CPL_IGNORE_RET_VAL(
70✔
5279
        sqlite3_prepare_v2(m_hDB,
70✔
5280
                           "SELECT DISTINCT layer FROM temp "
5281
                           "WHERE z = ? AND x = ? AND y = ? ORDER BY layer",
5282
                           -1, &hStmtLayer, nullptr));
5283
    if (hStmtLayer == nullptr)
70✔
5284
    {
5285
        CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
×
5286
        sqlite3_finalize(hStmtZXY);
×
5287
        return false;
×
5288
    }
5289
    sqlite3_stmt *hStmtRows = nullptr;
70✔
5290
    CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
70✔
5291
        m_hDB,
5292
        "SELECT feature FROM temp "
5293
        "WHERE z = ? AND x = ? AND y = ? AND layer = ? ORDER BY idx",
5294
        -1, &hStmtRows, nullptr));
5295
    if (hStmtRows == nullptr)
70✔
5296
    {
5297
        CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
×
5298
        sqlite3_finalize(hStmtZXY);
×
5299
        sqlite3_finalize(hStmtLayer);
×
5300
        return false;
×
5301
    }
5302

5303
    sqlite3_stmt *hInsertStmt = nullptr;
70✔
5304
    if (m_hDBMBTILES)
70✔
5305
    {
5306
        CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
43✔
5307
            m_hDBMBTILES,
5308
            "INSERT INTO tiles(zoom_level, tile_column, tile_row, "
5309
            "tile_data) VALUES (?,?,?,?)",
5310
            -1, &hInsertStmt, nullptr));
5311
        if (hInsertStmt == nullptr)
43✔
5312
        {
5313
            CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
×
5314
            sqlite3_finalize(hStmtZXY);
×
5315
            sqlite3_finalize(hStmtLayer);
×
5316
            sqlite3_finalize(hStmtRows);
×
5317
            return false;
×
5318
        }
5319
    }
5320

5321
    int nLastZ = -1;
70✔
5322
    int nLastX = -1;
70✔
5323
    bool bRet = true;
70✔
5324
    GIntBig nTempTilesRead = 0;
70✔
5325

5326
    while (sqlite3_step(hStmtZXY) == SQLITE_ROW)
934✔
5327
    {
5328
        int nZ = sqlite3_column_int(hStmtZXY, 0);
865✔
5329
        int nX = sqlite3_column_int(hStmtZXY, 1);
865✔
5330
        int nY = sqlite3_column_int(hStmtZXY, 2);
865✔
5331

5332
        std::string oTileBuffer(EncodeTile(nZ, nX, nY, hStmtLayer, hStmtRows,
5333
                                           oMapLayerProps, oSetLayers,
5334
                                           nTempTilesRead));
865✔
5335

5336
        if (oTileBuffer.empty())
865✔
5337
        {
5338
            bRet = false;
×
5339
        }
5340
        else if (hInsertStmt)
865✔
5341
        {
5342
            sqlite3_bind_int(hInsertStmt, 1, nZ);
531✔
5343
            sqlite3_bind_int(hInsertStmt, 2, nX);
531✔
5344
            sqlite3_bind_int(hInsertStmt, 3, (1 << nZ) - 1 - nY);
531✔
5345
            sqlite3_bind_blob(hInsertStmt, 4, oTileBuffer.data(),
531✔
5346
                              static_cast<int>(oTileBuffer.size()),
531✔
5347
                              SQLITE_STATIC);
5348
            const int rc = sqlite3_step(hInsertStmt);
531✔
5349
            bRet = (rc == SQLITE_OK || rc == SQLITE_DONE);
531✔
5350
            sqlite3_reset(hInsertStmt);
531✔
5351
        }
5352
        else
5353
        {
5354
            const std::string osZDirname(CPLFormFilenameSafe(
5355
                GetDescription(), CPLSPrintf("%d", nZ), nullptr));
668✔
5356
            const std::string osXDirname(CPLFormFilenameSafe(
5357
                osZDirname.c_str(), CPLSPrintf("%d", nX), nullptr));
668✔
5358
            if (nZ != nLastZ)
334✔
5359
            {
5360
                VSIMkdir(osZDirname.c_str(), 0755);
113✔
5361
                nLastZ = nZ;
113✔
5362
                nLastX = -1;
113✔
5363
            }
5364
            if (nX != nLastX)
334✔
5365
            {
5366
                VSIMkdir(osXDirname.c_str(), 0755);
192✔
5367
                nLastX = nX;
192✔
5368
            }
5369
            const std::string osTileFilename(
5370
                CPLFormFilenameSafe(osXDirname.c_str(), CPLSPrintf("%d", nY),
5371
                                    m_osExtension.c_str()));
668✔
5372
            VSILFILE *fpOut = VSIFOpenL(osTileFilename.c_str(), "wb");
334✔
5373
            if (fpOut)
334✔
5374
            {
5375
                const size_t nRet = VSIFWriteL(oTileBuffer.data(), 1,
333✔
5376
                                               oTileBuffer.size(), fpOut);
5377
                bRet = (nRet == oTileBuffer.size());
333✔
5378
                VSIFCloseL(fpOut);
333✔
5379
            }
5380
            else
5381
            {
5382
                bRet = false;
1✔
5383
            }
5384
        }
5385

5386
        if (!bRet)
865✔
5387
        {
5388
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
5389
                     "Error while writing tile %d/%d/%d", nZ, nX, nY);
5390
            break;
1✔
5391
        }
5392
    }
5393
    sqlite3_finalize(hStmtZXY);
70✔
5394
    sqlite3_finalize(hStmtLayer);
70✔
5395
    sqlite3_finalize(hStmtRows);
70✔
5396
    if (hInsertStmt)
70✔
5397
        sqlite3_finalize(hInsertStmt);
43✔
5398

5399
    bRet &= GenerateMetadata(oSetLayers.size(), oMapLayerProps);
70✔
5400

5401
    return bRet;
70✔
5402
}
5403

5404
/************************************************************************/
5405
/*                     SphericalMercatorToLongLat()                     */
5406
/************************************************************************/
5407

5408
static void SphericalMercatorToLongLat(double *x, double *y)
232✔
5409
{
5410
    double lng = *x / kmSPHERICAL_RADIUS / M_PI * 180;
232✔
5411
    double lat =
5412
        2 * (atan(exp(*y / kmSPHERICAL_RADIUS)) - M_PI / 4) / M_PI * 180;
232✔
5413
    *x = lng;
232✔
5414
    *y = lat;
232✔
5415
}
232✔
5416

5417
/************************************************************************/
5418
/*                          WriteMetadataItem()                         */
5419
/************************************************************************/
5420

5421
template <class T>
5422
static bool WriteMetadataItemT(const char *pszKey, T value,
1,299✔
5423
                               const char *pszValueFormat, sqlite3 *hDBMBTILES,
5424
                               CPLJSONObject &oRoot)
5425
{
5426
    if (hDBMBTILES)
1,299✔
5427
    {
5428
        char *pszSQL;
5429

5430
        pszSQL = sqlite3_mprintf(
825✔
5431
            CPLSPrintf("INSERT INTO metadata(name, value) VALUES('%%q', '%s')",
5432
                       pszValueFormat),
5433
            pszKey, value);
5434
        OGRErr eErr = SQLCommand(hDBMBTILES, pszSQL);
825✔
5435
        sqlite3_free(pszSQL);
825✔
5436
        return eErr == OGRERR_NONE;
825✔
5437
    }
5438
    else
5439
    {
5440
        oRoot.Add(pszKey, value);
474✔
5441
        return true;
474✔
5442
    }
5443
}
5444

5445
/************************************************************************/
5446
/*                          WriteMetadataItem()                         */
5447
/************************************************************************/
5448

5449
static bool WriteMetadataItem(const char *pszKey, const char *pszValue,
919✔
5450
                              sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
5451
{
5452
    return WriteMetadataItemT(pszKey, pszValue, "%q", hDBMBTILES, oRoot);
919✔
5453
}
5454

5455
/************************************************************************/
5456
/*                          WriteMetadataItem()                         */
5457
/************************************************************************/
5458

5459
static bool WriteMetadataItem(const char *pszKey, int nValue,
368✔
5460
                              sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
5461
{
5462
    return WriteMetadataItemT(pszKey, nValue, "%d", hDBMBTILES, oRoot);
368✔
5463
}
5464

5465
/************************************************************************/
5466
/*                          WriteMetadataItem()                         */
5467
/************************************************************************/
5468

5469
static bool WriteMetadataItem(const char *pszKey, double dfValue,
12✔
5470
                              sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
5471
{
5472
    return WriteMetadataItemT(pszKey, dfValue, "%.17g", hDBMBTILES, oRoot);
12✔
5473
}
5474

5475
/************************************************************************/
5476
/*                          GenerateMetadata()                          */
5477
/************************************************************************/
5478

5479
bool OGRMVTWriterDataset::GenerateMetadata(
120✔
5480
    size_t nLayers, const std::map<CPLString, MVTLayerProperties> &oMap)
5481
{
5482
    CPLJSONDocument oDoc;
240✔
5483
    CPLJSONObject oRoot = oDoc.GetRoot();
240✔
5484

5485
    OGRSpatialReference oSRS_EPSG3857;
240✔
5486
    double dfTopXWebMercator;
5487
    double dfTopYWebMercator;
5488
    double dfTileDim0WebMercator;
5489
    InitWebMercatorTilingScheme(&oSRS_EPSG3857, dfTopXWebMercator,
120✔
5490
                                dfTopYWebMercator, dfTileDim0WebMercator);
5491
    const bool bIsStandardTilingScheme =
5492
        m_poSRS->IsSame(&oSRS_EPSG3857) && m_dfTopX == dfTopXWebMercator &&
236✔
5493
        m_dfTopY == dfTopYWebMercator && m_dfTileDim0 == dfTileDim0WebMercator;
236✔
5494
    if (bIsStandardTilingScheme)
120✔
5495
    {
5496
        SphericalMercatorToLongLat(&(m_oEnvelope.MinX), &(m_oEnvelope.MinY));
116✔
5497
        SphericalMercatorToLongLat(&(m_oEnvelope.MaxX), &(m_oEnvelope.MaxY));
116✔
5498
        m_oEnvelope.MinY = std::max(-85.0, m_oEnvelope.MinY);
116✔
5499
        m_oEnvelope.MaxY = std::min(85.0, m_oEnvelope.MaxY);
116✔
5500
    }
5501
    else
5502
    {
5503
        OGRSpatialReference oSRS_EPSG4326;
8✔
5504
        oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
4✔
5505
        oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4✔
5506
        OGRCoordinateTransformation *poCT =
5507
            OGRCreateCoordinateTransformation(m_poSRS, &oSRS_EPSG4326);
4✔
5508
        if (poCT)
4✔
5509
        {
5510
            OGRPoint oPoint1(m_oEnvelope.MinX, m_oEnvelope.MinY);
8✔
5511
            oPoint1.transform(poCT);
4✔
5512
            OGRPoint oPoint2(m_oEnvelope.MinX, m_oEnvelope.MaxY);
8✔
5513
            oPoint2.transform(poCT);
4✔
5514
            OGRPoint oPoint3(m_oEnvelope.MaxX, m_oEnvelope.MaxY);
8✔
5515
            oPoint3.transform(poCT);
4✔
5516
            OGRPoint oPoint4(m_oEnvelope.MaxX, m_oEnvelope.MinY);
8✔
5517
            oPoint4.transform(poCT);
4✔
5518
            m_oEnvelope.MinX =
4✔
5519
                std::min(std::min(oPoint1.getX(), oPoint2.getX()),
4✔
5520
                         std::min(oPoint3.getX(), oPoint4.getX()));
8✔
5521
            m_oEnvelope.MinY =
4✔
5522
                std::min(std::min(oPoint1.getY(), oPoint2.getY()),
4✔
5523
                         std::min(oPoint3.getY(), oPoint4.getY()));
8✔
5524
            m_oEnvelope.MaxX =
4✔
5525
                std::max(std::max(oPoint1.getX(), oPoint2.getX()),
4✔
5526
                         std::max(oPoint3.getX(), oPoint4.getX()));
8✔
5527
            m_oEnvelope.MaxY =
4✔
5528
                std::max(std::max(oPoint1.getY(), oPoint2.getY()),
4✔
5529
                         std::max(oPoint3.getY(), oPoint4.getY()));
8✔
5530
            delete poCT;
4✔
5531
        }
5532
    }
5533
    const double dfCenterX = (m_oEnvelope.MinX + m_oEnvelope.MaxX) / 2;
120✔
5534
    const double dfCenterY = (m_oEnvelope.MinY + m_oEnvelope.MaxY) / 2;
120✔
5535
    CPLString osCenter(
5536
        CPLSPrintf("%.7f,%.7f,%d", dfCenterX, dfCenterY, m_nMinZoom));
240✔
5537
    CPLString osBounds(CPLSPrintf("%.7f,%.7f,%.7f,%.7f", m_oEnvelope.MinX,
5538
                                  m_oEnvelope.MinY, m_oEnvelope.MaxX,
5539
                                  m_oEnvelope.MaxY));
240✔
5540

5541
    WriteMetadataItem("name", m_osName, m_hDBMBTILES, oRoot);
120✔
5542
    WriteMetadataItem("description", m_osDescription, m_hDBMBTILES, oRoot);
120✔
5543
    WriteMetadataItem("version", m_nMetadataVersion, m_hDBMBTILES, oRoot);
120✔
5544
    WriteMetadataItem("minzoom", m_nMinZoom, m_hDBMBTILES, oRoot);
120✔
5545
    WriteMetadataItem("maxzoom", m_nMaxZoom, m_hDBMBTILES, oRoot);
120✔
5546
    WriteMetadataItem("center", !m_osCenter.empty() ? m_osCenter : osCenter,
120✔
5547
                      m_hDBMBTILES, oRoot);
5548
    WriteMetadataItem("bounds", !m_osBounds.empty() ? m_osBounds : osBounds,
120✔
5549
                      m_hDBMBTILES, oRoot);
5550
    WriteMetadataItem("type", m_osType, m_hDBMBTILES, oRoot);
120✔
5551
    WriteMetadataItem("format", "pbf", m_hDBMBTILES, oRoot);
120✔
5552
    if (m_hDBMBTILES)
120✔
5553
    {
5554
        WriteMetadataItem("scheme", "tms", m_hDBMBTILES, oRoot);
75✔
5555
    }
5556

5557
    // GDAL extension for custom tiling schemes
5558
    if (!bIsStandardTilingScheme)
120✔
5559
    {
5560
        const char *pszAuthName = m_poSRS->GetAuthorityName(nullptr);
4✔
5561
        const char *pszAuthCode = m_poSRS->GetAuthorityCode(nullptr);
4✔
5562
        if (pszAuthName && pszAuthCode)
4✔
5563
        {
5564
            WriteMetadataItem("crs",
4✔
5565
                              CPLSPrintf("%s:%s", pszAuthName, pszAuthCode),
5566
                              m_hDBMBTILES, oRoot);
5567
        }
5568
        else
5569
        {
5570
            char *pszWKT = nullptr;
×
5571
            m_poSRS->exportToWkt(&pszWKT);
×
5572
            WriteMetadataItem("crs", pszWKT, m_hDBMBTILES, oRoot);
×
5573
            CPLFree(pszWKT);
×
5574
        }
5575
        WriteMetadataItem("tile_origin_upper_left_x", m_dfTopX, m_hDBMBTILES,
4✔
5576
                          oRoot);
5577
        WriteMetadataItem("tile_origin_upper_left_y", m_dfTopY, m_hDBMBTILES,
4✔
5578
                          oRoot);
5579
        WriteMetadataItem("tile_dimension_zoom_0", m_dfTileDim0, m_hDBMBTILES,
4✔
5580
                          oRoot);
5581
        WriteMetadataItem("tile_matrix_width_zoom_0", m_nTileMatrixWidth0,
4✔
5582
                          m_hDBMBTILES, oRoot);
5583
        WriteMetadataItem("tile_matrix_height_zoom_0", m_nTileMatrixHeight0,
4✔
5584
                          m_hDBMBTILES, oRoot);
5585
    }
5586

5587
    CPLJSONDocument oJsonDoc;
240✔
5588
    CPLJSONObject oJsonRoot = oJsonDoc.GetRoot();
240✔
5589

5590
    CPLJSONArray oVectorLayers;
240✔
5591
    oJsonRoot.Add("vector_layers", oVectorLayers);
120✔
5592
    std::set<std::string> oAlreadyVisited;
240✔
5593
    for (const auto &poLayer : m_apoLayers)
287✔
5594
    {
5595
        auto oIter = oMap.find(poLayer->m_osTargetName);
167✔
5596
        if (oIter != oMap.end() &&
242✔
5597
            oAlreadyVisited.find(poLayer->m_osTargetName) ==
75✔
5598
                oAlreadyVisited.end())
242✔
5599
        {
5600
            oAlreadyVisited.insert(poLayer->m_osTargetName);
75✔
5601

5602
            CPLJSONObject oLayerObj;
150✔
5603
            oLayerObj.Add("id", poLayer->m_osTargetName);
75✔
5604
            oLayerObj.Add("description",
75✔
5605
                          m_oMapLayerNameToDesc[poLayer->m_osTargetName]);
75✔
5606
            oLayerObj.Add("minzoom", oIter->second.m_nMinZoom);
75✔
5607
            oLayerObj.Add("maxzoom", oIter->second.m_nMaxZoom);
75✔
5608

5609
            CPLJSONObject oFields;
150✔
5610
            oLayerObj.Add("fields", oFields);
75✔
5611
            auto poFDefn = poLayer->GetLayerDefn();
75✔
5612
            for (int i = 0; i < poFDefn->GetFieldCount(); i++)
279✔
5613
            {
5614
                auto poFieldDefn = poFDefn->GetFieldDefn(i);
204✔
5615
                auto eType = poFieldDefn->GetType();
204✔
5616
                if (eType == OFTInteger &&
239✔
5617
                    poFieldDefn->GetSubType() == OFSTBoolean)
35✔
5618
                {
5619
                    oFields.Add(poFieldDefn->GetNameRef(), "Boolean");
1✔
5620
                }
5621
                else if (eType == OFTInteger || eType == OFTInteger64 ||
203✔
5622
                         eType == OFTReal)
5623
                {
5624
                    oFields.Add(poFieldDefn->GetNameRef(), "Number");
73✔
5625
                }
5626
                else
5627
                {
5628
                    oFields.Add(poFieldDefn->GetNameRef(), "String");
130✔
5629
                }
5630
            }
5631

5632
            oVectorLayers.Add(oLayerObj);
75✔
5633
        }
5634
    }
5635

5636
    CPLJSONObject oTileStats;
240✔
5637
    oJsonRoot.Add("tilestats", oTileStats);
120✔
5638
    oTileStats.Add("layerCount", static_cast<int>(nLayers));
120✔
5639
    CPLJSONArray oTileStatsLayers;
240✔
5640
    oTileStats.Add("layers", oTileStatsLayers);
120✔
5641
    oAlreadyVisited.clear();
120✔
5642
    for (const auto &poLayer : m_apoLayers)
287✔
5643
    {
5644
        auto oIter = oMap.find(poLayer->m_osTargetName);
167✔
5645
        if (oIter != oMap.end() &&
242✔
5646
            oAlreadyVisited.find(poLayer->m_osTargetName) ==
75✔
5647
                oAlreadyVisited.end())
242✔
5648
        {
5649
            oAlreadyVisited.insert(poLayer->m_osTargetName);
75✔
5650
            auto &oLayerProps = oIter->second;
75✔
5651
            CPLJSONObject oLayerObj;
150✔
5652

5653
            std::string osName(poLayer->m_osTargetName);
150✔
5654
            osName.resize(std::min(knMAX_LAYER_NAME_LENGTH, osName.size()));
75✔
5655
            oLayerObj.Add("layer", osName);
75✔
5656
            oLayerObj.Add(
75✔
5657
                "count",
5658
                m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]);
75✔
5659

5660
            // Find majority geometry type
5661
            MVTTileLayerFeature::GeomType eMaxGeomType =
75✔
5662
                MVTTileLayerFeature::GeomType::UNKNOWN;
5663
            GIntBig nMaxCountGeom = 0;
75✔
5664
            for (int i = static_cast<int>(MVTTileLayerFeature::GeomType::POINT);
300✔
5665
                 i <= static_cast<int>(MVTTileLayerFeature::GeomType::POLYGON);
300✔
5666
                 i++)
5667
            {
5668
                MVTTileLayerFeature::GeomType eGeomType =
225✔
5669
                    static_cast<MVTTileLayerFeature::GeomType>(i);
225✔
5670
                auto oIterCountGeom =
5671
                    oLayerProps.m_oCountGeomType.find(eGeomType);
225✔
5672
                if (oIterCountGeom != oLayerProps.m_oCountGeomType.end())
225✔
5673
                {
5674
                    if (oIterCountGeom->second >= nMaxCountGeom)
79✔
5675
                    {
5676
                        eMaxGeomType = eGeomType;
78✔
5677
                        nMaxCountGeom = oIterCountGeom->second;
78✔
5678
                    }
5679
                }
5680
            }
5681
            if (eMaxGeomType == MVTTileLayerFeature::GeomType::POINT)
75✔
5682
                oLayerObj.Add("geometry", "Point");
63✔
5683
            else if (eMaxGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
12✔
5684
                oLayerObj.Add("geometry", "LineString");
6✔
5685
            else if (eMaxGeomType == MVTTileLayerFeature::GeomType::POLYGON)
6✔
5686
                oLayerObj.Add("geometry", "Polygon");
6✔
5687

5688
            oLayerObj.Add("attributeCount",
75✔
5689
                          static_cast<int>(oLayerProps.m_oSetFields.size()));
75✔
5690
            CPLJSONArray oAttributes;
150✔
5691
            oLayerObj.Add("attributes", oAttributes);
75✔
5692
            for (const auto &oFieldProps : oLayerProps.m_aoFields)
256✔
5693
            {
5694
                CPLJSONObject oFieldObj;
362✔
5695
                oAttributes.Add(oFieldObj);
181✔
5696
                std::string osFieldNameTruncated(oFieldProps.m_osName);
362✔
5697
                osFieldNameTruncated.resize(std::min(
181✔
5698
                    knMAX_FIELD_NAME_LENGTH, osFieldNameTruncated.size()));
181✔
5699
                oFieldObj.Add("attribute", osFieldNameTruncated);
181✔
5700
                oFieldObj.Add("count", static_cast<int>(
181✔
5701
                                           oFieldProps.m_oSetAllValues.size()));
181✔
5702
                oFieldObj.Add("type",
181✔
5703
                              oFieldProps.m_eType ==
181✔
5704
                                      MVTTileLayerValue::ValueType::DOUBLE
5705
                                  ? "number"
5706
                              : oFieldProps.m_eType ==
108✔
5707
                                      MVTTileLayerValue::ValueType::STRING
5708
                                  ? "string"
108✔
5709
                                  : "boolean");
5710

5711
                CPLJSONArray oValues;
362✔
5712
                oFieldObj.Add("values", oValues);
181✔
5713
                for (const auto &oIterValue : oFieldProps.m_oSetValues)
408✔
5714
                {
5715
                    if (oIterValue.getType() ==
227✔
5716
                        MVTTileLayerValue::ValueType::BOOL)
5717
                    {
5718
                        oValues.Add(oIterValue.getBoolValue());
1✔
5719
                    }
5720
                    else if (oIterValue.isNumeric())
226✔
5721
                    {
5722
                        if (oFieldProps.m_bAllInt)
104✔
5723
                        {
5724
                            oValues.Add(static_cast<GInt64>(
53✔
5725
                                oIterValue.getNumericValue()));
53✔
5726
                        }
5727
                        else
5728
                        {
5729
                            oValues.Add(oIterValue.getNumericValue());
51✔
5730
                        }
5731
                    }
5732
                    else if (oIterValue.isString())
122✔
5733
                    {
5734
                        oValues.Add(oIterValue.getStringValue());
122✔
5735
                    }
5736
                }
5737

5738
                if (oFieldProps.m_eType == MVTTileLayerValue::ValueType::DOUBLE)
181✔
5739
                {
5740
                    if (oFieldProps.m_bAllInt)
73✔
5741
                    {
5742
                        oFieldObj.Add(
37✔
5743
                            "min", static_cast<GInt64>(oFieldProps.m_dfMinVal));
37✔
5744
                        oFieldObj.Add(
37✔
5745
                            "max", static_cast<GInt64>(oFieldProps.m_dfMaxVal));
37✔
5746
                    }
5747
                    else
5748
                    {
5749
                        oFieldObj.Add("min", oFieldProps.m_dfMinVal);
36✔
5750
                        oFieldObj.Add("max", oFieldProps.m_dfMaxVal);
36✔
5751
                    }
5752
                }
5753
            }
5754

5755
            oTileStatsLayers.Add(oLayerObj);
75✔
5756
        }
5757
    }
5758

5759
    WriteMetadataItem("json", oJsonDoc.SaveAsString().c_str(), m_hDBMBTILES,
120✔
5760
                      oRoot);
5761

5762
    if (m_hDBMBTILES)
120✔
5763
    {
5764
        return true;
75✔
5765
    }
5766

5767
    return oDoc.Save(
45✔
5768
        CPLFormFilenameSafe(GetDescription(), "metadata.json", nullptr));
90✔
5769
}
5770

5771
/************************************************************************/
5772
/*                            WriteFeature()                            */
5773
/************************************************************************/
5774

5775
OGRErr OGRMVTWriterDataset::WriteFeature(OGRMVTWriterLayer *poLayer,
250✔
5776
                                         OGRFeature *poFeature, GIntBig nSerial,
5777
                                         OGRGeometry *poGeom)
5778
{
5779
    if (poFeature->GetGeometryRef() == poGeom)
250✔
5780
    {
5781
        m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]++;
192✔
5782
    }
5783

5784
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
250✔
5785
    if (eGeomType == wkbGeometryCollection)
250✔
5786
    {
5787
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
21✔
5788
        for (int i = 0; i < poGC->getNumGeometries(); i++)
77✔
5789
        {
5790
            if (WriteFeature(poLayer, poFeature, nSerial,
58✔
5791
                             poGC->getGeometryRef(i)) != OGRERR_NONE)
58✔
5792
            {
5793
                return OGRERR_FAILURE;
2✔
5794
            }
5795
        }
5796
        return OGRERR_NONE;
19✔
5797
    }
5798

5799
    OGREnvelope sExtent;
229✔
5800
    poGeom->getEnvelope(&sExtent);
229✔
5801

5802
    if (!m_oEnvelope.IsInit())
229✔
5803
    {
5804
        CPLDebug("MVT", "Creating temporary database...");
72✔
5805
    }
5806

5807
    m_oEnvelope.Merge(sExtent);
229✔
5808

5809
    if (!m_bReuseTempFile)
229✔
5810
    {
5811
        auto poFeatureContent =
5812
            std::shared_ptr<OGRMVTFeatureContent>(new OGRMVTFeatureContent());
228✔
5813
        auto poSharedGeom = std::shared_ptr<OGRGeometry>(poGeom->clone());
228✔
5814

5815
        poFeatureContent->nFID = poFeature->GetFID();
228✔
5816

5817
        const OGRFeatureDefn *poFDefn = poFeature->GetDefnRef();
228✔
5818
        for (int i = 0; i < poFeature->GetFieldCount(); i++)
995✔
5819
        {
5820
            if (poFeature->IsFieldSetAndNotNull(i))
767✔
5821
            {
5822
                MVTTileLayerValue oValue;
666✔
5823
                const OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(i);
666✔
5824
                OGRFieldType eFieldType = poFieldDefn->GetType();
666✔
5825
                if (eFieldType == OFTInteger || eFieldType == OFTInteger64)
666✔
5826
                {
5827
                    if (poFieldDefn->GetSubType() == OFSTBoolean)
145✔
5828
                    {
5829
                        oValue.setBoolValue(poFeature->GetFieldAsInteger(i) !=
2✔
5830
                                            0);
5831
                    }
5832
                    else
5833
                    {
5834
                        oValue.setValue(poFeature->GetFieldAsInteger64(i));
143✔
5835
                    }
5836
                }
5837
                else if (eFieldType == OFTReal)
521✔
5838
                {
5839
                    oValue.setValue(poFeature->GetFieldAsDouble(i));
140✔
5840
                }
5841
                else if (eFieldType == OFTDate || eFieldType == OFTDateTime)
381✔
5842
                {
5843
                    int nYear, nMonth, nDay, nHour, nMin, nTZ;
5844
                    float fSec;
5845
                    poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay,
238✔
5846
                                                  &nHour, &nMin, &fSec, &nTZ);
5847
                    CPLString osFormatted;
476✔
5848
                    if (eFieldType == OFTDate)
238✔
5849
                    {
5850
                        osFormatted.Printf("%04d-%02d-%02d", nYear, nMonth,
119✔
5851
                                           nDay);
119✔
5852
                    }
5853
                    else
5854
                    {
5855
                        char *pszFormatted =
5856
                            OGRGetXMLDateTime(poFeature->GetRawFieldRef(i));
119✔
5857
                        osFormatted = pszFormatted;
119✔
5858
                        CPLFree(pszFormatted);
119✔
5859
                    }
5860
                    oValue.setStringValue(osFormatted);
476✔
5861
                }
5862
                else
5863
                {
5864
                    oValue.setStringValue(
143✔
5865
                        std::string(poFeature->GetFieldAsString(i)));
286✔
5866
                }
5867

5868
                poFeatureContent->oValues.emplace_back(
666✔
5869
                    std::pair<std::string, MVTTileLayerValue>(
666✔
5870
                        poFieldDefn->GetNameRef(), oValue));
1,332✔
5871
            }
5872
        }
5873

5874
        for (int nZ = poLayer->m_nMinZoom; nZ <= poLayer->m_nMaxZoom; nZ++)
1,558✔
5875
        {
5876
            double dfTileDim = m_dfTileDim0 / (1 << nZ);
1,332✔
5877
            double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
1,332✔
5878
            const int nTileMinX = std::max(
5879
                0, static_cast<int>((sExtent.MinX - m_dfTopX - dfBuffer) /
2,664✔
5880
                                    dfTileDim));
1,332✔
5881
            const int nTileMinY = std::max(
5882
                0, static_cast<int>((m_dfTopY - sExtent.MaxY - dfBuffer) /
2,664✔
5883
                                    dfTileDim));
1,332✔
5884
            const int nTileMaxX =
5885
                std::min(static_cast<int>((sExtent.MaxX - m_dfTopX + dfBuffer) /
2,664✔
5886
                                          dfTileDim),
5887
                         static_cast<int>(std::min<int64_t>(
2,664✔
5888
                             INT_MAX, (static_cast<int64_t>(1) << nZ) *
2,664✔
5889
                                              m_nTileMatrixWidth0 -
2,664✔
5890
                                          1)));
1,332✔
5891
            const int nTileMaxY =
5892
                std::min(static_cast<int>((m_dfTopY - sExtent.MinY + dfBuffer) /
2,664✔
5893
                                          dfTileDim),
5894
                         static_cast<int>(std::min<int64_t>(
2,664✔
5895
                             INT_MAX, (static_cast<int64_t>(1) << nZ) *
2,664✔
5896
                                              m_nTileMatrixHeight0 -
2,664✔
5897
                                          1)));
1,332✔
5898
            for (int iX = nTileMinX; iX <= nTileMaxX; iX++)
3,574✔
5899
            {
5900
                for (int iY = nTileMinY; iY <= nTileMaxY; iY++)
6,275✔
5901
                {
5902
                    if (PreGenerateForTile(
8,066✔
5903
                            nZ, iX, iY, poLayer->m_osTargetName,
4,033✔
5904
                            (nZ == poLayer->m_nMaxZoom), poFeatureContent,
4,033✔
5905
                            nSerial, poSharedGeom, sExtent) != OGRERR_NONE)
4,033✔
5906
                    {
5907
                        return OGRERR_FAILURE;
2✔
5908
                    }
5909
                }
5910
            }
5911
        }
5912
    }
5913

5914
    return OGRERR_NONE;
227✔
5915
}
5916

5917
/************************************************************************/
5918
/*                            TestCapability()                          */
5919
/************************************************************************/
5920

5921
int OGRMVTWriterDataset::TestCapability(const char *pszCap)
204✔
5922
{
5923
    if (EQUAL(pszCap, ODsCCreateLayer))
204✔
5924
        return true;
116✔
5925
    return false;
88✔
5926
}
5927

5928
/************************************************************************/
5929
/*                         ValidateMinMaxZoom()                         */
5930
/************************************************************************/
5931

5932
static bool ValidateMinMaxZoom(int nMinZoom, int nMaxZoom)
299✔
5933
{
5934
    if (nMinZoom < 0 || nMinZoom > 22)
299✔
5935
    {
5936
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid MINZOOM");
2✔
5937
        return false;
2✔
5938
    }
5939
    if (nMaxZoom < 0 || nMaxZoom > 22)
297✔
5940
    {
5941
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM");
1✔
5942
        return false;
1✔
5943
    }
5944
    if (nMaxZoom < nMinZoom)
296✔
5945
    {
5946
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM < MINZOOM");
1✔
5947
        return false;
1✔
5948
    }
5949
    return true;
295✔
5950
}
5951

5952
/************************************************************************/
5953
/*                           ICreateLayer()                             */
5954
/************************************************************************/
5955

5956
OGRLayer *
5957
OGRMVTWriterDataset::ICreateLayer(const char *pszLayerName,
170✔
5958
                                  const OGRGeomFieldDefn *poGeomFieldDefn,
5959
                                  CSLConstList papszOptions)
5960
{
5961
    OGRSpatialReference *poSRSClone = nullptr;
170✔
5962
    const auto poSRS =
5963
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
170✔
5964
    if (poSRS)
170✔
5965
    {
5966
        poSRSClone = poSRS->Clone();
7✔
5967
        poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
7✔
5968
    }
5969
    OGRMVTWriterLayer *poLayer =
5970
        new OGRMVTWriterLayer(this, pszLayerName, poSRSClone);
170✔
5971
    if (poSRSClone)
170✔
5972
        poSRSClone->Release();
7✔
5973
    poLayer->m_nMinZoom = m_nMinZoom;
170✔
5974
    poLayer->m_nMaxZoom = m_nMaxZoom;
170✔
5975
    poLayer->m_osTargetName = pszLayerName;
170✔
5976

5977
    /*
5978

5979
            {
5980
                "src_layer":
5981
                    { "target_name": "",
5982
                      "description": "",
5983
                      "minzoom": 0,
5984
                      "maxzoom": 0
5985
                    }
5986
            }
5987
    */
5988

5989
    CPLJSONObject oObj = m_oConf.GetRoot().GetObj(pszLayerName);
510✔
5990
    CPLString osDescription;
340✔
5991
    if (oObj.IsValid())
170✔
5992
    {
5993
        std::string osTargetName = oObj.GetString("target_name");
4✔
5994
        if (!osTargetName.empty())
2✔
5995
            poLayer->m_osTargetName = std::move(osTargetName);
2✔
5996
        int nMinZoom = oObj.GetInteger("minzoom", -1);
2✔
5997
        if (nMinZoom >= 0)
2✔
5998
            poLayer->m_nMinZoom = nMinZoom;
2✔
5999
        int nMaxZoom = oObj.GetInteger("maxzoom", -1);
2✔
6000
        if (nMaxZoom >= 0)
2✔
6001
            poLayer->m_nMaxZoom = nMaxZoom;
2✔
6002
        osDescription = oObj.GetString("description");
2✔
6003
    }
6004

6005
    poLayer->m_nMinZoom = atoi(CSLFetchNameValueDef(
170✔
6006
        papszOptions, "MINZOOM", CPLSPrintf("%d", poLayer->m_nMinZoom)));
6007
    poLayer->m_nMaxZoom = atoi(CSLFetchNameValueDef(
170✔
6008
        papszOptions, "MAXZOOM", CPLSPrintf("%d", poLayer->m_nMaxZoom)));
6009
    if (!ValidateMinMaxZoom(poLayer->m_nMinZoom, poLayer->m_nMaxZoom))
170✔
6010
    {
6011
        delete poLayer;
1✔
6012
        return nullptr;
1✔
6013
    }
6014
    poLayer->m_osTargetName = CSLFetchNameValueDef(
6015
        papszOptions, "NAME", poLayer->m_osTargetName.c_str());
169✔
6016
    osDescription =
6017
        CSLFetchNameValueDef(papszOptions, "DESCRIPTION", osDescription);
169✔
6018
    if (!osDescription.empty())
169✔
6019
        m_oMapLayerNameToDesc[poLayer->m_osTargetName] =
4✔
6020
            std::move(osDescription);
2✔
6021

6022
    m_apoLayers.push_back(std::unique_ptr<OGRMVTWriterLayer>(poLayer));
169✔
6023
    return m_apoLayers.back().get();
169✔
6024
}
6025

6026
/************************************************************************/
6027
/*                                Create()                              */
6028
/************************************************************************/
6029

6030
GDALDataset *OGRMVTWriterDataset::Create(const char *pszFilename, int nXSize,
139✔
6031
                                         int nYSize, int nBandsIn,
6032
                                         GDALDataType eDT, char **papszOptions)
6033
{
6034
    if (nXSize != 0 || nYSize != 0 || nBandsIn != 0 || eDT != GDT_Unknown)
139✔
6035
    {
6036
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
6037
                 "Only vector creation supported");
6038
        return nullptr;
1✔
6039
    }
6040

6041
    const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
138✔
6042
    const bool bMBTILESExt =
6043
        EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "mbtiles");
138✔
6044
    if (pszFormat == nullptr && bMBTILESExt)
138✔
6045
    {
6046
        pszFormat = "MBTILES";
4✔
6047
    }
6048
    const bool bMBTILES = pszFormat != nullptr && EQUAL(pszFormat, "MBTILES");
138✔
6049

6050
    // For debug only
6051
    bool bReuseTempFile =
6052
        CPLTestBool(CPLGetConfigOption("OGR_MVT_REUSE_TEMP_FILE", "NO"));
138✔
6053

6054
    if (bMBTILES)
138✔
6055
    {
6056
        if (!bMBTILESExt)
80✔
6057
        {
6058
            CPLError(CE_Failure, CPLE_FileIO,
1✔
6059
                     "%s should have mbtiles extension", pszFilename);
6060
            return nullptr;
1✔
6061
        }
6062

6063
        VSIUnlink(pszFilename);
79✔
6064
    }
6065
    else
6066
    {
6067
        VSIStatBufL sStat;
6068
        if (VSIStatL(pszFilename, &sStat) == 0)
58✔
6069
        {
6070
            CPLError(CE_Failure, CPLE_FileIO, "%s already exists", pszFilename);
3✔
6071
            return nullptr;
5✔
6072
        }
6073

6074
        if (VSIMkdir(pszFilename, 0755) != 0)
55✔
6075
        {
6076
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
2✔
6077
                     pszFilename);
6078
            return nullptr;
2✔
6079
        }
6080
    }
6081

6082
    OGRMVTWriterDataset *poDS = new OGRMVTWriterDataset();
132✔
6083
    poDS->m_pMyVFS = OGRSQLiteCreateVFS(nullptr, poDS);
132✔
6084
    sqlite3_vfs_register(poDS->m_pMyVFS, 0);
132✔
6085

6086
    CPLString osTempDBDefault = CPLString(pszFilename) + ".temp.db";
396✔
6087
    if (STARTS_WITH(osTempDBDefault, "/vsizip/"))
132✔
6088
    {
6089
        osTempDBDefault =
6090
            CPLString(pszFilename + strlen("/vsizip/")) + ".temp.db";
×
6091
    }
6092
    CPLString osTempDB = CSLFetchNameValueDef(papszOptions, "TEMPORARY_DB",
6093
                                              osTempDBDefault.c_str());
264✔
6094
    if (!bReuseTempFile)
132✔
6095
        VSIUnlink(osTempDB);
131✔
6096

6097
    sqlite3 *hDB = nullptr;
132✔
6098
    if (sqlite3_open_v2(osTempDB, &hDB,
132✔
6099
                        SQLITE_OPEN_READWRITE |
6100
                            (bReuseTempFile ? 0 : SQLITE_OPEN_CREATE) |
6101
                            SQLITE_OPEN_NOMUTEX,
6102
                        poDS->m_pMyVFS->zName) != SQLITE_OK ||
393✔
6103
        hDB == nullptr)
129✔
6104
    {
6105
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", osTempDB.c_str());
3✔
6106
        delete poDS;
3✔
6107
        sqlite3_close(hDB);
3✔
6108
        return nullptr;
3✔
6109
    }
6110
    poDS->m_osTempDB = osTempDB;
129✔
6111
    poDS->m_hDB = hDB;
129✔
6112
    poDS->m_bReuseTempFile = bReuseTempFile;
129✔
6113

6114
    // For Unix
6115
    if (!poDS->m_bReuseTempFile &&
257✔
6116
        CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
128✔
6117
    {
6118
        VSIUnlink(osTempDB);
125✔
6119
    }
6120

6121
    if (poDS->m_bReuseTempFile)
129✔
6122
    {
6123
        poDS->m_nTempTiles =
1✔
6124
            SQLGetInteger64(hDB, "SELECT COUNT(*) FROM temp", nullptr);
1✔
6125
    }
6126
    else
6127
    {
6128
        CPL_IGNORE_RET_VAL(SQLCommand(
128✔
6129
            hDB,
6130
            "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
6131
            "PRAGMA synchronous = OFF;"
6132
            "PRAGMA journal_mode = OFF;"
6133
            "PRAGMA temp_store = MEMORY;"
6134
            "CREATE TABLE temp(z INTEGER, x INTEGER, y INTEGER, layer TEXT, "
6135
            "idx INTEGER, feature BLOB, geomtype INTEGER, area_or_length "
6136
            "DOUBLE);"
6137
            "CREATE INDEX temp_index ON temp (z, x, y, layer, idx);"));
6138
    }
6139

6140
    sqlite3_stmt *hInsertStmt = nullptr;
129✔
6141
    CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
129✔
6142
        hDB,
6143
        "INSERT INTO temp (z,x,y,layer,idx,feature,geomtype,area_or_length) "
6144
        "VALUES (?,?,?,?,?,?,?,?)",
6145
        -1, &hInsertStmt, nullptr));
6146
    if (hInsertStmt == nullptr)
129✔
6147
    {
6148
        delete poDS;
×
6149
        return nullptr;
×
6150
    }
6151
    poDS->m_hInsertStmt = hInsertStmt;
129✔
6152

6153
    poDS->m_nMinZoom = atoi(CSLFetchNameValueDef(
129✔
6154
        papszOptions, "MINZOOM", CPLSPrintf("%d", poDS->m_nMinZoom)));
6155
    poDS->m_nMaxZoom = atoi(CSLFetchNameValueDef(
129✔
6156
        papszOptions, "MAXZOOM", CPLSPrintf("%d", poDS->m_nMaxZoom)));
6157
    if (!ValidateMinMaxZoom(poDS->m_nMinZoom, poDS->m_nMaxZoom))
129✔
6158
    {
6159
        delete poDS;
3✔
6160
        return nullptr;
3✔
6161
    }
6162

6163
    const char *pszConf = CSLFetchNameValue(papszOptions, "CONF");
126✔
6164
    if (pszConf)
126✔
6165
    {
6166
        VSIStatBufL sStat;
6167
        bool bSuccess;
6168
        if (VSIStatL(pszConf, &sStat) == 0)
3✔
6169
        {
6170
            bSuccess = poDS->m_oConf.Load(pszConf);
2✔
6171
        }
6172
        else
6173
        {
6174
            bSuccess = poDS->m_oConf.LoadMemory(pszConf);
1✔
6175
        }
6176
        if (!bSuccess)
3✔
6177
        {
6178
            delete poDS;
1✔
6179
            return nullptr;
1✔
6180
        }
6181
    }
6182

6183
    poDS->m_dfSimplification =
125✔
6184
        CPLAtof(CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION", "0"));
125✔
6185
    poDS->m_dfSimplificationMaxZoom = CPLAtof(
125✔
6186
        CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION_MAX_ZOOM",
6187
                             CPLSPrintf("%g", poDS->m_dfSimplification)));
6188
    poDS->m_nExtent = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
125✔
6189
        papszOptions, "EXTENT", CPLSPrintf("%u", poDS->m_nExtent))));
6190
    poDS->m_nBuffer = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
125✔
6191
        papszOptions, "BUFFER", CPLSPrintf("%u", 5 * poDS->m_nExtent / 256))));
125✔
6192

6193
    {
6194
        const char *pszMaxSize = CSLFetchNameValue(papszOptions, "MAX_SIZE");
125✔
6195
        poDS->m_bMaxTileSizeOptSpecified = pszMaxSize != nullptr;
125✔
6196
        // This is used by unit tests
6197
        pszMaxSize = CSLFetchNameValueDef(papszOptions, "@MAX_SIZE_FOR_TEST",
125✔
6198
                                          pszMaxSize);
6199
        if (pszMaxSize)
125✔
6200
        {
6201
            poDS->m_nMaxTileSize =
3✔
6202
                std::max(100U, static_cast<unsigned>(atoi(pszMaxSize)));
3✔
6203
        }
6204
    }
6205

6206
    {
6207
        const char *pszMaxFeatures =
6208
            CSLFetchNameValue(papszOptions, "MAX_FEATURES");
125✔
6209
        poDS->m_bMaxFeaturesOptSpecified = pszMaxFeatures != nullptr;
125✔
6210
        pszMaxFeatures = CSLFetchNameValueDef(
125✔
6211
            // This is used by unit tests
6212
            papszOptions, "@MAX_FEATURES_FOR_TEST", pszMaxFeatures);
6213
        if (pszMaxFeatures)
125✔
6214
        {
6215
            poDS->m_nMaxFeatures =
2✔
6216
                std::max(1U, static_cast<unsigned>(atoi(pszMaxFeatures)));
2✔
6217
        }
6218
    }
6219

6220
    poDS->m_osName = CSLFetchNameValueDef(
6221
        papszOptions, "NAME", CPLGetBasenameSafe(pszFilename).c_str());
125✔
6222
    poDS->m_osDescription = CSLFetchNameValueDef(papszOptions, "DESCRIPTION",
6223
                                                 poDS->m_osDescription.c_str());
125✔
6224
    poDS->m_osType =
6225
        CSLFetchNameValueDef(papszOptions, "TYPE", poDS->m_osType.c_str());
125✔
6226
    poDS->m_bGZip = CPLFetchBool(papszOptions, "COMPRESS", poDS->m_bGZip);
125✔
6227
    poDS->m_osBounds = CSLFetchNameValueDef(papszOptions, "BOUNDS", "");
125✔
6228
    poDS->m_osCenter = CSLFetchNameValueDef(papszOptions, "CENTER", "");
125✔
6229
    poDS->m_osExtension = CSLFetchNameValueDef(papszOptions, "TILE_EXTENSION",
6230
                                               poDS->m_osExtension);
125✔
6231

6232
    const char *pszTilingScheme =
6233
        CSLFetchNameValue(papszOptions, "TILING_SCHEME");
125✔
6234
    if (pszTilingScheme)
125✔
6235
    {
6236
        if (bMBTILES)
6✔
6237
        {
6238
            CPLError(CE_Failure, CPLE_NotSupported,
1✔
6239
                     "Custom TILING_SCHEME not supported with MBTILES output");
6240
            delete poDS;
1✔
6241
            return nullptr;
2✔
6242
        }
6243

6244
        const CPLStringList aoList(CSLTokenizeString2(pszTilingScheme, ",", 0));
5✔
6245
        if (aoList.Count() >= 4)
5✔
6246
        {
6247
            poDS->m_poSRS->SetFromUserInput(aoList[0]);
4✔
6248
            poDS->m_dfTopX = CPLAtof(aoList[1]);
4✔
6249
            poDS->m_dfTopY = CPLAtof(aoList[2]);
4✔
6250
            poDS->m_dfTileDim0 = CPLAtof(aoList[3]);
4✔
6251
            if (aoList.Count() == 6)
4✔
6252
            {
6253
                poDS->m_nTileMatrixWidth0 = std::max(1, atoi(aoList[4]));
2✔
6254
                poDS->m_nTileMatrixHeight0 = std::max(1, atoi(aoList[5]));
2✔
6255
            }
6256
            else if (poDS->m_dfTopX == -180 && poDS->m_dfTileDim0 == 180)
2✔
6257
            {
6258
                // Assumes WorldCRS84Quad with 2 tiles in width
6259
                // cf https://github.com/OSGeo/gdal/issues/11749
6260
                poDS->m_nTileMatrixWidth0 = 2;
1✔
6261
            }
6262
        }
6263
        else
6264
        {
6265
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
6266
                     "Wrong format for TILING_SCHEME. "
6267
                     "Expecting EPSG:XXXX,tile_origin_upper_left_x,"
6268
                     "tile_origin_upper_left_y,tile_dimension_zoom_0[,tile_"
6269
                     "matrix_width_zoom_0,tile_matrix_height_zoom_0]");
6270
            delete poDS;
1✔
6271
            return nullptr;
1✔
6272
        }
6273
    }
6274

6275
    if (bMBTILES)
123✔
6276
    {
6277
        if (sqlite3_open_v2(pszFilename, &poDS->m_hDBMBTILES,
228✔
6278
                            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
6279
                                SQLITE_OPEN_NOMUTEX,
6280
                            poDS->m_pMyVFS->zName) != SQLITE_OK ||
151✔
6281
            poDS->m_hDBMBTILES == nullptr)
75✔
6282
        {
6283
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
1✔
6284
            delete poDS;
1✔
6285
            return nullptr;
1✔
6286
        }
6287

6288
        if (SQLCommand(
75✔
6289
                poDS->m_hDBMBTILES,
6290
                "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
6291
                "PRAGMA synchronous = OFF;"
6292
                "PRAGMA journal_mode = OFF;"
6293
                "PRAGMA temp_store = MEMORY;"
6294
                "CREATE TABLE metadata (name text, value text);"
6295
                "CREATE TABLE tiles (zoom_level integer, tile_column integer, "
6296
                "tile_row integer, tile_data blob, "
6297
                "UNIQUE (zoom_level, tile_column, tile_row))") != OGRERR_NONE)
75✔
6298
        {
6299
            delete poDS;
×
6300
            return nullptr;
×
6301
        }
6302
    }
6303

6304
    int nThreads = CPLGetNumCPUs();
122✔
6305
    const char *pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
122✔
6306
    if (pszNumThreads && CPLGetValueType(pszNumThreads) == CPL_VALUE_INTEGER)
122✔
6307
    {
6308
        nThreads = atoi(pszNumThreads);
3✔
6309
    }
6310
    if (nThreads > 1)
122✔
6311
    {
6312
        poDS->m_bThreadPoolOK =
119✔
6313
            poDS->m_oThreadPool.Setup(nThreads, nullptr, nullptr);
119✔
6314
    }
6315

6316
    poDS->SetDescription(pszFilename);
122✔
6317
    poDS->poDriver = GDALDriver::FromHandle(GDALGetDriverByName("MVT"));
122✔
6318

6319
    return poDS;
122✔
6320
}
6321

6322
GDALDataset *OGRMVTWriterDatasetCreate(const char *pszFilename, int nXSize,
75✔
6323
                                       int nYSize, int nBandsIn,
6324
                                       GDALDataType eDT, char **papszOptions)
6325
{
6326
    return OGRMVTWriterDataset::Create(pszFilename, nXSize, nYSize, nBandsIn,
75✔
6327
                                       eDT, papszOptions);
75✔
6328
}
6329

6330
#endif  // HAVE_MVT_WRITE_SUPPORT
6331

6332
/************************************************************************/
6333
/*                           RegisterOGRMVT()                           */
6334
/************************************************************************/
6335

6336
void RegisterOGRMVT()
1,927✔
6337

6338
{
6339
    if (GDALGetDriverByName("MVT") != nullptr)
1,927✔
6340
        return;
282✔
6341

6342
    GDALDriver *poDriver = new GDALDriver();
1,645✔
6343

6344
    poDriver->SetDescription("MVT");
1,645✔
6345
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1,645✔
6346
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Mapbox Vector Tiles");
1,645✔
6347
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/mvt.html");
1,645✔
6348
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,645✔
6349
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "mvt mvt.gz pbf");
1,645✔
6350
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
1,645✔
6351

6352
    poDriver->SetMetadataItem(
1,645✔
6353
        GDAL_DMD_OPENOPTIONLIST,
6354
        "<OpenOptionList>"
6355
        "  <Option name='X' type='int' description='X coordinate of tile'/>"
6356
        "  <Option name='Y' type='int' description='Y coordinate of tile'/>"
6357
        "  <Option name='Z' type='int' description='Z coordinate of tile'/>"
6358
        //"  <Option name='@GEOREF_TOPX' type='float' description='X coordinate
6359
        // of top-left corner of tile'/>" "  <Option name='@GEOREF_TOPY'
6360
        // type='float' description='Y coordinate of top-left corner of tile'/>"
6361
        //"  <Option name='@GEOREF_TILEDIMX' type='float' description='Tile
6362
        // width in georeferenced units'/>" "  <Option name='@GEOREF_TILEDIMY'
6363
        // type='float' description='Tile height in georeferenced units'/>"
6364
        "  <Option name='METADATA_FILE' type='string' "
6365
        "description='Path to metadata.json'/>"
6366
        "  <Option name='CLIP' type='boolean' "
6367
        "description='Whether to clip geometries to tile extent' "
6368
        "default='YES'/>"
6369
        "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
6370
        "description="
6371
        "'For tilesets, extension of tiles'/>"
6372
        "  <Option name='TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN' type='int' "
6373
        "description="
6374
        "'For tilesets without metadata file, maximum number of tiles to use "
6375
        "to "
6376
        "establish the layer schemas' default='1000'/>"
6377
        "  <Option name='JSON_FIELD' type='boolean' description='For tilesets, "
6378
        "whether to put all attributes as a serialized JSon dictionary'/>"
6379
        "</OpenOptionList>");
1,645✔
6380

6381
    poDriver->pfnIdentify = OGRMVTDriverIdentify;
1,645✔
6382
    poDriver->pfnOpen = OGRMVTDataset::Open;
1,645✔
6383
#ifdef HAVE_MVT_WRITE_SUPPORT
6384
    poDriver->pfnCreate = OGRMVTWriterDataset::Create;
1,645✔
6385
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1,645✔
6386
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
1,645✔
6387
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
1,645✔
6388
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
1,645✔
6389
                              "Integer Integer64 Real String");
1,645✔
6390
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
1,645✔
6391
                              "Boolean Float32");
1,645✔
6392
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
1,645✔
6393

6394
    poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
1,645✔
6395

6396
    poDriver->SetMetadataItem(
1,645✔
6397
        GDAL_DMD_CREATIONOPTIONLIST,
6398
        "<CreationOptionList>"
6399
        "  <Option name='NAME' type='string' description='Tileset name'/>"
6400
        "  <Option name='DESCRIPTION' type='string' "
6401
        "description='A description of the tileset'/>"
6402
        "  <Option name='TYPE' type='string-select' description='Layer type' "
6403
        "default='overlay'>"
6404
        "    <Value>overlay</Value>"
6405
        "    <Value>baselayer</Value>"
6406
        "  </Option>"
6407
        "  <Option name='FORMAT' type='string-select' description='Format'>"
6408
        "    <Value>DIRECTORY</Value>"
6409
        "    <Value>MBTILES</Value>"
6410
        "  </Option>"
6411
        "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
6412
        "description="
6413
        "'For tilesets as directories of files, extension of "
6414
        "tiles'/>" MVT_MBTILES_COMMON_DSCO
6415
        "  <Option name='BOUNDS' type='string' "
6416
        "description='Override default value for bounds metadata item'/>"
6417
        "  <Option name='CENTER' type='string' "
6418
        "description='Override default value for center metadata item'/>"
6419
        "  <Option name='TILING_SCHEME' type='string' "
6420
        "description='Custom tiling scheme with following format "
6421
        "\"EPSG:XXXX,tile_origin_upper_left_x,tile_origin_upper_left_y,"
6422
        "tile_dimension_zoom_0[,tile_matrix_width_zoom_0,tile_matrix_height_"
6423
        "zoom_0]\"'/>"
6424
        "</CreationOptionList>");
1,645✔
6425
#endif  // HAVE_MVT_WRITE_SUPPORT
6426

6427
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,645✔
6428

6429
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,645✔
6430
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc