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

OSGeo / gdal / 8872387746

29 Apr 2024 02:16AM UTC coverage: 69.076% (+0.003%) from 69.073%
8872387746

Pull #9801

github

web-flow
Bump actions/upload-artifact from 4.3.2 to 4.3.3

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.2 to 4.3.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/1746f4ab6...65462800f)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #9801: Bump actions/upload-artifact from 4.3.2 to 4.3.3

534153 of 773282 relevant lines covered (69.08%)

205719.18 hits per line

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

94.51
/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
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28

29
#if defined(HAVE_SQLITE) && defined(HAVE_GEOS)
30
// Needed by mvtutils.h
31
#define HAVE_MVT_WRITE_SUPPORT
32
#endif
33

34
#include "ogrsf_frmts.h"
35
#include "cpl_conv.h"
36
#include "cpl_json.h"
37
#include "cpl_http.h"
38
#include "ogr_p.h"
39

40
#include "mvt_tile.h"
41
#include "mvtutils.h"
42

43
#include "ogr_geos.h"
44

45
#include "gpb.h"
46

47
#include <algorithm>
48
#include <memory>
49
#include <vector>
50
#include <set>
51

52
const char *SRS_EPSG_3857 =
53
    "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS "
54
    "84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
55
    "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY["
56
    "\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],"
57
    "UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],"
58
    "AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Mercator_1SP\"],PARAMETER["
59
    "\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_"
60
    "easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY["
61
    "\"EPSG\",\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION["
62
    "\"PROJ4\",\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 "
63
    "+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  "
64
    "+no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]";
65

66
// WebMercator related constants
67
constexpr double kmSPHERICAL_RADIUS = 6378137.0;
68

69
constexpr int knMAX_FILES_PER_DIR = 10000;
70

71
#ifdef HAVE_MVT_WRITE_SUPPORT
72

73
#include <sqlite3.h>
74
#include "../sqlite/ogrsqliteutility.h"
75

76
#include "../sqlite/ogrsqlitevfs.h"
77

78
#include "cpl_worker_thread_pool.h"
79

80
#include <mutex>
81

82
// Limitations from https://github.com/mapbox/mapbox-geostats
83
constexpr size_t knMAX_COUNT_LAYERS = 1000;
84
constexpr size_t knMAX_REPORT_LAYERS = 100;
85
constexpr size_t knMAX_COUNT_FIELDS = 1000;
86
constexpr size_t knMAX_REPORT_FIELDS = 100;
87
constexpr size_t knMAX_COUNT_VALUES = 1000;
88
constexpr size_t knMAX_REPORT_VALUES = 100;
89
constexpr size_t knMAX_STRING_VALUE_LENGTH = 256;
90
constexpr size_t knMAX_LAYER_NAME_LENGTH = 256;
91
constexpr size_t knMAX_FIELD_NAME_LENGTH = 256;
92

93
#undef SQLITE_STATIC
94
#define SQLITE_STATIC ((sqlite3_destructor_type) nullptr)
95

96
#endif
97

98
/************************************************************************/
99
/*                    InitWebMercatorTilingScheme()                     */
100
/************************************************************************/
101

102
static void InitWebMercatorTilingScheme(OGRSpatialReference *poSRS,
1,196✔
103
                                        double &dfTopX, double &dfTopY,
104
                                        double &dfTileDim0)
105
{
106
    constexpr double kmMAX_GM =
1,196✔
107
        kmSPHERICAL_RADIUS * M_PI;  // 20037508.342789244
108
    poSRS->SetFromUserInput(SRS_EPSG_3857);
1,196✔
109
    dfTopX = -kmMAX_GM;
1,196✔
110
    dfTopY = kmMAX_GM;
1,196✔
111
    dfTileDim0 = 2 * kmMAX_GM;
1,196✔
112
}
1,196✔
113

114
/************************************************************************/
115
/*                           GetCmdId()                                 */
116
/************************************************************************/
117

118
/* For a drawing instruction combining a command id and a command count,
119
 * return the command id */
120
static unsigned GetCmdId(unsigned int nCmdCountCombined)
1,802✔
121
{
122
    return nCmdCountCombined & 0x7;
1,802✔
123
}
124

125
/************************************************************************/
126
/*                           GetCmdCount()                              */
127
/************************************************************************/
128

129
/* For a drawing instruction combining a command id and a command count,
130
 * return the command count */
131
static unsigned GetCmdCount(unsigned int nCmdCountCombined)
5,571✔
132
{
133
    return nCmdCountCombined >> 3;
5,571✔
134
}
135

136
/************************************************************************/
137
/*                          OGRMVTLayerBase                             */
138
/************************************************************************/
139

140
class OGRMVTLayerBase CPL_NON_FINAL
141
    : public OGRLayer,
142
      public OGRGetNextFeatureThroughRaw<OGRMVTLayerBase>
143
{
144
    virtual OGRFeature *GetNextRawFeature() = 0;
145

146
  protected:
147
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
148

149
    void InitFields(const CPLJSONObject &oFields,
150
                    const CPLJSONArray &oAttributesFromTileStats);
151

152
  public:
153
    virtual ~OGRMVTLayerBase();
154

155
    virtual OGRFeatureDefn *GetLayerDefn() override
7,739✔
156
    {
157
        return m_poFeatureDefn;
7,739✔
158
    }
159

160
    DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRMVTLayerBase)
2,848✔
161

162
    virtual int TestCapability(const char *) override;
163
};
164

165
/************************************************************************/
166
/*                             OGRMVTLayer                              */
167
/************************************************************************/
168

169
class OGRMVTDataset;
170

171
class OGRMVTLayer final : public OGRMVTLayerBase
172
{
173
    OGRMVTDataset *m_poDS;
174
    const GByte *m_pabyDataStart;
175
    const GByte *m_pabyDataEnd;
176
    const GByte *m_pabyDataCur = nullptr;
177
    const GByte *m_pabyDataFeatureStart = nullptr;
178
    bool m_bError = false;
179
    unsigned int m_nExtent = knDEFAULT_EXTENT;
180
    std::vector<CPLString> m_aosKeys;
181

182
    typedef struct
183
    {
184
        OGRFieldType eType;
185
        OGRFieldSubType eSubType;
186
        OGRField sValue;
187
    } Value;
188

189
    std::vector<Value> m_asValues;
190
    GIntBig m_nFID = 0;
191
    GIntBig m_nFeatureCount = -1;
192
    OGRPolygon m_oClipPoly;
193
    double m_dfTileMinX = 0;
194
    double m_dfTileMinY = 0;
195
    double m_dfTileMaxX = 0;
196
    double m_dfTileMaxY = 0;
197
    bool m_bEnforceExternalIsClockwise = false;
198

199
    void Init(const CPLJSONObject &oFields,
200
              const CPLJSONArray &oAttributesFromTileStats);
201
    bool QuickScanFeature(const GByte *pabyData,
202
                          const GByte *pabyDataFeatureEnd, bool bScanFields,
203
                          bool bScanGeometries, bool &bGeomTypeSet);
204
    void GetXY(int nX, int nY, double &dfX, double &dfY);
205
    OGRGeometry *ParseGeometry(unsigned int nGeomType,
206
                               const GByte *pabyDataGeometryEnd);
207
    void SanitizeClippedGeometry(OGRGeometry *&poGeom);
208

209
    virtual OGRFeature *GetNextRawFeature() override;
210

211
  public:
212
    OGRMVTLayer(OGRMVTDataset *poDS, const char *pszLayerName,
213
                const GByte *pabyData, int nLayerSize,
214
                const CPLJSONObject &oFields,
215
                const CPLJSONArray &oAttributesFromTileStats,
216
                OGRwkbGeometryType eGeomType);
217
    virtual ~OGRMVTLayer();
218

219
    virtual void ResetReading() override;
220

221
    virtual GIntBig GetFeatureCount(int bForce) override;
222

223
    GDALDataset *GetDataset() override;
224
};
225

226
/************************************************************************/
227
/*                        OGRMVTDirectoryLayer                          */
228
/************************************************************************/
229

230
class OGRMVTDirectoryLayer final : public OGRMVTLayerBase
231
{
232
    OGRMVTDataset *m_poDS;
233
    int m_nZ = 0;
234
    bool m_bUseReadDir = true;
235
    CPLString m_osDirName;
236
    CPLStringList m_aosDirContent;
237
    CPLString m_aosSubDirName;
238
    CPLStringList m_aosSubDirContent;
239
    bool m_bEOF = false;
240
    int m_nXIndex = 0;
241
    int m_nYIndex = 0;
242
    GDALDataset *m_poCurrentTile = nullptr;
243
    bool m_bJsonField = false;
244
    GIntBig m_nFIDBase = 0;
245
    OGREnvelope m_sExtent;
246
    int m_nFilterMinX = 0;
247
    int m_nFilterMinY = 0;
248
    int m_nFilterMaxX = 0;
249
    int m_nFilterMaxY = 0;
250

251
    virtual OGRFeature *GetNextRawFeature() override;
252
    OGRFeature *CreateFeatureFrom(OGRFeature *poSrcFeature);
253
    void ReadNewSubDir();
254
    void OpenTile();
255
    void OpenTileIfNeeded();
256

257
  public:
258
    OGRMVTDirectoryLayer(OGRMVTDataset *poDS, const char *pszLayerName,
259
                         const char *pszDirectoryName,
260
                         const CPLJSONObject &oFields,
261
                         const CPLJSONArray &oAttributesFromTileStats,
262
                         bool bJsonField, OGRwkbGeometryType eGeomType,
263
                         const OGREnvelope *psExtent);
264
    virtual ~OGRMVTDirectoryLayer();
265

266
    virtual void ResetReading() override;
267

268
    virtual GIntBig GetFeatureCount(int bForce) override;
269
    OGRErr GetExtent(OGREnvelope *psExtent, int bForce) override;
270

271
    virtual OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent,
4✔
272
                             int bForce) override
273
    {
274
        return OGRLayer::GetExtent(iGeomField, psExtent, bForce);
4✔
275
    }
276

277
    virtual void SetSpatialFilter(OGRGeometry *) override;
278

279
    virtual void SetSpatialFilter(int iGeomField, OGRGeometry *poGeom) override
13✔
280
    {
281
        OGRLayer::SetSpatialFilter(iGeomField, poGeom);
13✔
282
    }
13✔
283

284
    virtual OGRFeature *GetFeature(GIntBig nFID) override;
285

286
    virtual int TestCapability(const char *) override;
287

288
    GDALDataset *GetDataset() override;
289
};
290

291
/************************************************************************/
292
/*                           OGRMVTDataset                              */
293
/************************************************************************/
294

295
class OGRMVTDataset final : public GDALDataset
296
{
297
    friend class OGRMVTLayer;
298
    friend class OGRMVTDirectoryLayer;
299

300
    GByte *m_pabyData;
301
    std::vector<std::unique_ptr<OGRLayer>> m_apoLayers;
302
    bool m_bGeoreferenced = false;
303
    double m_dfTileDimX = 0.0;
304
    double m_dfTileDimY = 0.0;
305
    double m_dfTopX = 0.0;
306
    double m_dfTopY = 0.0;
307
    CPLString m_osMetadataMemFilename;
308
    bool m_bClip = true;
309
    CPLString m_osTileExtension{"pbf"};
310
    OGRSpatialReference *m_poSRS = nullptr;
311
    double m_dfTileDim0 = 0.0;
312
    double m_dfTopXOrigin = 0.0;
313
    double m_dfTopYOrigin = 0.0;
314

315
    static GDALDataset *OpenDirectory(GDALOpenInfo *);
316

317
  public:
318
    explicit OGRMVTDataset(GByte *pabyData);
319
    virtual ~OGRMVTDataset();
320

321
    virtual int GetLayerCount() override
3,294✔
322
    {
323
        return static_cast<int>(m_apoLayers.size());
3,294✔
324
    }
325

326
    virtual OGRLayer *GetLayer(int) override;
327

328
    virtual int TestCapability(const char *) override
12✔
329
    {
330
        return FALSE;
12✔
331
    }
332

333
    static GDALDataset *Open(GDALOpenInfo *);
334

335
    OGRSpatialReference *GetSRS()
997✔
336
    {
337
        return m_poSRS;
997✔
338
    }
339

340
    double GetTileDim0() const
180✔
341
    {
342
        return m_dfTileDim0;
180✔
343
    }
344

345
    double GetTopXOrigin() const
72✔
346
    {
347
        return m_dfTopXOrigin;
72✔
348
    }
349

350
    double GetTopYOrigin() const
72✔
351
    {
352
        return m_dfTopYOrigin;
72✔
353
    }
354
};
355

356
/************************************************************************/
357
/*                        ~OGRMVTLayerBase()                            */
358
/************************************************************************/
359

360
OGRMVTLayerBase::~OGRMVTLayerBase()
1,037✔
361
{
362
    m_poFeatureDefn->Release();
1,037✔
363
}
1,037✔
364

365
/************************************************************************/
366
/*                           InitFields()                               */
367
/************************************************************************/
368

369
void OGRMVTLayerBase::InitFields(const CPLJSONObject &oFields,
1,036✔
370
                                 const CPLJSONArray &oAttributesFromTileStats)
371
{
372
    OGRMVTInitFields(m_poFeatureDefn, oFields, oAttributesFromTileStats);
1,036✔
373
}
1,036✔
374

375
/************************************************************************/
376
/*                           TestCapability()                           */
377
/************************************************************************/
378

379
int OGRMVTLayerBase::TestCapability(const char *pszCap)
69✔
380
{
381
    if (EQUAL(pszCap, OLCStringsAsUTF8) || EQUAL(pszCap, OLCFastSpatialFilter))
69✔
382
    {
383
        return TRUE;
25✔
384
    }
385
    return FALSE;
44✔
386
}
387

388
/************************************************************************/
389
/*                           OGRMVTLayer()                              */
390
/************************************************************************/
391

392
OGRMVTLayer::OGRMVTLayer(OGRMVTDataset *poDS, const char *pszLayerName,
1,016✔
393
                         const GByte *pabyData, int nLayerSize,
394
                         const CPLJSONObject &oFields,
395
                         const CPLJSONArray &oAttributesFromTileStats,
396
                         OGRwkbGeometryType eGeomType)
1,016✔
397
    : m_poDS(poDS), m_pabyDataStart(pabyData),
398
      m_pabyDataEnd(pabyData + nLayerSize)
1,016✔
399
{
400
    m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
1,016✔
401
    SetDescription(m_poFeatureDefn->GetName());
1,016✔
402
    m_poFeatureDefn->SetGeomType(eGeomType);
1,016✔
403
    m_poFeatureDefn->Reference();
1,016✔
404

405
    if (m_poDS->m_bGeoreferenced)
1,016✔
406
    {
407
        m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poDS->GetSRS());
976✔
408
    }
409

410
    Init(oFields, oAttributesFromTileStats);
1,016✔
411

412
    GetXY(0, 0, m_dfTileMinX, m_dfTileMaxY);
1,016✔
413
    GetXY(m_nExtent, m_nExtent, m_dfTileMaxX, m_dfTileMinY);
1,016✔
414
    OGRLinearRing *poLR = new OGRLinearRing();
1,016✔
415
    poLR->addPoint(m_dfTileMinX, m_dfTileMinY);
1,016✔
416
    poLR->addPoint(m_dfTileMinX, m_dfTileMaxY);
1,016✔
417
    poLR->addPoint(m_dfTileMaxX, m_dfTileMaxY);
1,016✔
418
    poLR->addPoint(m_dfTileMaxX, m_dfTileMinY);
1,016✔
419
    poLR->addPoint(m_dfTileMinX, m_dfTileMinY);
1,016✔
420
    m_oClipPoly.addRingDirectly(poLR);
1,016✔
421

422
    // Config option only for tests for now. When set, it ensures that
423
    // the first ring (exterior ring) of a polygon is clockwise oriented,
424
    // as per the MVT spec.
425
    // By default, we are more tolerant and only use reversal of winding order
426
    // to detect inner rings.
427
    m_bEnforceExternalIsClockwise = CPLTestBool(
1,016✔
428
        CPLGetConfigOption("OGR_MVT_ENFORE_EXTERNAL_RING_IS_CLOCKWISE", "NO"));
429
}
1,016✔
430

431
/************************************************************************/
432
/*                          ~OGRMVTLayer()                              */
433
/************************************************************************/
434

435
OGRMVTLayer::~OGRMVTLayer()
2,032✔
436
{
437
    for (auto &sValue : m_asValues)
21,732✔
438
    {
439
        if (sValue.eType == OFTString)
20,716✔
440
        {
441
            CPLFree(sValue.sValue.String);
11,731✔
442
        }
443
    }
444
}
2,032✔
445

446
/************************************************************************/
447
/*                               Init()                                 */
448
/************************************************************************/
449

450
void OGRMVTLayer::Init(const CPLJSONObject &oFields,
1,016✔
451
                       const CPLJSONArray &oAttributesFromTileStats)
452
{
453
    // First pass to collect keys and values
454
    const GByte *pabyData = m_pabyDataStart;
1,016✔
455
    const GByte *pabyDataLimit = m_pabyDataEnd;
1,016✔
456
    unsigned int nKey = 0;
1,016✔
457
    bool bGeomTypeSet = false;
1,016✔
458
    const bool bScanFields = !oFields.IsValid();
1,016✔
459
    const bool bScanGeometries = m_poFeatureDefn->GetGeomType() == wkbUnknown;
1,016✔
460
    const bool bQuickScanFeature = bScanFields || bScanGeometries;
1,016✔
461

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

561
                pabyData = pabyDataValueEnd;
20,717✔
562
            }
563
            else if (nKey == MAKE_KEY(knLAYER_EXTENT, WT_VARINT))
5,589✔
564
            {
565
                GUInt32 nExtent = 0;
1,003✔
566
                READ_VARUINT32(pabyData, pabyDataLimit, nExtent);
1,003✔
567
                m_nExtent = std::max(1U, nExtent);  // to avoid divide by zero
1,003✔
568
            }
569
            else
570
            {
571
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
4,586✔
572
            }
573
        }
574

575
        InitFields(oFields, oAttributesFromTileStats);
1,016✔
576

577
        m_nFeatureCount = 0;
1,016✔
578
        pabyData = m_pabyDataStart;
1,016✔
579
        // Second pass to iterate over features to figure out the geometry type
580
        // and attribute schema
581
        while (pabyData < pabyDataLimit)
43,604✔
582
        {
583
            const GByte *pabyDataBefore = pabyData;
42,591✔
584
            READ_FIELD_KEY(nKey);
42,591✔
585
            if (nKey == MAKE_KEY(knLAYER_FEATURES, WT_DATA))
42,591✔
586
            {
587
                if (m_pabyDataFeatureStart == nullptr)
2,553✔
588
                {
589
                    m_pabyDataFeatureStart = pabyDataBefore;
1,014✔
590
                    m_pabyDataCur = pabyDataBefore;
1,014✔
591
                }
592

593
                unsigned int nFeatureLength = 0;
2,553✔
594
                READ_SIZE(pabyData, pabyDataLimit, nFeatureLength);
2,553✔
595
                const GByte *pabyDataFeatureEnd = pabyData + nFeatureLength;
2,553✔
596
                if (bQuickScanFeature)
2,553✔
597
                {
598
                    if (!QuickScanFeature(pabyData, pabyDataFeatureEnd,
504✔
599
                                          bScanFields, bScanGeometries,
600
                                          bGeomTypeSet))
601
                    {
602
                        return;
3✔
603
                    }
604
                }
605
                pabyData = pabyDataFeatureEnd;
2,550✔
606

607
                m_nFeatureCount++;
2,550✔
608
            }
609
            else
610
            {
611
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
40,038✔
612
            }
613
        }
614
    }
615
    catch (const GPBException &e)
×
616
    {
617
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
×
618
    }
619
}
620

621
/************************************************************************/
622
/*                          MergeFieldDefn()                            */
623
/************************************************************************/
624

625
static void MergeFieldDefn(OGRFieldDefn *poFieldDefn, OGRFieldType eSrcType,
11✔
626
                           OGRFieldSubType eSrcSubType)
627
{
628
    if (eSrcType == OFTString)
11✔
629
    {
630
        poFieldDefn->SetSubType(OFSTNone);
4✔
631
        poFieldDefn->SetType(OFTString);
4✔
632
    }
633
    else if (poFieldDefn->GetType() == OFTInteger && eSrcType == OFTInteger64)
7✔
634
    {
635
        poFieldDefn->SetSubType(OFSTNone);
1✔
636
        poFieldDefn->SetType(OFTInteger64);
1✔
637
    }
638
    else if ((poFieldDefn->GetType() == OFTInteger ||
10✔
639
              poFieldDefn->GetType() == OFTInteger64) &&
10✔
640
             eSrcType == OFTReal)
641
    {
642
        poFieldDefn->SetSubType(OFSTNone);
2✔
643
        poFieldDefn->SetType(OFTReal);
2✔
644
        poFieldDefn->SetSubType(eSrcSubType);
2✔
645
    }
646
    else if (poFieldDefn->GetType() == OFTReal && eSrcType == OFTReal &&
4✔
647
             eSrcSubType == OFSTNone)
648
    {
649
        poFieldDefn->SetSubType(OFSTNone);
1✔
650
    }
651
    else if (poFieldDefn->GetType() == OFTInteger && eSrcType == OFTInteger &&
3✔
652
             eSrcSubType == OFSTNone)
653
    {
654
        poFieldDefn->SetSubType(OFSTNone);
1✔
655
    }
656
}
11✔
657

658
/************************************************************************/
659
/*                         QuickScanFeature()                           */
660
/************************************************************************/
661

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

736
                if (nGeomType == knGEOM_TYPE_POINT)
483✔
737
                {
738
                    eType = wkbPoint;
74✔
739
                    unsigned int nCmdCountCombined = 0;
74✔
740
                    READ_VARUINT32(pabyData, pabyDataGeometryEnd,
74✔
741
                                   nCmdCountCombined);
742
                    if (GetCmdId(nCmdCountCombined) == knCMD_MOVETO &&
148✔
743
                        GetCmdCount(nCmdCountCombined) > 1)
74✔
744
                    {
745
                        eType = wkbMultiPoint;
×
746
                    }
747
                }
748
                else if (nGeomType == knGEOM_TYPE_LINESTRING)
409✔
749
                {
750
                    eType = wkbLineString;
16✔
751
                    for (int iIter = 0; pabyData < pabyDataGeometryEnd; iIter++)
32✔
752
                    {
753
                        if (iIter == 1)
17✔
754
                        {
755
                            eType = wkbMultiLineString;
1✔
756
                            break;
1✔
757
                        }
758
                        unsigned int nCmdCountCombined = 0;
16✔
759
                        unsigned int nLineToCount;
760
                        // Should be a moveto
761
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
16✔
762
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
16✔
763
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
16✔
764
                        READ_VARUINT32(pabyData, pabyDataGeometryEnd,
16✔
765
                                       nCmdCountCombined);
766
                        nLineToCount = GetCmdCount(nCmdCountCombined);
16✔
767
                        for (unsigned i = 0; i < 2 * nLineToCount; i++)
50✔
768
                        {
769
                            SKIP_VARINT(pabyData, pabyDataGeometryEnd);
34✔
770
                        }
771
                    }
772
                }
773
                else /* if( nGeomType == knGEOM_TYPE_POLYGON ) */
774
                {
775
                    eType = wkbPolygon;
393✔
776
                    for (int iIter = 0; pabyData < pabyDataGeometryEnd; iIter++)
786✔
777
                    {
778
                        if (iIter == 1)
427✔
779
                        {
780
                            eType = wkbMultiPolygon;
34✔
781
                            break;
34✔
782
                        }
783
                        unsigned int nCmdCountCombined = 0;
393✔
784
                        unsigned int nLineToCount;
785
                        // Should be a moveto
786
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
393✔
787
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
393✔
788
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
393✔
789
                        READ_VARUINT32(pabyData, pabyDataGeometryEnd,
393✔
790
                                       nCmdCountCombined);
791
                        nLineToCount = GetCmdCount(nCmdCountCombined);
393✔
792
                        for (unsigned i = 0; i < 2 * nLineToCount; i++)
2,523✔
793
                        {
794
                            SKIP_VARINT(pabyData, pabyDataGeometryEnd);
2,130✔
795
                        }
796
                        // Should be a closepath
797
                        SKIP_VARINT(pabyData, pabyDataGeometryEnd);
393✔
798
                    }
799
                }
800

801
                if (bGeomTypeSet && m_poFeatureDefn->GetGeomType() ==
500✔
802
                                        OGR_GT_GetCollection(eType))
17✔
803
                {
804
                    // do nothing
805
                }
806
                else if (bGeomTypeSet &&
490✔
807
                         eType == OGR_GT_GetCollection(
12✔
808
                                      m_poFeatureDefn->GetGeomType()))
12✔
809
                {
810
                    m_poFeatureDefn->SetGeomType(eType);
×
811
                }
812
                else if (bGeomTypeSet &&
490✔
813
                         m_poFeatureDefn->GetGeomType() != eType)
12✔
814
                {
815
                    m_poFeatureDefn->SetGeomType(wkbUnknown);
×
816
                }
817
                else
818
                {
819
                    m_poFeatureDefn->SetGeomType(eType);
478✔
820
                }
821
                bGeomTypeSet = true;
483✔
822

823
                pabyData = pabyDataGeometryEnd;
483✔
824
            }
825
            else
826
            {
827
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataFeatureEnd, FALSE);
418✔
828
            }
829
        }
830
        return true;
501✔
831
    }
832
    catch (const GPBException &e)
1✔
833
    {
834
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
1✔
835
        return false;
1✔
836
    }
837
}
838

839
/************************************************************************/
840
/*                         GetFeatureCount()                            */
841
/************************************************************************/
842

843
GIntBig OGRMVTLayer::GetFeatureCount(int bForce)
51✔
844
{
845
    if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr &&
51✔
846
        m_nFeatureCount >= 0)
45✔
847
    {
848
        return m_nFeatureCount;
45✔
849
    }
850
    return OGRLayer::GetFeatureCount(bForce);
6✔
851
}
852

853
/************************************************************************/
854
/*                          ResetReading()                              */
855
/************************************************************************/
856

857
void OGRMVTLayer::ResetReading()
131✔
858
{
859
    m_nFID = 0;
131✔
860
    m_pabyDataCur = m_pabyDataFeatureStart;
131✔
861
}
131✔
862

863
/************************************************************************/
864
/*                              GetXY()                                 */
865
/************************************************************************/
866

867
void OGRMVTLayer::GetXY(int nX, int nY, double &dfX, double &dfY)
353,923✔
868
{
869
    if (m_poDS->m_bGeoreferenced)
353,923✔
870
    {
871
        dfX = m_poDS->m_dfTopX + nX * m_poDS->m_dfTileDimX / m_nExtent;
352,368✔
872
        dfY = m_poDS->m_dfTopY - nY * m_poDS->m_dfTileDimY / m_nExtent;
352,368✔
873
    }
874
    else
875
    {
876
        dfX = nX;
1,555✔
877
        dfY = static_cast<double>(m_nExtent) - nY;
1,555✔
878
    }
879
}
353,923✔
880

881
/************************************************************************/
882
/*                     AddWithOverflowAccepted()                        */
883
/************************************************************************/
884

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

899
/************************************************************************/
900
/*                           ParseGeometry()                            */
901
/************************************************************************/
902

903
OGRGeometry *OGRMVTLayer::ParseGeometry(unsigned int nGeomType,
2,032✔
904
                                        const GByte *pabyDataGeometryEnd)
905
{
906
    OGRMultiPoint *poMultiPoint = nullptr;
2,032✔
907
    OGRMultiLineString *poMultiLS = nullptr;
2,032✔
908
    OGRLineString *poLine = nullptr;
2,032✔
909
    OGRMultiPolygon *poMultiPoly = nullptr;
2,032✔
910
    OGRPolygon *poPoly = nullptr;
2,032✔
911
    OGRLinearRing *poRing = nullptr;
2,032✔
912

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

1120
                        poPoly = new OGRPolygon();
1,557✔
1121
                        poMultiPoly->addGeometryDirectly(poPoly);
1,557✔
1122
                        poPoly->addRingDirectly(poRing);
1,557✔
1123
                    }
1124
                }
1125
                poRing = nullptr;
3,525✔
1126
            }
1127
            if (poMultiPoly == nullptr && poPoly != nullptr &&
3,086✔
1128
                m_poFeatureDefn->GetGeomType() == wkbMultiPolygon)
1,185✔
1129
            {
1130
                poMultiPoly = new OGRMultiPolygon();
833✔
1131
                poMultiPoly->addGeometryDirectly(poPoly);
833✔
1132
            }
1133
            if (poMultiPoly)
1,901✔
1134
            {
1135
                return poMultiPoly;
1,549✔
1136
            }
1137
            else
1138
            {
1139
                return poPoly;
352✔
1140
            }
1141
        }
1142
    }
1143
    catch (const GPBException &e)
2✔
1144
    {
1145
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
1✔
1146
        delete poMultiPoint;
1✔
1147
        if (poMultiPoly)
1✔
1148
            delete poMultiPoly;
×
1149
        else if (poPoly)
1✔
1150
            delete poPoly;
×
1151
        if (poMultiLS)
1✔
1152
            delete poMultiLS;
×
1153
        else if (poLine)
1✔
1154
            delete poLine;
×
1155
        delete poRing;
1✔
1156
    }
1157
    return nullptr;
1✔
1158
}
1159

1160
/************************************************************************/
1161
/*                      SanitizeClippedGeometry()                       */
1162
/************************************************************************/
1163

1164
void OGRMVTLayer::SanitizeClippedGeometry(OGRGeometry *&poGeom)
636✔
1165
{
1166
    OGRwkbGeometryType eInGeomType = wkbFlatten(poGeom->getGeometryType());
636✔
1167
    const OGRwkbGeometryType eLayerGeomType = GetGeomType();
636✔
1168
    if (eLayerGeomType == wkbUnknown)
636✔
1169
    {
1170
        return;
×
1171
    }
1172

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

1209
                    poTargetGC->addGeometry(poSubGeom);
49✔
1210
                }
1211
                else
1212
                {
1213
                    poTargetSingleGeom = poSubGeom->clone();
113✔
1214
                    poGeom = poTargetSingleGeom;
113✔
1215
                }
1216
            }
1217
        }
1218
        if (poGeom != poGC)
113✔
1219
        {
1220
            delete poGC;
113✔
1221
        }
1222
        eInGeomType = wkbFlatten(poGeom->getGeometryType());
113✔
1223
    }
1224

1225
    // Wrap single into multi if requested by the layer geometry type
1226
    if (OGR_GT_GetCollection(eInGeomType) == eLayerGeomType)
636✔
1227
    {
1228
        OGRGeometryCollection *poGC =
1229
            OGRGeometryFactory::createGeometry(eLayerGeomType)
1230
                ->toGeometryCollection();
383✔
1231
        poGC->addGeometryDirectly(poGeom);
383✔
1232
        poGeom = poGC;
383✔
1233
        return;
383✔
1234
    }
1235
}
1236

1237
/************************************************************************/
1238
/*                         GetNextRawFeature()                          */
1239
/************************************************************************/
1240

1241
OGRFeature *OGRMVTLayer::GetNextRawFeature()
2,787✔
1242
{
1243
    if (m_pabyDataCur == nullptr || m_pabyDataCur >= m_pabyDataEnd || m_bError)
2,787✔
1244
    {
1245
        return nullptr;
106✔
1246
    }
1247

1248
    unsigned int nKey = 0;
2,681✔
1249
    const GByte *pabyDataLimit = m_pabyDataEnd;
2,681✔
1250
    OGRFeature *poFeature = nullptr;
2,681✔
1251
    unsigned int nFeatureLength = 0;
2,681✔
1252
    unsigned int nGeomType = 0;
2,681✔
1253

1254
    try
1255
    {
1256
        while (true)
1257
        {
1258
            bool bOK = true;
2,685✔
1259

1260
            while (m_pabyDataCur < pabyDataLimit)
33,400✔
1261
            {
1262
                READ_VARUINT32(m_pabyDataCur, pabyDataLimit, nKey);
32,755✔
1263
                if (nKey == MAKE_KEY(knLAYER_FEATURES, WT_DATA))
32,755✔
1264
                {
1265
                    poFeature = new OGRFeature(m_poFeatureDefn);
2,040✔
1266
                    break;
2,040✔
1267
                }
1268
                else
1269
                {
1270
                    SKIP_UNKNOWN_FIELD(m_pabyDataCur, pabyDataLimit, FALSE);
30,715✔
1271
                }
1272
            }
1273

1274
            if (poFeature == nullptr)
2,685✔
1275
                return nullptr;
645✔
1276

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

1397
                        if (poGeom)
2,031✔
1398
                        {
1399
                            poGeom->assignSpatialReference(GetSpatialRef());
1,395✔
1400
                            poFeature->SetGeometryDirectly(poGeom);
1,395✔
1401
                        }
1402
                    }
1403

1404
                    m_pabyDataCur = pabyDataGeometryEnd;
2,032✔
1405
                }
1406
                else
1407
                {
1408
                    SKIP_UNKNOWN_FIELD(m_pabyDataCur, pabyDataFeatureEnd,
2✔
1409
                                       FALSE);
1410
                }
1411
            }
1412
            m_pabyDataCur = pabyDataFeatureEnd;
2,040✔
1413

1414
            if (bOK)
2,040✔
1415
            {
1416
                poFeature->SetFID(m_nFID);
2,036✔
1417
                m_nFID++;
2,036✔
1418
                return poFeature;
2,036✔
1419
            }
1420
            else
1421
            {
1422
                delete poFeature;
4✔
1423
                poFeature = nullptr;
4✔
1424
            }
1425
        }
4✔
1426
    }
1427
    catch (const GPBException &e)
×
1428
    {
1429
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
×
1430
        delete poFeature;
×
1431
        return nullptr;
×
1432
    }
1433
}
1434

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

1439
GDALDataset *OGRMVTLayer::GetDataset()
1✔
1440
{
1441
    return m_poDS;
1✔
1442
}
1443

1444
/************************************************************************/
1445
/*                         StripDummyEntries()                           */
1446
/************************************************************************/
1447

1448
static CPLStringList StripDummyEntries(const CPLStringList &aosInput)
92✔
1449
{
1450
    CPLStringList aosOutput;
184✔
1451
    for (int i = 0; i < aosInput.Count(); i++)
353✔
1452
    {
1453
        if (aosInput[i] != CPLString(".") && aosInput[i] != CPLString("..") &&
631✔
1454
            CPLString(aosInput[i]).find(".properties") == std::string::npos)
370✔
1455
        {
1456
            aosOutput.AddString(aosInput[i]);
109✔
1457
        }
1458
    }
1459
    return aosOutput.Sort();
184✔
1460
}
1461

1462
/************************************************************************/
1463
/*                       OGRMVTDirectoryLayer()                         */
1464
/************************************************************************/
1465

1466
OGRMVTDirectoryLayer::OGRMVTDirectoryLayer(
21✔
1467
    OGRMVTDataset *poDS, const char *pszLayerName, const char *pszDirectoryName,
1468
    const CPLJSONObject &oFields, const CPLJSONArray &oAttributesFromTileStats,
1469
    bool bJsonField, OGRwkbGeometryType eGeomType, const OGREnvelope *psExtent)
21✔
1470
    : m_poDS(poDS), m_osDirName(pszDirectoryName), m_bJsonField(bJsonField)
21✔
1471
{
1472
    m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
21✔
1473
    SetDescription(m_poFeatureDefn->GetName());
21✔
1474
    m_poFeatureDefn->SetGeomType(eGeomType);
21✔
1475
    m_poFeatureDefn->Reference();
21✔
1476

1477
    m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
21✔
1478

1479
    if (m_bJsonField)
21✔
1480
    {
1481
        OGRFieldDefn oFieldDefnId("mvt_id", OFTInteger64);
2✔
1482
        m_poFeatureDefn->AddFieldDefn(&oFieldDefnId);
1✔
1483
    }
1484
    else
1485
    {
1486
        InitFields(oFields, oAttributesFromTileStats);
20✔
1487
    }
1488

1489
    m_nZ = atoi(CPLGetFilename(m_osDirName));
21✔
1490
    SetMetadataItem("ZOOM_LEVEL", CPLSPrintf("%d", m_nZ));
21✔
1491
    m_bUseReadDir = CPLTestBool(CPLGetConfigOption(
21✔
1492
        "MVT_USE_READDIR", (!STARTS_WITH(m_osDirName, "/vsicurl") &&
21✔
1493
                            !STARTS_WITH(m_osDirName, "http://") &&
21✔
1494
                            !STARTS_WITH(m_osDirName, "https://"))
17✔
1495
                               ? "YES"
1496
                               : "NO"));
1497
    if (m_bUseReadDir)
21✔
1498
    {
1499
        m_aosDirContent = VSIReadDirEx(m_osDirName, knMAX_FILES_PER_DIR);
16✔
1500
        if (m_aosDirContent.Count() >= knMAX_FILES_PER_DIR)
16✔
1501
        {
1502
            CPLDebug("MVT", "Disabling readdir");
×
1503
            m_aosDirContent.Clear();
×
1504
            m_bUseReadDir = false;
×
1505
        }
1506
        m_aosDirContent = StripDummyEntries(m_aosDirContent);
16✔
1507
    }
1508
    OGRMVTDirectoryLayer::ResetReading();
21✔
1509

1510
    if (psExtent)
21✔
1511
    {
1512
        m_sExtent = *psExtent;
17✔
1513
    }
1514

1515
    OGRMVTDirectoryLayer::SetSpatialFilter(nullptr);
21✔
1516

1517
    // If the metadata contains an empty fields object, this may be a sign
1518
    // that it doesn't know the schema. In that case check if a tile has
1519
    // attributes, and in that case create a json field.
1520
    if (!m_bJsonField && oFields.IsValid() && oFields.GetChildren().empty())
21✔
1521
    {
1522
        m_bJsonField = true;
11✔
1523
        OpenTileIfNeeded();
11✔
1524
        m_bJsonField = false;
11✔
1525

1526
        if (m_poCurrentTile)
11✔
1527
        {
1528
            OGRLayer *poUnderlyingLayer =
1529
                m_poCurrentTile->GetLayerByName(GetName());
9✔
1530
            // There is at least the mvt_id field
1531
            if (poUnderlyingLayer->GetLayerDefn()->GetFieldCount() > 1)
9✔
1532
            {
1533
                m_bJsonField = true;
×
1534
            }
1535
        }
1536
        OGRMVTDirectoryLayer::ResetReading();
11✔
1537
    }
1538

1539
    if (m_bJsonField)
21✔
1540
    {
1541
        OGRFieldDefn oFieldDefn("json", OFTString);
2✔
1542
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
1✔
1543
    }
1544
}
21✔
1545

1546
/************************************************************************/
1547
/*                      ~OGRMVTDirectoryLayer()                         */
1548
/************************************************************************/
1549

1550
OGRMVTDirectoryLayer::~OGRMVTDirectoryLayer()
42✔
1551
{
1552
    delete m_poCurrentTile;
21✔
1553
}
42✔
1554

1555
/************************************************************************/
1556
/*                          ResetReading()                              */
1557
/************************************************************************/
1558

1559
void OGRMVTDirectoryLayer::ResetReading()
135✔
1560
{
1561
    m_bEOF = false;
135✔
1562
    m_nXIndex = -1;
135✔
1563
    m_nYIndex = -1;
135✔
1564
    delete m_poCurrentTile;
135✔
1565
    m_poCurrentTile = nullptr;
135✔
1566
}
135✔
1567

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

1572
static bool IsBetween(int nVal, int nMin, int nMax)
128✔
1573
{
1574
    return nVal >= nMin && nVal <= nMax;
128✔
1575
}
1576

1577
/************************************************************************/
1578
/*                          ReadNewSubDir()                             */
1579
/************************************************************************/
1580

1581
void OGRMVTDirectoryLayer::ReadNewSubDir()
111✔
1582
{
1583
    delete m_poCurrentTile;
111✔
1584
    m_poCurrentTile = nullptr;
111✔
1585
    if (m_bUseReadDir || !m_aosDirContent.empty())
111✔
1586
    {
1587
        while (
2✔
1588
            m_nXIndex < m_aosDirContent.Count() &&
165✔
1589
            (CPLGetValueType(m_aosDirContent[m_nXIndex]) != CPL_VALUE_INTEGER ||
65✔
1590
             !IsBetween(atoi(m_aosDirContent[m_nXIndex]), m_nFilterMinX,
65✔
1591
                        m_nFilterMaxX)))
1592
        {
1593
            m_nXIndex++;
2✔
1594
        }
1595
    }
1596
    else
1597
    {
1598
        if (m_nXIndex < m_nFilterMinX)
13✔
1599
            m_nXIndex = m_nFilterMinX;
×
1600
        else if (m_nXIndex > m_nFilterMaxX)
13✔
1601
            m_nXIndex = (1 << m_nZ);
4✔
1602
    }
1603
    if (m_nXIndex < ((m_bUseReadDir || !m_aosDirContent.empty())
124✔
1604
                         ? m_aosDirContent.Count()
111✔
1605
                         : (1 << m_nZ)))
13✔
1606
    {
1607
        m_aosSubDirName =
1608
            CPLFormFilename(m_osDirName,
1609
                            (m_bUseReadDir || !m_aosDirContent.empty())
9✔
1610
                                ? m_aosDirContent[m_nXIndex]
63✔
1611
                                : CPLSPrintf("%d", m_nXIndex),
9✔
1612
                            nullptr);
144✔
1613
        if (m_bUseReadDir)
72✔
1614
        {
1615
            m_aosSubDirContent =
1616
                VSIReadDirEx(m_aosSubDirName, knMAX_FILES_PER_DIR);
63✔
1617
            if (m_aosSubDirContent.Count() >= knMAX_FILES_PER_DIR)
63✔
1618
            {
1619
                CPLDebug("MVT", "Disabling readdir");
×
1620
                m_aosSubDirContent.Clear();
×
1621
                m_bUseReadDir = false;
×
1622
            }
1623
            m_aosSubDirContent = StripDummyEntries(m_aosSubDirContent);
63✔
1624
        }
1625
        m_nYIndex = -1;
72✔
1626
        OpenTileIfNeeded();
72✔
1627
    }
1628
    else
1629
    {
1630
        m_bEOF = true;
39✔
1631
    }
1632
}
111✔
1633

1634
/************************************************************************/
1635
/*                            OpenTile()                                */
1636
/************************************************************************/
1637

1638
void OGRMVTDirectoryLayer::OpenTile()
72✔
1639
{
1640
    delete m_poCurrentTile;
72✔
1641
    m_poCurrentTile = nullptr;
72✔
1642
    if (m_nYIndex < (m_bUseReadDir ? m_aosSubDirContent.Count() : (1 << m_nZ)))
72✔
1643
    {
1644
        CPLString osFilename = CPLFormFilename(
1645
            m_aosSubDirName,
1646
            m_bUseReadDir ? m_aosSubDirContent[m_nYIndex]
63✔
1647
                          : CPLSPrintf("%d.%s", m_nYIndex,
9✔
1648
                                       m_poDS->m_osTileExtension.c_str()),
9✔
1649
            nullptr);
216✔
1650
        GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(), GA_ReadOnly);
72✔
1651
        oOpenInfo.papszOpenOptions = CSLSetNameValue(
72✔
1652
            nullptr, "METADATA_FILE",
1653
            m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
72✔
1654
        oOpenInfo.papszOpenOptions = CSLSetNameValue(
72✔
1655
            oOpenInfo.papszOpenOptions, "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
1656
        m_poCurrentTile = OGRMVTDataset::Open(&oOpenInfo);
72✔
1657
        CSLDestroy(oOpenInfo.papszOpenOptions);
72✔
1658

1659
        int nX = (m_bUseReadDir || !m_aosDirContent.empty())
9✔
1660
                     ? atoi(m_aosDirContent[m_nXIndex])
81✔
1661
                     : m_nXIndex;
72✔
1662
        int nY =
1663
            m_bUseReadDir ? atoi(m_aosSubDirContent[m_nYIndex]) : m_nYIndex;
72✔
1664
        m_nFIDBase = (static_cast<GIntBig>(nX) << m_nZ) | nY;
72✔
1665
    }
1666
}
72✔
1667

1668
/************************************************************************/
1669
/*                         OpenTileIfNeeded()                           */
1670
/************************************************************************/
1671

1672
void OGRMVTDirectoryLayer::OpenTileIfNeeded()
212✔
1673
{
1674
    if (m_nXIndex < 0)
212✔
1675
    {
1676
        m_nXIndex = 0;
71✔
1677
        ReadNewSubDir();
71✔
1678
    }
1679
    while ((m_poCurrentTile == nullptr && !m_bEOF) ||
536✔
1680
           (m_poCurrentTile != nullptr &&
212✔
1681
            m_poCurrentTile->GetLayerByName(GetName()) == nullptr))
168✔
1682
    {
1683
        m_nYIndex++;
112✔
1684
        if (m_bUseReadDir)
112✔
1685
        {
1686
            while (m_nYIndex < m_aosSubDirContent.Count() &&
162✔
1687
                   (CPLGetValueType(CPLGetBasename(
63✔
1688
                        m_aosSubDirContent[m_nYIndex])) != CPL_VALUE_INTEGER ||
63✔
1689
                    !IsBetween(atoi(m_aosSubDirContent[m_nYIndex]),
63✔
1690
                               m_nFilterMinY, m_nFilterMaxY)))
1691
            {
1692
                m_nYIndex++;
×
1693
            }
1694
        }
1695
        else
1696
        {
1697
            if (m_nYIndex < m_nFilterMinY)
13✔
1698
                m_nYIndex = m_nFilterMinY;
×
1699
            else if (m_nYIndex > m_nFilterMaxY)
13✔
1700
                m_nYIndex = (1 << m_nZ);
4✔
1701
        }
1702
        if (m_nYIndex ==
112✔
1703
            (m_bUseReadDir ? m_aosSubDirContent.Count() : (1 << m_nZ)))
112✔
1704
        {
1705
            m_nXIndex++;
40✔
1706
            ReadNewSubDir();
40✔
1707
        }
1708
        else
1709
        {
1710
            OpenTile();
72✔
1711
        }
1712
    }
1713
}
212✔
1714

1715
/************************************************************************/
1716
/*                         GetFeatureCount()                            */
1717
/************************************************************************/
1718

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

1742
/************************************************************************/
1743
/*                         SetSpatialFilter()                           */
1744
/************************************************************************/
1745

1746
void OGRMVTDirectoryLayer::SetSpatialFilter(OGRGeometry *poGeomIn)
45✔
1747
{
1748
    OGRLayer::SetSpatialFilter(poGeomIn);
45✔
1749

1750
    OGREnvelope sEnvelope;
45✔
1751
    if (m_poFilterGeom != nullptr)
45✔
1752
        sEnvelope = m_sFilterEnvelope;
7✔
1753
    if (m_sExtent.IsInit())
45✔
1754
    {
1755
        if (sEnvelope.IsInit())
41✔
1756
            sEnvelope.Intersect(m_sExtent);
7✔
1757
        else
1758
            sEnvelope = m_sExtent;
34✔
1759
    }
1760

1761
    if (sEnvelope.IsInit() && sEnvelope.MinX >= -10 * m_poDS->GetTileDim0() &&
81✔
1762
        sEnvelope.MinY >= -10 * m_poDS->GetTileDim0() &&
36✔
1763
        sEnvelope.MaxX <= 10 * m_poDS->GetTileDim0() &&
117✔
1764
        sEnvelope.MaxY <= 10 * m_poDS->GetTileDim0())
36✔
1765
    {
1766
        const double dfTileDim = m_poDS->GetTileDim0() / (1 << m_nZ);
36✔
1767
        m_nFilterMinX = std::max(
36✔
1768
            0, static_cast<int>(floor(
72✔
1769
                   (sEnvelope.MinX - m_poDS->GetTopXOrigin()) / dfTileDim)));
36✔
1770
        m_nFilterMinY = std::max(
36✔
1771
            0, static_cast<int>(floor(
72✔
1772
                   (m_poDS->GetTopYOrigin() - sEnvelope.MaxY) / dfTileDim)));
36✔
1773
        m_nFilterMaxX = std::min(
36✔
1774
            static_cast<int>(
72✔
1775
                ceil((sEnvelope.MaxX - m_poDS->GetTopXOrigin()) / dfTileDim)),
72✔
1776
            (1 << m_nZ) - 1);
36✔
1777
        m_nFilterMaxY = std::min(
36✔
1778
            static_cast<int>(
72✔
1779
                ceil((m_poDS->GetTopYOrigin() - sEnvelope.MinY) / dfTileDim)),
72✔
1780
            (1 << m_nZ) - 1);
36✔
1781
    }
1782
    else
1783
    {
1784
        m_nFilterMinX = 0;
9✔
1785
        m_nFilterMinY = 0;
9✔
1786
        m_nFilterMaxX = (1 << m_nZ) - 1;
9✔
1787
        m_nFilterMaxY = (1 << m_nZ) - 1;
9✔
1788
    }
1789
}
45✔
1790

1791
/************************************************************************/
1792
/*                           TestCapability()                           */
1793
/************************************************************************/
1794

1795
int OGRMVTDirectoryLayer::TestCapability(const char *pszCap)
35✔
1796
{
1797
    if (EQUAL(pszCap, OLCFastGetExtent))
35✔
1798
    {
1799
        return TRUE;
2✔
1800
    }
1801
    return OGRMVTLayerBase::TestCapability(pszCap);
33✔
1802
}
1803

1804
/************************************************************************/
1805
/*                             GetExtent()                              */
1806
/************************************************************************/
1807

1808
OGRErr OGRMVTDirectoryLayer::GetExtent(OGREnvelope *psExtent, int bForce)
4✔
1809
{
1810
    if (m_sExtent.IsInit())
4✔
1811
    {
1812
        *psExtent = m_sExtent;
4✔
1813
        return OGRERR_NONE;
4✔
1814
    }
1815
    return OGRLayer::GetExtent(psExtent, bForce);
×
1816
}
1817

1818
/************************************************************************/
1819
/*                         CreateFeatureFrom()                          */
1820
/************************************************************************/
1821

1822
OGRFeature *OGRMVTDirectoryLayer::CreateFeatureFrom(OGRFeature *poSrcFeature)
57✔
1823
{
1824

1825
    return OGRMVTCreateFeatureFrom(poSrcFeature, m_poFeatureDefn, m_bJsonField,
57✔
1826
                                   GetSpatialRef());
114✔
1827
}
1828

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

1833
OGRFeature *OGRMVTDirectoryLayer::GetNextRawFeature()
108✔
1834
{
1835
    while (true)
1836
    {
1837
        OpenTileIfNeeded();
108✔
1838
        if (m_poCurrentTile == nullptr)
108✔
1839
            return nullptr;
28✔
1840
        OGRLayer *poUnderlyingLayer =
1841
            m_poCurrentTile->GetLayerByName(GetName());
80✔
1842
        OGRFeature *poUnderlyingFeature = poUnderlyingLayer->GetNextFeature();
80✔
1843
        if (poUnderlyingFeature != nullptr)
80✔
1844
        {
1845
            OGRFeature *poFeature = CreateFeatureFrom(poUnderlyingFeature);
55✔
1846
            poFeature->SetFID(m_nFIDBase +
110✔
1847
                              (poUnderlyingFeature->GetFID() << (2 * m_nZ)));
55✔
1848
            delete poUnderlyingFeature;
55✔
1849
            return poFeature;
55✔
1850
        }
1851
        else
1852
        {
1853
            delete m_poCurrentTile;
25✔
1854
            m_poCurrentTile = nullptr;
25✔
1855
        }
1856
    }
25✔
1857
}
1858

1859
/************************************************************************/
1860
/*                           GetFeature()                               */
1861
/************************************************************************/
1862

1863
OGRFeature *OGRMVTDirectoryLayer::GetFeature(GIntBig nFID)
5✔
1864
{
1865
    const int nX = static_cast<int>(nFID & ((1 << m_nZ) - 1));
5✔
1866
    const int nY = static_cast<int>((nFID >> m_nZ) & ((1 << m_nZ) - 1));
5✔
1867
    const GIntBig nTileFID = nFID >> (2 * m_nZ);
5✔
1868
    const CPLString osFilename = CPLFormFilename(
1869
        CPLFormFilename(m_osDirName, CPLSPrintf("%d", nX), nullptr),
1870
        CPLSPrintf("%d.%s", nY, m_poDS->m_osTileExtension.c_str()), nullptr);
10✔
1871
    GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(), GA_ReadOnly);
5✔
1872
    oOpenInfo.papszOpenOptions = CSLSetNameValue(
5✔
1873
        nullptr, "METADATA_FILE",
1874
        m_bJsonField ? "" : m_poDS->m_osMetadataMemFilename.c_str());
5✔
1875
    oOpenInfo.papszOpenOptions = CSLSetNameValue(
5✔
1876
        oOpenInfo.papszOpenOptions, "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
1877
    GDALDataset *poTile = OGRMVTDataset::Open(&oOpenInfo);
5✔
1878
    CSLDestroy(oOpenInfo.papszOpenOptions);
5✔
1879
    OGRFeature *poFeature = nullptr;
5✔
1880
    if (poTile)
5✔
1881
    {
1882
        OGRLayer *poLayer = poTile->GetLayerByName(GetName());
5✔
1883
        if (poLayer)
5✔
1884
        {
1885
            OGRFeature *poUnderlyingFeature = poLayer->GetFeature(nTileFID);
5✔
1886
            if (poUnderlyingFeature)
5✔
1887
            {
1888
                poFeature = CreateFeatureFrom(poUnderlyingFeature);
2✔
1889
                poFeature->SetFID(nFID);
2✔
1890
            }
1891
            delete poUnderlyingFeature;
5✔
1892
        }
1893
    }
1894
    delete poTile;
5✔
1895
    return poFeature;
10✔
1896
}
1897

1898
/************************************************************************/
1899
/*                             GetDataset()                             */
1900
/************************************************************************/
1901

1902
GDALDataset *OGRMVTDirectoryLayer::GetDataset()
1✔
1903
{
1904
    return m_poDS;
1✔
1905
}
1906

1907
/************************************************************************/
1908
/*                           OGRMVTDataset()                            */
1909
/************************************************************************/
1910

1911
OGRMVTDataset::OGRMVTDataset(GByte *pabyData)
954✔
1912
    : m_pabyData(pabyData), m_poSRS(new OGRSpatialReference())
954✔
1913
{
1914
    m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
954✔
1915

1916
    m_bClip = CPLTestBool(CPLGetConfigOption("OGR_MVT_CLIP", "YES"));
954✔
1917

1918
    // Default WebMercator tiling scheme
1919
    InitWebMercatorTilingScheme(m_poSRS, m_dfTopXOrigin, m_dfTopYOrigin,
954✔
1920
                                m_dfTileDim0);
954✔
1921
}
954✔
1922

1923
/************************************************************************/
1924
/*                           ~OGRMVTDataset()                           */
1925
/************************************************************************/
1926

1927
OGRMVTDataset::~OGRMVTDataset()
1,908✔
1928
{
1929
    VSIFree(m_pabyData);
954✔
1930
    if (!m_osMetadataMemFilename.empty())
954✔
1931
        VSIUnlink(m_osMetadataMemFilename);
15✔
1932
    if (m_poSRS)
954✔
1933
        m_poSRS->Release();
951✔
1934
}
1,908✔
1935

1936
/************************************************************************/
1937
/*                              GetLayer()                              */
1938
/************************************************************************/
1939

1940
OGRLayer *OGRMVTDataset::GetLayer(int iLayer)
1,644✔
1941

1942
{
1943
    if (iLayer < 0 || iLayer >= GetLayerCount())
1,644✔
1944
        return nullptr;
4✔
1945
    return m_apoLayers[iLayer].get();
1,640✔
1946
}
1947

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

1952
static int OGRMVTDriverIdentify(GDALOpenInfo *poOpenInfo)
43,508✔
1953

1954
{
1955
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
43,508✔
1956
        return TRUE;
1,770✔
1957

1958
    if (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl"))
41,738✔
1959
    {
1960
        if (CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
×
1961
            CPL_VALUE_INTEGER)
1962
        {
1963
            return TRUE;
×
1964
        }
1965
    }
1966

1967
    if (poOpenInfo->bIsDirectory)
41,738✔
1968
    {
1969
        if (CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
492✔
1970
            CPL_VALUE_INTEGER)
1971
        {
1972
            VSIStatBufL sStat;
1973
            CPLString osMetadataFile(CPLFormFilename(
1974
                CPLGetPath(poOpenInfo->pszFilename), "metadata.json", nullptr));
26✔
1975
            const char *pszMetadataFile = CSLFetchNameValue(
52✔
1976
                poOpenInfo->papszOpenOptions, "METADATA_FILE");
26✔
1977
            if (pszMetadataFile)
26✔
1978
            {
1979
                osMetadataFile = pszMetadataFile;
4✔
1980
            }
1981
            if (!osMetadataFile.empty() &&
48✔
1982
                (STARTS_WITH(osMetadataFile, "http://") ||
22✔
1983
                 STARTS_WITH(osMetadataFile, "https://") ||
22✔
1984
                 VSIStatL(osMetadataFile, &sStat) == 0))
22✔
1985
            {
1986
                return TRUE;
20✔
1987
            }
1988
            if (pszMetadataFile == nullptr)
6✔
1989
            {
1990
                // tileserver-gl metadata file:
1991
                // If opening /path/to/foo/0, try looking for /path/to/foo.json
1992
                CPLString osParentDir(CPLGetPath(poOpenInfo->pszFilename));
2✔
1993
                osMetadataFile =
1994
                    CPLFormFilename(CPLGetPath(osParentDir),
1995
                                    CPLGetFilename(osParentDir), "json");
2✔
1996
                if (VSIStatL(osMetadataFile, &sStat) == 0)
2✔
1997
                {
1998
                    return TRUE;
2✔
1999
                }
2000
            }
2001

2002
            // At least 3 files, to include the dummy . and ..
2003
            CPLStringList aosDirContent(
2004
                VSIReadDirEx(poOpenInfo->pszFilename, 3));
4✔
2005
            aosDirContent = StripDummyEntries(aosDirContent);
4✔
2006
            if (!aosDirContent.empty() &&
8✔
2007
                CPLGetValueType(aosDirContent[0]) == CPL_VALUE_INTEGER)
4✔
2008
            {
2009
                CPLString osSubDir = CPLFormFilename(poOpenInfo->pszFilename,
4✔
2010
                                                     aosDirContent[0], nullptr);
4✔
2011
                // At least 3 files, to include the dummy . and ..
2012
                CPLStringList aosSubDirContent(VSIReadDirEx(osSubDir, 10));
4✔
2013
                aosSubDirContent = StripDummyEntries(aosSubDirContent);
4✔
2014
                CPLString osTileExtension(CSLFetchNameValueDef(
2015
                    poOpenInfo->papszOpenOptions, "TILE_EXTENSION", "pbf"));
4✔
2016
                for (int i = 0; i < aosSubDirContent.Count(); i++)
4✔
2017
                {
2018
                    if (CPLGetValueType(CPLGetBasename(aosSubDirContent[i])) ==
4✔
2019
                        CPL_VALUE_INTEGER)
2020
                    {
2021
                        CPLString osExtension(
2022
                            CPLGetExtension(aosSubDirContent[i]));
4✔
2023
                        if (EQUAL(osExtension, osTileExtension) ||
4✔
2024
                            EQUAL(osExtension, "mvt"))
×
2025
                        {
2026
                            return TRUE;
4✔
2027
                        }
2028
                    }
2029
                }
2030
            }
2031
        }
2032
        return FALSE;
466✔
2033
    }
2034

2035
    if (poOpenInfo->nHeaderBytes <= 2)
41,246✔
2036
        return FALSE;
39,265✔
2037

2038
    // GZip header ?
2039
    if (poOpenInfo->pabyHeader[0] == 0x1F && poOpenInfo->pabyHeader[1] == 0x8B)
1,981✔
2040
    {
2041
        // Prevent recursion
2042
        if (STARTS_WITH(poOpenInfo->pszFilename, "/vsigzip/"))
36✔
2043
        {
2044
            return FALSE;
×
2045
        }
2046
        CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
2047
                                      false);
72✔
2048
        GDALOpenInfo oOpenInfo(
2049
            (CPLString("/vsigzip/") + poOpenInfo->pszFilename).c_str(),
72✔
2050
            GA_ReadOnly);
72✔
2051
        return OGRMVTDriverIdentify(&oOpenInfo);
36✔
2052
    }
2053

2054
    // The GPB macros assume that the buffer is nul terminated,
2055
    // which is the case
2056
    const GByte *pabyData = reinterpret_cast<GByte *>(poOpenInfo->pabyHeader);
1,945✔
2057
    const GByte *const pabyDataStart = pabyData;
1,945✔
2058
    const GByte *pabyLayerStart;
2059
    const GByte *const pabyDataLimit = pabyData + poOpenInfo->nHeaderBytes;
1,945✔
2060
    const GByte *pabyLayerEnd = pabyDataLimit;
1,945✔
2061
    int nKey = 0;
1,945✔
2062
    unsigned int nLayerLength = 0;
1,945✔
2063
    bool bLayerNameFound = false;
1,945✔
2064
    bool bKeyFound = false;
1,945✔
2065
    bool bFeatureFound = false;
1,945✔
2066
    bool bVersionFound = false;
1,945✔
2067

2068
    try
2069
    {
2070
        READ_FIELD_KEY(nKey);
1,945✔
2071
        if (nKey != MAKE_KEY(knLAYER, WT_DATA))
1,943✔
2072
            return FALSE;
1,896✔
2073
        READ_VARUINT32(pabyData, pabyDataLimit, nLayerLength);
47✔
2074
        pabyLayerStart = pabyData;
47✔
2075

2076
        // Sanity check on layer length
2077
        if (nLayerLength < static_cast<unsigned>(poOpenInfo->nHeaderBytes -
47✔
2078
                                                 (pabyData - pabyDataStart)))
47✔
2079
        {
2080
            if (pabyData[nLayerLength] != MAKE_KEY(knLAYER, WT_DATA))
5✔
2081
                return FALSE;
1✔
2082
            pabyLayerEnd = pabyData + nLayerLength;
4✔
2083
        }
2084
        else if (nLayerLength > 10 * 1024 * 1024)
42✔
2085
        {
2086
            return FALSE;
×
2087
        }
2088

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

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

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

2328
                        pabyData = pabyDataGeometryEnd;
44✔
2329
                    }
2330
                    else
2331
                    {
2332
                        SKIP_UNKNOWN_FIELD(pabyData, pabyDataFeatureEnd, FALSE);
4✔
2333
                    }
2334
                }
2335

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

2419
    return bLayerNameFound && (bKeyFound || bFeatureFound || bVersionFound);
48✔
2420
}
2421

2422
/************************************************************************/
2423
/*                     LongLatToSphericalMercator()                     */
2424
/************************************************************************/
2425

2426
static void LongLatToSphericalMercator(double *x, double *y)
28✔
2427
{
2428
    double X = kmSPHERICAL_RADIUS * (*x) / 180 * M_PI;
28✔
2429
    double Y =
2430
        kmSPHERICAL_RADIUS * log(tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI));
28✔
2431
    *x = X;
28✔
2432
    *y = Y;
28✔
2433
}
28✔
2434

2435
/************************************************************************/
2436
/*                          LoadMetadata()                              */
2437
/************************************************************************/
2438

2439
static bool LoadMetadata(const CPLString &osMetadataFile,
900✔
2440
                         const CPLString &osMetadataContent,
2441
                         CPLJSONArray &oVectorLayers,
2442
                         CPLJSONArray &oTileStatLayers, CPLJSONObject &oBounds,
2443
                         OGRSpatialReference *poSRS, double &dfTopX,
2444
                         double &dfTopY, double &dfTileDim0,
2445
                         const CPLString &osMetadataMemFilename)
2446

2447
{
2448
    CPLJSONDocument oDoc;
1,800✔
2449

2450
    bool bLoadOK;
2451
    if (!osMetadataContent.empty())
900✔
2452
    {
2453
        bLoadOK = oDoc.LoadMemory(osMetadataContent);
3✔
2454
    }
2455
    else if (STARTS_WITH(osMetadataFile, "http://") ||
1,794✔
2456
             STARTS_WITH(osMetadataFile, "https://"))
897✔
2457
    {
2458
        bLoadOK = oDoc.LoadUrl(osMetadataFile, nullptr);
×
2459
    }
2460
    else
2461
    {
2462
        bLoadOK = oDoc.Load(osMetadataFile);
897✔
2463
    }
2464
    if (!bLoadOK)
900✔
2465
        return false;
2✔
2466

2467
    CPLJSONObject oCrs(oDoc.GetRoot().GetObj("crs"));
2,694✔
2468
    CPLJSONObject oTopX(oDoc.GetRoot().GetObj("tile_origin_upper_left_x"));
2,694✔
2469
    CPLJSONObject oTopY(oDoc.GetRoot().GetObj("tile_origin_upper_left_y"));
2,694✔
2470
    CPLJSONObject oTileDim0(oDoc.GetRoot().GetObj("tile_dimension_zoom_0"));
2,694✔
2471
    if (oCrs.IsValid() && oTopX.IsValid() && oTopY.IsValid() &&
900✔
2472
        oTileDim0.IsValid())
2✔
2473
    {
2474
        poSRS->SetFromUserInput(oCrs.ToString().c_str());
2✔
2475
        dfTopX = oTopX.ToDouble();
2✔
2476
        dfTopY = oTopY.ToDouble();
2✔
2477
        dfTileDim0 = oTileDim0.ToDouble();
2✔
2478
    }
2479

2480
    oVectorLayers.Deinit();
898✔
2481
    oTileStatLayers.Deinit();
898✔
2482

2483
    CPLJSONObject oJson = oDoc.GetRoot().GetObj("json");
2,694✔
2484
    if (!(oJson.IsValid() && oJson.GetType() == CPLJSONObject::Type::String))
898✔
2485
    {
2486
        oVectorLayers = oDoc.GetRoot().GetArray("vector_layers");
527✔
2487

2488
        oTileStatLayers = oDoc.GetRoot().GetArray("tilestats/layers");
527✔
2489
    }
2490
    else
2491
    {
2492
        CPLJSONDocument oJsonDoc;
371✔
2493
        if (!oJsonDoc.LoadMemory(oJson.ToString()))
371✔
2494
        {
2495
            return false;
1✔
2496
        }
2497

2498
        oVectorLayers = oJsonDoc.GetRoot().GetArray("vector_layers");
370✔
2499

2500
        oTileStatLayers = oJsonDoc.GetRoot().GetArray("tilestats/layers");
370✔
2501
    }
2502

2503
    oBounds = oDoc.GetRoot().GetObj("bounds");
897✔
2504

2505
    if (!osMetadataMemFilename.empty())
897✔
2506
    {
2507
        oDoc.Save(osMetadataMemFilename);
15✔
2508
    }
2509

2510
    return oVectorLayers.IsValid();
897✔
2511
}
2512

2513
/************************************************************************/
2514
/*                       ConvertFromWGS84()                             */
2515
/************************************************************************/
2516

2517
static void ConvertFromWGS84(OGRSpatialReference *poTargetSRS, double &dfX0,
15✔
2518
                             double &dfY0, double &dfX1, double &dfY1)
2519
{
2520
    OGRSpatialReference oSRS_EPSG3857;
30✔
2521
    oSRS_EPSG3857.SetFromUserInput(SRS_EPSG_3857);
15✔
2522

2523
    if (poTargetSRS->IsSame(&oSRS_EPSG3857))
15✔
2524
    {
2525
        LongLatToSphericalMercator(&dfX0, &dfY0);
14✔
2526
        LongLatToSphericalMercator(&dfX1, &dfY1);
14✔
2527
    }
2528
    else
2529
    {
2530
        OGRSpatialReference oSRS_EPSG4326;
2✔
2531
        oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
1✔
2532
        oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1✔
2533
        OGRCoordinateTransformation *poCT =
2534
            OGRCreateCoordinateTransformation(&oSRS_EPSG4326, poTargetSRS);
1✔
2535
        if (poCT)
1✔
2536
        {
2537
            poCT->Transform(1, &dfX0, &dfY0);
1✔
2538
            poCT->Transform(1, &dfX1, &dfY1);
1✔
2539
            delete poCT;
1✔
2540
        }
2541
    }
2542
}
15✔
2543

2544
/************************************************************************/
2545
/*                         OpenDirectory()                              */
2546
/************************************************************************/
2547

2548
GDALDataset *OGRMVTDataset::OpenDirectory(GDALOpenInfo *poOpenInfo)
21✔
2549

2550
{
2551
    const CPLString osZ(CPLGetFilename(poOpenInfo->pszFilename));
42✔
2552
    if (CPLGetValueType(osZ) != CPL_VALUE_INTEGER)
21✔
2553
        return nullptr;
1✔
2554

2555
    const int nZ = atoi(osZ);
20✔
2556
    if (nZ < 0 || nZ > 30)
20✔
2557
        return nullptr;
1✔
2558

2559
    CPLString osMetadataFile(CPLFormFilename(
2560
        CPLGetPath(poOpenInfo->pszFilename), "metadata.json", nullptr));
38✔
2561
    const char *pszMetadataFile =
2562
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
19✔
2563
    if (pszMetadataFile)
19✔
2564
    {
2565
        osMetadataFile = pszMetadataFile;
2✔
2566
    }
2567

2568
    const CPLString osTileExtension(CSLFetchNameValueDef(
2569
        poOpenInfo->papszOpenOptions, "TILE_EXTENSION", "pbf"));
38✔
2570
    bool bJsonField =
2571
        CPLFetchBool(poOpenInfo->papszOpenOptions, "JSON_FIELD", false);
19✔
2572
    VSIStatBufL sStat;
2573

2574
    bool bMetadataFileExists = false;
19✔
2575
    CPLString osMetadataContent;
38✔
2576
    if (STARTS_WITH(osMetadataFile, "http://") ||
33✔
2577
        STARTS_WITH(osMetadataFile, "https://"))
14✔
2578
    {
2579
        for (int i = 0; i < 2; i++)
8✔
2580
        {
2581
            if (pszMetadataFile == nullptr)
8✔
2582
                CPLPushErrorHandler(CPLQuietErrorHandler);
8✔
2583
            CPLHTTPResult *psResult = CPLHTTPFetch(osMetadataFile, nullptr);
8✔
2584
            if (pszMetadataFile == nullptr)
8✔
2585
                CPLPopErrorHandler();
8✔
2586
            if (psResult == nullptr)
8✔
2587
            {
2588
                osMetadataFile.clear();
×
2589
            }
2590
            else if (psResult->pszErrBuf != nullptr ||
8✔
2591
                     psResult->pabyData == nullptr)
3✔
2592
            {
2593
                CPLHTTPDestroyResult(psResult);
5✔
2594
                osMetadataFile.clear();
5✔
2595

2596
                if (i == 0 && pszMetadataFile == nullptr)
5✔
2597
                {
2598
                    // tileserver-gl metadata file:
2599
                    // If opening /path/to/foo/0, try looking for
2600
                    // /path/to/foo.json
2601
                    CPLString osParentDir(CPLGetPath(poOpenInfo->pszFilename));
6✔
2602
                    osMetadataFile =
2603
                        CPLFormFilename(CPLGetPath(osParentDir),
2604
                                        CPLGetFilename(osParentDir), "json");
3✔
2605
                    continue;
3✔
2606
                }
2✔
2607
            }
2608
            else
2609
            {
2610
                bMetadataFileExists = true;
3✔
2611
                osMetadataContent =
2612
                    reinterpret_cast<const char *>(psResult->pabyData);
3✔
2613
                CPLHTTPDestroyResult(psResult);
3✔
2614
            }
2615
            break;
5✔
2616
        }
2617
    }
2618
    else if (!osMetadataFile.empty())
14✔
2619
    {
2620
        bMetadataFileExists = (VSIStatL(osMetadataFile, &sStat) == 0);
12✔
2621
        if (!bMetadataFileExists && pszMetadataFile == nullptr)
12✔
2622
        {
2623
            // tileserver-gl metadata file:
2624
            // If opening /path/to/foo/0, try looking for /path/to/foo.json
2625
            CPLString osParentDir(CPLGetPath(poOpenInfo->pszFilename));
1✔
2626
            osMetadataFile = CPLFormFilename(
2627
                CPLGetPath(osParentDir), CPLGetFilename(osParentDir), "json");
1✔
2628
            bMetadataFileExists = (VSIStatL(osMetadataFile, &sStat) == 0);
1✔
2629
        }
2630
    }
2631

2632
    if (!bMetadataFileExists)
19✔
2633
    {
2634
        // If we don't have a metadata file, iterate through all tiles to
2635
        // establish the layer definitions.
2636
        OGRMVTDataset *poDS = nullptr;
4✔
2637
        bool bTryToListDir =
4✔
2638
            !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl/") &&
8✔
2639
            !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl_streaming/") &&
4✔
2640
            !STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl?") &&
4✔
2641
            !STARTS_WITH(poOpenInfo->pszFilename, "http://") &&
10✔
2642
            !STARTS_WITH(poOpenInfo->pszFilename, "https://");
2✔
2643
        CPLStringList aosDirContent;
4✔
2644
        if (bTryToListDir)
4✔
2645
        {
2646
            aosDirContent = VSIReadDir(poOpenInfo->pszFilename);
2✔
2647
            aosDirContent = StripDummyEntries(aosDirContent);
2✔
2648
        }
2649
        const int nMaxTiles = atoi(CSLFetchNameValueDef(
8✔
2650
            poOpenInfo->papszOpenOptions,
4✔
2651
            "TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN", "1000"));
2652
        int nCountTiles = 0;
4✔
2653
        int nFailedAttempts = 0;
4✔
2654
        for (int i = 0; i < (bTryToListDir ? aosDirContent.Count() : (1 << nZ));
9✔
2655
             i++)
2656
        {
2657
            if (bTryToListDir)
5✔
2658
            {
2659
                if (CPLGetValueType(aosDirContent[i]) != CPL_VALUE_INTEGER)
3✔
2660
                {
2661
                    continue;
×
2662
                }
2663
            }
2664
            CPLString osSubDir = CPLFormFilename(
2665
                poOpenInfo->pszFilename,
5✔
2666
                bTryToListDir ? aosDirContent[i] : CPLSPrintf("%d", i),
5✔
2667
                nullptr);
10✔
2668
            CPLStringList aosSubDirContent;
5✔
2669
            if (bTryToListDir)
5✔
2670
            {
2671
                aosSubDirContent = VSIReadDir(osSubDir);
3✔
2672
                aosSubDirContent = StripDummyEntries(aosSubDirContent);
3✔
2673
            }
2674
            for (int j = 0;
12✔
2675
                 j < (bTryToListDir ? aosSubDirContent.Count() : (1 << nZ));
12✔
2676
                 j++)
2677
            {
2678
                if (bTryToListDir)
7✔
2679
                {
2680
                    if (CPLGetValueType(CPLGetBasename(aosSubDirContent[j])) !=
5✔
2681
                        CPL_VALUE_INTEGER)
2682
                    {
2683
                        continue;
×
2684
                    }
2685
                }
2686
                CPLString osFilename(CPLFormFilename(
2687
                    osSubDir,
2688
                    bTryToListDir
2689
                        ? aosSubDirContent[j]
5✔
2690
                        : CPLSPrintf("%d.%s", j, osTileExtension.c_str()),
2✔
2691
                    nullptr));
14✔
2692
                GDALOpenInfo oOpenInfo(("MVT:" + osFilename).c_str(),
7✔
2693
                                       GA_ReadOnly);
7✔
2694
                oOpenInfo.papszOpenOptions =
7✔
2695
                    CSLSetNameValue(nullptr, "METADATA_FILE", "");
7✔
2696
                oOpenInfo.papszOpenOptions =
7✔
2697
                    CSLSetNameValue(oOpenInfo.papszOpenOptions,
7✔
2698
                                    "DO_NOT_ERROR_ON_MISSING_TILE", "YES");
2699
                auto poTileDS = OGRMVTDataset::Open(&oOpenInfo);
7✔
2700
                if (poTileDS)
7✔
2701
                {
2702
                    if (poDS == nullptr)
6✔
2703
                    {
2704
                        poDS = new OGRMVTDataset(nullptr);
3✔
2705
                        poDS->m_osTileExtension = osTileExtension;
3✔
2706
                        poDS->SetDescription(poOpenInfo->pszFilename);
3✔
2707
                        poDS->m_bClip =
3✔
2708
                            CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP",
3✔
2709
                                         poDS->m_bClip);
3✔
2710
                    }
2711

2712
                    for (int k = 0; k < poTileDS->GetLayerCount(); k++)
14✔
2713
                    {
2714
                        OGRLayer *poTileLayer = poTileDS->GetLayer(k);
8✔
2715
                        OGRFeatureDefn *poTileLDefn =
2716
                            poTileLayer->GetLayerDefn();
8✔
2717
                        OGRwkbGeometryType eTileGeomType =
2718
                            poTileLDefn->GetGeomType();
8✔
2719
                        OGRwkbGeometryType eTileGeomTypeColl =
2720
                            OGR_GT_GetCollection(eTileGeomType);
8✔
2721
                        if (eTileGeomTypeColl != wkbUnknown &&
8✔
2722
                            eTileGeomTypeColl != eTileGeomType)
2723
                        {
2724
                            eTileGeomType = eTileGeomTypeColl;
4✔
2725
                        }
2726

2727
                        OGRLayer *poLayer =
2728
                            poDS->GetLayerByName(poTileLayer->GetName());
8✔
2729
                        OGRFeatureDefn *poLDefn;
2730
                        if (poLayer == nullptr)
8✔
2731
                        {
2732
                            CPLJSONObject oFields;
8✔
2733
                            oFields.Deinit();
4✔
2734
                            poDS->m_apoLayers.push_back(
4✔
2735
                                std::unique_ptr<OGRLayer>(
8✔
2736
                                    new OGRMVTDirectoryLayer(
2737
                                        poDS, poTileLayer->GetName(),
4✔
2738
                                        poOpenInfo->pszFilename, oFields,
4✔
2739
                                        CPLJSONArray(), bJsonField, wkbUnknown,
8✔
2740
                                        nullptr)));
4✔
2741
                            poLayer = poDS->m_apoLayers.back().get();
4✔
2742
                            poLDefn = poLayer->GetLayerDefn();
4✔
2743
                            poLDefn->SetGeomType(eTileGeomType);
4✔
2744
                        }
2745
                        else
2746
                        {
2747
                            poLDefn = poLayer->GetLayerDefn();
4✔
2748
                            if (poLayer->GetGeomType() != eTileGeomType)
4✔
2749
                            {
2750
                                poLDefn->SetGeomType(wkbUnknown);
×
2751
                            }
2752
                        }
2753

2754
                        if (!bJsonField)
8✔
2755
                        {
2756
                            for (int l = 1; l < poTileLDefn->GetFieldCount();
11✔
2757
                                 l++)
2758
                            {
2759
                                OGRFieldDefn *poTileFDefn =
2760
                                    poTileLDefn->GetFieldDefn(l);
4✔
2761
                                int nFieldIdx = poLDefn->GetFieldIndex(
4✔
2762
                                    poTileFDefn->GetNameRef());
4✔
2763
                                if (nFieldIdx < 0)
4✔
2764
                                {
2765
                                    poLDefn->AddFieldDefn(poTileFDefn);
1✔
2766
                                }
2767
                                else
2768
                                {
2769
                                    MergeFieldDefn(
6✔
2770
                                        poLDefn->GetFieldDefn(nFieldIdx),
3✔
2771
                                        poTileFDefn->GetType(),
2772
                                        poTileFDefn->GetSubType());
2773
                                }
2774
                            }
2775
                        }
2776
                    }
2777
                    nCountTiles++;
6✔
2778
                }
2779
                else if (!bTryToListDir)
1✔
2780
                {
2781
                    nFailedAttempts++;
1✔
2782
                }
2783
                delete poTileDS;
7✔
2784
                CSLDestroy(oOpenInfo.papszOpenOptions);
7✔
2785

2786
                if (nFailedAttempts == 10)
7✔
2787
                    break;
×
2788
                if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
7✔
2789
                    break;
×
2790
            }
2791

2792
            if (nFailedAttempts == 10)
5✔
2793
                break;
×
2794
            if (nMaxTiles > 0 && nCountTiles == nMaxTiles)
5✔
2795
                break;
×
2796
        }
2797
        return poDS;
4✔
2798
    }
2799

2800
    CPLJSONArray oVectorLayers;
30✔
2801
    CPLJSONArray oTileStatLayers;
30✔
2802
    CPLJSONObject oBounds;
30✔
2803

2804
    OGRMVTDataset *poDS = new OGRMVTDataset(nullptr);
15✔
2805

2806
    const CPLString osMetadataMemFilename =
2807
        CPLSPrintf("/vsimem/%p_metadata.json", poDS);
30✔
2808
    if (!LoadMetadata(osMetadataFile, osMetadataContent, oVectorLayers,
15✔
2809
                      oTileStatLayers, oBounds, poDS->m_poSRS,
2810
                      poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
15✔
2811
                      poDS->m_dfTileDim0, osMetadataMemFilename))
15✔
2812
    {
2813
        delete poDS;
×
2814
        return nullptr;
×
2815
    }
2816

2817
    OGREnvelope sExtent;
15✔
2818
    bool bExtentValid = false;
15✔
2819
    if (oBounds.IsValid() && oBounds.GetType() == CPLJSONObject::Type::String)
15✔
2820
    {
2821
        CPLStringList aosTokens(
2822
            CSLTokenizeString2(oBounds.ToString().c_str(), ",", 0));
42✔
2823
        if (aosTokens.Count() == 4)
14✔
2824
        {
2825
            double dfX0 = CPLAtof(aosTokens[0]);
14✔
2826
            double dfY0 = CPLAtof(aosTokens[1]);
14✔
2827
            double dfX1 = CPLAtof(aosTokens[2]);
14✔
2828
            double dfY1 = CPLAtof(aosTokens[3]);
14✔
2829
            ConvertFromWGS84(poDS->m_poSRS, dfX0, dfY0, dfX1, dfY1);
14✔
2830
            bExtentValid = true;
14✔
2831
            sExtent.MinX = dfX0;
14✔
2832
            sExtent.MinY = dfY0;
14✔
2833
            sExtent.MaxX = dfX1;
14✔
2834
            sExtent.MaxY = dfY1;
14✔
2835
        }
2836
    }
2837
    else if (oBounds.IsValid() &&
2✔
2838
             oBounds.GetType() == CPLJSONObject::Type::Array)
1✔
2839
    {
2840
        // Cf https://free.tilehosting.com/data/v3.json?key=THE_KEY
2841
        CPLJSONArray oBoundArray = oBounds.ToArray();
2✔
2842
        if (oBoundArray.Size() == 4)
1✔
2843
        {
2844
            bExtentValid = true;
1✔
2845
            sExtent.MinX = oBoundArray[0].ToDouble();
1✔
2846
            sExtent.MinY = oBoundArray[1].ToDouble();
1✔
2847
            sExtent.MaxX = oBoundArray[2].ToDouble();
1✔
2848
            sExtent.MaxY = oBoundArray[3].ToDouble();
1✔
2849
            ConvertFromWGS84(poDS->m_poSRS, sExtent.MinX, sExtent.MinY,
1✔
2850
                             sExtent.MaxX, sExtent.MaxY);
2851
        }
2852
    }
2853

2854
    poDS->SetDescription(poOpenInfo->pszFilename);
15✔
2855
    poDS->m_bClip =
15✔
2856
        CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
15✔
2857
    poDS->m_osTileExtension = osTileExtension;
15✔
2858
    poDS->m_osMetadataMemFilename = osMetadataMemFilename;
15✔
2859
    for (int i = 0; i < oVectorLayers.Size(); i++)
32✔
2860
    {
2861
        CPLJSONObject oId = oVectorLayers[i].GetObj("id");
51✔
2862
        if (oId.IsValid() && oId.GetType() == CPLJSONObject::Type::String)
17✔
2863
        {
2864
            OGRwkbGeometryType eGeomType = wkbUnknown;
17✔
2865
            if (oTileStatLayers.IsValid())
17✔
2866
            {
2867
                eGeomType = OGRMVTFindGeomTypeFromTileStat(
16✔
2868
                    oTileStatLayers, oId.ToString().c_str());
32✔
2869
            }
2870

2871
            CPLJSONObject oFields = oVectorLayers[i].GetObj("fields");
51✔
2872
            CPLJSONArray oAttributesFromTileStats =
2873
                OGRMVTFindAttributesFromTileStat(oTileStatLayers,
2874
                                                 oId.ToString().c_str());
34✔
2875

2876
            poDS->m_apoLayers.push_back(
17✔
2877
                std::unique_ptr<OGRLayer>(new OGRMVTDirectoryLayer(
34✔
2878
                    poDS, oId.ToString().c_str(), poOpenInfo->pszFilename,
34✔
2879
                    oFields, oAttributesFromTileStats, bJsonField, eGeomType,
2880
                    (bExtentValid) ? &sExtent : nullptr)));
17✔
2881
        }
2882
    }
2883

2884
    return poDS;
15✔
2885
}
2886

2887
/************************************************************************/
2888
/*                                Open()                                */
2889
/************************************************************************/
2890

2891
GDALDataset *OGRMVTDataset::Open(GDALOpenInfo *poOpenInfo)
963✔
2892

2893
{
2894
    if (!OGRMVTDriverIdentify(poOpenInfo) || poOpenInfo->eAccess == GA_Update)
963✔
2895
        return nullptr;
×
2896

2897
    VSILFILE *fp = poOpenInfo->fpL;
963✔
2898
    CPLString osFilename(poOpenInfo->pszFilename);
1,926✔
2899
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "MVT:"))
963✔
2900
    {
2901
        osFilename = poOpenInfo->pszFilename + strlen("MVT:");
927✔
2902
        if (STARTS_WITH(osFilename, "/vsigzip/http://") ||
1,854✔
2903
            STARTS_WITH(osFilename, "/vsigzip/https://"))
927✔
2904
        {
2905
            osFilename = osFilename.substr(strlen("/vsigzip/"));
×
2906
        }
2907

2908
        // If the filename has no extension and is a directory, consider
2909
        // we open a directory
2910
        VSIStatBufL sStat;
2911
        if (!STARTS_WITH(osFilename, "/vsigzip/") &&
927✔
2912
            strchr((CPLGetFilename(osFilename)), '.') == nullptr &&
926✔
2913
            VSIStatL(osFilename, &sStat) == 0 && VSI_ISDIR(sStat.st_mode))
1,853✔
2914
        {
2915
            GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
3✔
2916
            oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
3✔
2917
            GDALDataset *poDS = OpenDirectory(&oOpenInfo);
3✔
2918
            if (poDS)
3✔
2919
                poDS->SetDescription(poOpenInfo->pszFilename);
1✔
2920
            return poDS;
3✔
2921
        }
2922

2923
        // For a network resource, if the filename is an integer, consider it
2924
        // is a directory and open as such
2925
        if ((STARTS_WITH(osFilename, "/vsicurl") ||
924✔
2926
             STARTS_WITH(osFilename, "http://") ||
924✔
2927
             STARTS_WITH(osFilename, "https://")) &&
1,848✔
2928
            CPLGetValueType(CPLGetFilename(osFilename)) == CPL_VALUE_INTEGER)
15✔
2929
        {
2930
            GDALOpenInfo oOpenInfo(osFilename, GA_ReadOnly);
5✔
2931
            oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
5✔
2932
            GDALDataset *poDS = OpenDirectory(&oOpenInfo);
5✔
2933
            if (poDS)
5✔
2934
                poDS->SetDescription(poOpenInfo->pszFilename);
4✔
2935
            return poDS;
5✔
2936
        }
2937

2938
        if (!STARTS_WITH(osFilename, "http://") &&
1,828✔
2939
            !STARTS_WITH(osFilename, "https://"))
909✔
2940
        {
2941
            CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES",
2942
                                          "NO", false);
1,818✔
2943
            CPLConfigOptionSetter oSetter2("CPL_VSIL_GZIP_SAVE_INFO", "NO",
2944
                                           false);
1,818✔
2945
            fp = VSIFOpenL(osFilename, "rb");
909✔
2946
            // Is it a gzipped file ?
2947
            if (fp && !STARTS_WITH(osFilename, "/vsigzip/"))
909✔
2948
            {
2949
                GByte abyHeaderBytes[2] = {0, 0};
907✔
2950
                VSIFReadL(abyHeaderBytes, 2, 1, fp);
907✔
2951
                if (abyHeaderBytes[0] == 0x1F && abyHeaderBytes[1] == 0x8B)
907✔
2952
                {
2953
                    VSIFCloseL(fp);
378✔
2954
                    fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
378✔
2955
                }
2956
            }
2957
        }
2958
    }
2959
    else if (poOpenInfo->bIsDirectory ||
59✔
2960
             (STARTS_WITH(poOpenInfo->pszFilename, "/vsicurl") &&
23✔
2961
              CPLGetValueType(CPLGetFilename(poOpenInfo->pszFilename)) ==
×
2962
                  CPL_VALUE_INTEGER))
2963
    {
2964
        return OpenDirectory(poOpenInfo);
13✔
2965
    }
2966
    // Is it a gzipped file ?
2967
    else if (poOpenInfo->nHeaderBytes >= 2 &&
23✔
2968
             poOpenInfo->pabyHeader[0] == 0x1F &&
23✔
2969
             poOpenInfo->pabyHeader[1] == 0x8B)
18✔
2970
    {
2971
        CPLConfigOptionSetter oSetter("CPL_VSIL_GZIP_WRITE_PROPERTIES", "NO",
2972
                                      false);
18✔
2973
        fp = VSIFOpenL(("/vsigzip/" + osFilename).c_str(), "rb");
18✔
2974
    }
2975
    else
2976
    {
2977
        poOpenInfo->fpL = nullptr;
5✔
2978
    }
2979
    if (fp == nullptr && !STARTS_WITH(osFilename, "http://") &&
943✔
2980
        !STARTS_WITH(osFilename, "https://"))
1✔
2981
    {
2982
        return nullptr;
1✔
2983
    }
2984

2985
    CPLString osY = CPLGetBasename(osFilename);
1,882✔
2986
    CPLString osX = CPLGetBasename(CPLGetPath(osFilename));
1,882✔
2987
    CPLString osZ = CPLGetBasename(CPLGetPath(CPLGetPath(osFilename)));
1,882✔
2988
    size_t nPos = osY.find('.');
941✔
2989
    if (nPos != std::string::npos)
941✔
2990
        osY.resize(nPos);
×
2991

2992
    CPLString osMetadataFile;
1,882✔
2993
    if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE"))
941✔
2994
    {
2995
        osMetadataFile =
2996
            CSLFetchNameValue(poOpenInfo->papszOpenOptions, "METADATA_FILE");
920✔
2997
    }
2998
    else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
21✔
2999
             CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
33✔
3000
             CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
12✔
3001
    {
3002
        osMetadataFile =
3003
            CPLFormFilename(CPLGetPath(CPLGetPath(CPLGetPath(osFilename))),
3004
                            "metadata.json", nullptr);
12✔
3005
        if (osMetadataFile.find("/vsigzip/") == 0)
12✔
3006
        {
3007
            osMetadataFile = osMetadataFile.substr(strlen("/vsigzip/"));
2✔
3008
        }
3009
        VSIStatBufL sStat;
3010
        if (osMetadataFile.empty() || VSIStatL(osMetadataFile, &sStat) != 0)
12✔
3011
        {
3012
            osMetadataFile.clear();
1✔
3013
        }
3014
    }
3015

3016
    if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X") &&
941✔
3017
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y") &&
1,748✔
3018
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z"))
807✔
3019
    {
3020
        osX = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "X");
807✔
3021
        osY = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Y");
807✔
3022
        osZ = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "Z");
807✔
3023
    }
3024

3025
    GByte *pabyDataMod;
3026
    size_t nFileSize;
3027

3028
    if (fp == nullptr)
941✔
3029
    {
3030
        bool bSilenceErrors =
3031
            CPLFetchBool(poOpenInfo->papszOpenOptions,
10✔
3032
                         "DO_NOT_ERROR_ON_MISSING_TILE", false);
3033
        if (bSilenceErrors)
10✔
3034
            CPLPushErrorHandler(CPLQuietErrorHandler);
9✔
3035
        CPLHTTPResult *psResult = CPLHTTPFetch(osFilename, nullptr);
10✔
3036
        if (bSilenceErrors)
10✔
3037
            CPLPopErrorHandler();
9✔
3038
        if (psResult == nullptr)
10✔
3039
            return nullptr;
×
3040
        if (psResult->pszErrBuf != nullptr)
10✔
3041
        {
3042
            CPLHTTPDestroyResult(psResult);
5✔
3043
            return nullptr;
5✔
3044
        }
3045
        pabyDataMod = psResult->pabyData;
5✔
3046
        if (pabyDataMod == nullptr)
5✔
3047
        {
3048
            CPLHTTPDestroyResult(psResult);
×
3049
            return nullptr;
×
3050
        }
3051
        nFileSize = psResult->nDataLen;
5✔
3052
        psResult->pabyData = nullptr;
5✔
3053
        CPLHTTPDestroyResult(psResult);
5✔
3054

3055
        // zlib decompress if needed
3056
        if (nFileSize > 2 && pabyDataMod[0] == 0x1F && pabyDataMod[1] == 0x8B)
5✔
3057
        {
3058
            size_t nOutBytes = 0;
5✔
3059
            void *pUncompressed =
3060
                CPLZLibInflate(pabyDataMod, nFileSize, nullptr, 0, &nOutBytes);
5✔
3061
            CPLFree(pabyDataMod);
5✔
3062
            if (pUncompressed == nullptr)
5✔
3063
            {
3064
                return nullptr;
×
3065
            }
3066
            pabyDataMod = static_cast<GByte *>(pUncompressed);
5✔
3067
            nFileSize = nOutBytes;
5✔
3068
        }
3069
    }
3070
    else
3071
    {
3072
        // Check file size and ingest into memory
3073
        VSIFSeekL(fp, 0, SEEK_END);
931✔
3074
        vsi_l_offset nFileSizeL = VSIFTellL(fp);
931✔
3075
        if (nFileSizeL > 10 * 1024 * 1024)
931✔
3076
        {
3077
            VSIFCloseL(fp);
×
3078
            return nullptr;
×
3079
        }
3080
        nFileSize = static_cast<size_t>(nFileSizeL);
931✔
3081
        pabyDataMod = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nFileSize + 1));
931✔
3082
        if (pabyDataMod == nullptr)
931✔
3083
        {
3084
            VSIFCloseL(fp);
×
3085
            return nullptr;
×
3086
        }
3087
        VSIFSeekL(fp, 0, SEEK_SET);
931✔
3088
        VSIFReadL(pabyDataMod, 1, nFileSize, fp);
931✔
3089
        pabyDataMod[nFileSize] = 0;
931✔
3090
        VSIFCloseL(fp);
931✔
3091
    }
3092

3093
    const GByte *pabyData = pabyDataMod;
936✔
3094

3095
    // First scan to browse through layers
3096
    const GByte *pabyDataLimit = pabyData + nFileSize;
936✔
3097
    int nKey = 0;
936✔
3098
    OGRMVTDataset *poDS = new OGRMVTDataset(pabyDataMod);
936✔
3099
    poDS->SetDescription(poOpenInfo->pszFilename);
936✔
3100
    poDS->m_bClip =
936✔
3101
        CPLFetchBool(poOpenInfo->papszOpenOptions, "CLIP", poDS->m_bClip);
936✔
3102

3103
    if (!(CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
1,841✔
3104
          CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
905✔
3105
          CPLGetValueType(osZ) == CPL_VALUE_INTEGER))
905✔
3106
    {
3107
        // See
3108
        // https://github.com/mapbox/mvt-fixtures/tree/master/real-world/compressed
3109
        int nX = 0;
31✔
3110
        int nY = 0;
31✔
3111
        int nZ = 0;
31✔
3112
        CPLString osBasename(CPLGetBasename(CPLGetBasename(osFilename)));
62✔
3113
        if (sscanf(osBasename, "%d-%d-%d", &nZ, &nX, &nY) == 3 ||
61✔
3114
            sscanf(osBasename, "%d_%d_%d", &nZ, &nX, &nY) == 3)
30✔
3115
        {
3116
            osX = CPLSPrintf("%d", nX);
1✔
3117
            osY = CPLSPrintf("%d", nY);
1✔
3118
            osZ = CPLSPrintf("%d", nZ);
1✔
3119
        }
3120
    }
3121

3122
    CPLJSONArray oVectorLayers;
1,872✔
3123
    oVectorLayers.Deinit();
936✔
3124

3125
    CPLJSONArray oTileStatLayers;
1,872✔
3126
    oTileStatLayers.Deinit();
936✔
3127

3128
    if (!osMetadataFile.empty())
936✔
3129
    {
3130
        CPLJSONObject oBounds;
885✔
3131
        LoadMetadata(osMetadataFile, CPLString(), oVectorLayers,
885✔
3132
                     oTileStatLayers, oBounds, poDS->m_poSRS,
3133
                     poDS->m_dfTopXOrigin, poDS->m_dfTopYOrigin,
885✔
3134
                     poDS->m_dfTileDim0, CPLString());
1,770✔
3135
    }
3136

3137
    const char *pszGeorefTopX =
3138
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPX");
936✔
3139
    const char *pszGeorefTopY =
3140
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TOPY");
936✔
3141
    const char *pszGeorefTileDimX =
3142
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMX");
936✔
3143
    const char *pszGeorefTileDimY =
3144
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "GEOREF_TILEDIMY");
936✔
3145
    if (pszGeorefTopX && pszGeorefTopY && pszGeorefTileDimX &&
936✔
3146
        pszGeorefTileDimY)
3147
    {
3148
        poDS->m_bGeoreferenced = true;
3✔
3149
        poDS->m_dfTileDimX = CPLAtof(pszGeorefTileDimX);
3✔
3150
        poDS->m_dfTileDimY = CPLAtof(pszGeorefTileDimY);
3✔
3151
        poDS->m_dfTopX = CPLAtof(pszGeorefTopX);
3✔
3152
        poDS->m_dfTopY = CPLAtof(pszGeorefTopY);
3✔
3153
        poDS->m_poSRS->Release();
3✔
3154
        poDS->m_poSRS = nullptr;
3✔
3155
    }
3156
    else if (CPLGetValueType(osX) == CPL_VALUE_INTEGER &&
933✔
3157
             CPLGetValueType(osY) == CPL_VALUE_INTEGER &&
1,839✔
3158
             CPLGetValueType(osZ) == CPL_VALUE_INTEGER)
906✔
3159
    {
3160
        int nX = atoi(osX);
906✔
3161
        int nY = atoi(osY);
906✔
3162
        int nZ = atoi(osZ);
906✔
3163
        if (nZ >= 0 && nZ < 30 && nX >= 0 && nX < (1 << nZ) && nY >= 0 &&
906✔
3164
            nY < (1 << nZ))
906✔
3165
        {
3166
            poDS->m_bGeoreferenced = true;
906✔
3167
            poDS->m_dfTileDimX = poDS->m_dfTileDim0 / (1 << nZ);
906✔
3168
            poDS->m_dfTileDimY = poDS->m_dfTileDimX;
906✔
3169
            poDS->m_dfTopX = poDS->m_dfTopXOrigin + nX * poDS->m_dfTileDimX;
906✔
3170
            poDS->m_dfTopY = poDS->m_dfTopYOrigin - nY * poDS->m_dfTileDimY;
906✔
3171
        }
3172
    }
3173

3174
    try
3175
    {
3176
        while (pabyData < pabyDataLimit)
1,952✔
3177
        {
3178
            READ_FIELD_KEY(nKey);
1,016✔
3179
            if (nKey == MAKE_KEY(knLAYER, WT_DATA))
1,016✔
3180
            {
3181
                unsigned int nLayerSize = 0;
1,016✔
3182
                READ_SIZE(pabyData, pabyDataLimit, nLayerSize);
1,016✔
3183
                const GByte *pabyDataLayer = pabyData;
1,016✔
3184
                const GByte *pabyDataLimitLayer = pabyData + nLayerSize;
1,016✔
3185
                while (pabyData < pabyDataLimitLayer)
1,255✔
3186
                {
3187
                    READ_VARINT32(pabyData, pabyDataLimitLayer, nKey);
1,255✔
3188
                    if (nKey == MAKE_KEY(knLAYER_NAME, WT_DATA))
1,255✔
3189
                    {
3190
                        char *pszLayerName = nullptr;
1,016✔
3191
                        READ_TEXT(pabyData, pabyDataLimitLayer, pszLayerName);
1,016✔
3192

3193
                        CPLJSONObject oFields;
2,032✔
3194
                        oFields.Deinit();
1,016✔
3195
                        if (oVectorLayers.IsValid())
1,016✔
3196
                        {
3197
                            for (int i = 0; i < oVectorLayers.Size(); i++)
1,064✔
3198
                            {
3199
                                CPLJSONObject oId =
3200
                                    oVectorLayers[i].GetObj("id");
2,128✔
3201
                                if (oId.IsValid() &&
2,128✔
3202
                                    oId.GetType() ==
1,064✔
3203
                                        CPLJSONObject::Type::String)
3204
                                {
3205
                                    if (oId.ToString() == pszLayerName)
1,064✔
3206
                                    {
3207
                                        oFields =
3208
                                            oVectorLayers[i].GetObj("fields");
940✔
3209
                                        break;
940✔
3210
                                    }
3211
                                }
3212
                            }
3213
                        }
3214

3215
                        OGRwkbGeometryType eGeomType = wkbUnknown;
1,016✔
3216
                        if (oTileStatLayers.IsValid())
1,016✔
3217
                        {
3218
                            eGeomType = OGRMVTFindGeomTypeFromTileStat(
539✔
3219
                                oTileStatLayers, pszLayerName);
3220
                        }
3221
                        CPLJSONArray oAttributesFromTileStats =
3222
                            OGRMVTFindAttributesFromTileStat(oTileStatLayers,
3223
                                                             pszLayerName);
2,032✔
3224

3225
                        poDS->m_apoLayers.push_back(
1,016✔
3226
                            std::unique_ptr<OGRLayer>(new OGRMVTLayer(
2,032✔
3227
                                poDS, pszLayerName, pabyDataLayer, nLayerSize,
3228
                                oFields, oAttributesFromTileStats, eGeomType)));
1,016✔
3229
                        CPLFree(pszLayerName);
1,016✔
3230
                        break;
1,016✔
3231
                    }
3232
                    else
3233
                    {
3234
                        SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimitLayer, FALSE);
239✔
3235
                    }
3236
                }
3237
                pabyData = pabyDataLimitLayer;
1,016✔
3238
            }
3239
            else
3240
            {
3241
                SKIP_UNKNOWN_FIELD(pabyData, pabyDataLimit, FALSE);
×
3242
            }
3243
        }
3244

3245
        return poDS;
936✔
3246
    }
3247
    catch (const GPBException &e)
×
3248
    {
3249
        CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
×
3250
        delete poDS;
×
3251
        return nullptr;
×
3252
    }
3253
}
3254

3255
#ifdef HAVE_MVT_WRITE_SUPPORT
3256

3257
/************************************************************************/
3258
/*                           OGRMVTWriterDataset                        */
3259
/************************************************************************/
3260

3261
class OGRMVTWriterLayer;
3262

3263
struct OGRMVTFeatureContent
3264
{
3265
    std::vector<std::pair<std::string, MVTTileLayerValue>> oValues;
3266
    GIntBig nFID;
3267
};
3268

3269
class OGRMVTWriterDataset final : public GDALDataset
3270
{
3271
    class MVTFieldProperties
3272
    {
3273
      public:
3274
        CPLString m_osName;
3275
        std::set<MVTTileLayerValue> m_oSetValues;
3276
        std::set<MVTTileLayerValue> m_oSetAllValues;
3277
        double m_dfMinVal = 0;
3278
        double m_dfMaxVal = 0;
3279
        bool m_bAllInt = false;
3280
        MVTTileLayerValue::ValueType m_eType =
3281
            MVTTileLayerValue::ValueType::NONE;
3282
    };
3283

3284
    class MVTLayerProperties
3285
    {
3286
      public:
3287
        int m_nMinZoom = 0;
3288
        int m_nMaxZoom = 0;
3289
        std::map<MVTTileLayerFeature::GeomType, GIntBig> m_oCountGeomType;
3290
        std::map<CPLString, size_t> m_oMapFieldNameToIdx;
3291
        std::vector<MVTFieldProperties> m_aoFields;
3292
        std::set<CPLString> m_oSetFields;
3293
    };
3294

3295
    std::vector<std::unique_ptr<OGRMVTWriterLayer>> m_apoLayers;
3296
    CPLString m_osTempDB;
3297
    mutable std::mutex m_oDBMutex;
3298
    mutable bool m_bWriteFeatureError = false;
3299
    sqlite3_vfs *m_pMyVFS = nullptr;
3300
    sqlite3 *m_hDB = nullptr;
3301
    sqlite3_stmt *m_hInsertStmt = nullptr;
3302
    int m_nMinZoom = 0;
3303
    int m_nMaxZoom = 5;
3304
    double m_dfSimplification = 0.0;
3305
    double m_dfSimplificationMaxZoom = 0.0;
3306
    CPLJSONDocument m_oConf;
3307
    unsigned m_nExtent = knDEFAULT_EXTENT;
3308
    int m_nMetadataVersion = 2;
3309
    int m_nMVTVersion = 2;
3310
    int m_nBuffer = 5 * knDEFAULT_EXTENT / 256;
3311
    bool m_bGZip = true;
3312
    mutable CPLWorkerThreadPool m_oThreadPool;
3313
    bool m_bThreadPoolOK = false;
3314
    mutable GIntBig m_nTempTiles = 0;
3315
    CPLString m_osName;
3316
    CPLString m_osDescription;
3317
    CPLString m_osType{"overlay"};
3318
    sqlite3 *m_hDBMBTILES = nullptr;
3319
    OGREnvelope m_oEnvelope;
3320
    unsigned m_nMaxTileSize = 500000;
3321
    unsigned m_nMaxFeatures = 200000;
3322
    std::map<std::string, std::string> m_oMapLayerNameToDesc;
3323
    std::map<std::string, GIntBig> m_oMapLayerNameToFeatureCount;
3324
    CPLString m_osBounds;
3325
    CPLString m_osCenter;
3326
    CPLString m_osExtension{"pbf"};
3327
    OGRSpatialReference *m_poSRS = nullptr;
3328
    double m_dfTopX = 0.0;
3329
    double m_dfTopY = 0.0;
3330
    double m_dfTileDim0 = 0.0;
3331
    bool m_bReuseTempFile = false;  // debug only
3332

3333
    OGRErr PreGenerateForTile(
3334
        int nZ, int nX, int nY, const CPLString &osTargetName,
3335
        bool bIsMaxZoomForLayer,
3336
        const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
3337
        GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
3338
        const OGREnvelope &sEnvelope) const;
3339

3340
    static void WriterTaskFunc(void *pParam);
3341

3342
    OGRErr PreGenerateForTileReal(int nZ, int nX, int nY,
3343
                                  const CPLString &osTargetName,
3344
                                  bool bIsMaxZoomForLayer,
3345
                                  const OGRMVTFeatureContent *poFeatureContent,
3346
                                  GIntBig nSerial, const OGRGeometry *poGeom,
3347
                                  const OGREnvelope &sEnvelope) const;
3348

3349
    void ConvertToTileCoords(double dfX, double dfY, int &nX, int &nY,
3350
                             double dfTopX, double dfTopY,
3351
                             double dfTileDim) const;
3352
    bool EncodeLineString(MVTTileLayerFeature *poGPBFeature,
3353
                          const OGRLineString *poLS, OGRLineString *poOutLS,
3354
                          bool bWriteLastPoint, bool bReverseOrder,
3355
                          GUInt32 nMinLineTo, double dfTopX, double dfTopY,
3356
                          double dfTileDim, int &nLastX, int &nLastY) const;
3357
    bool EncodePolygon(MVTTileLayerFeature *poGPBFeature,
3358
                       const OGRPolygon *poPoly, OGRPolygon *poOutPoly,
3359
                       double dfTopX, double dfTopY, double dfTileDim,
3360
                       int &nLastX, int &nLastY, double &dfArea) const;
3361
#ifdef notdef
3362
    bool EncodeRepairedOuterRing(MVTTileLayerFeature *poGPBFeature,
3363
                                 OGRPolygon &oOutPoly, int &nLastX,
3364
                                 int &nLastY) const;
3365
#endif
3366

3367
    static void UpdateLayerProperties(MVTLayerProperties *poLayerProperties,
3368
                                      const std::string &osKey,
3369
                                      const MVTTileLayerValue &oValue);
3370

3371
    void EncodeFeature(const void *pabyBlob, int nBlobSize,
3372
                       std::shared_ptr<MVTTileLayer> &poTargetLayer,
3373
                       std::map<CPLString, GUInt32> &oMapKeyToIdx,
3374
                       std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
3375
                       MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
3376
                       unsigned &nFeaturesInTile);
3377

3378
    std::string
3379
    EncodeTile(int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer,
3380
               sqlite3_stmt *hStmtRows,
3381
               std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
3382
               std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead);
3383

3384
    std::string RecodeTileLowerResolution(int nZ, int nX, int nY, int nExtent,
3385
                                          sqlite3_stmt *hStmtLayer,
3386
                                          sqlite3_stmt *hStmtRows);
3387

3388
    bool CreateOutput();
3389

3390
    bool GenerateMetadata(size_t nLayers,
3391
                          const std::map<CPLString, MVTLayerProperties> &oMap);
3392

3393
  public:
3394
    OGRMVTWriterDataset();
3395
    ~OGRMVTWriterDataset();
3396

3397
    CPLErr Close() override;
3398

3399
    OGRLayer *ICreateLayer(const char *pszName,
3400
                           const OGRGeomFieldDefn *poGeomFieldDefn,
3401
                           CSLConstList papszOptions) override;
3402

3403
    int TestCapability(const char *) override;
3404

3405
    OGRErr WriteFeature(OGRMVTWriterLayer *poLayer, OGRFeature *poFeature,
3406
                        GIntBig nSerial, OGRGeometry *poGeom);
3407

3408
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
3409
                               int nBandsIn, GDALDataType eDT,
3410
                               char **papszOptions);
3411

3412
    OGRSpatialReference *GetSRS()
171✔
3413
    {
3414
        return m_poSRS;
171✔
3415
    }
3416
};
3417

3418
/************************************************************************/
3419
/*                           OGRMVTWriterLayer                          */
3420
/************************************************************************/
3421

3422
class OGRMVTWriterLayer final : public OGRLayer
3423
{
3424
    friend class OGRMVTWriterDataset;
3425

3426
    OGRMVTWriterDataset *m_poDS = nullptr;
3427
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
3428
    OGRCoordinateTransformation *m_poCT = nullptr;
3429
    GIntBig m_nSerial = 0;
3430
    int m_nMinZoom = 0;
3431
    int m_nMaxZoom = 5;
3432
    CPLString m_osTargetName;
3433

3434
  public:
3435
    OGRMVTWriterLayer(OGRMVTWriterDataset *poDS, const char *pszLayerName,
3436
                      OGRSpatialReference *poSRS);
3437
    ~OGRMVTWriterLayer();
3438

3439
    void ResetReading() override
48✔
3440
    {
3441
    }
48✔
3442

3443
    OGRFeature *GetNextFeature() override
48✔
3444
    {
3445
        return nullptr;
48✔
3446
    }
3447

3448
    OGRFeatureDefn *GetLayerDefn() override
1,388✔
3449
    {
3450
        return m_poFeatureDefn;
1,388✔
3451
    }
3452

3453
    int TestCapability(const char *) override;
3454
    OGRErr ICreateFeature(OGRFeature *) override;
3455
    OGRErr CreateField(const OGRFieldDefn *, int) override;
3456

3457
    GDALDataset *GetDataset() override
49✔
3458
    {
3459
        return m_poDS;
49✔
3460
    }
3461
};
3462

3463
/************************************************************************/
3464
/*                          OGRMVTWriterLayer()                         */
3465
/************************************************************************/
3466

3467
OGRMVTWriterLayer::OGRMVTWriterLayer(OGRMVTWriterDataset *poDS,
165✔
3468
                                     const char *pszLayerName,
3469
                                     OGRSpatialReference *poSRSIn)
165✔
3470
{
3471
    m_poDS = poDS;
165✔
3472
    m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
165✔
3473
    SetDescription(m_poFeatureDefn->GetName());
165✔
3474
    m_poFeatureDefn->Reference();
165✔
3475

3476
    m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poDS->GetSRS());
165✔
3477

3478
    if (poSRSIn != nullptr && !poDS->GetSRS()->IsSame(poSRSIn))
165✔
3479
    {
3480
        m_poCT = OGRCreateCoordinateTransformation(poSRSIn, poDS->GetSRS());
2✔
3481
        if (m_poCT == nullptr)
2✔
3482
        {
3483
            // If we can't create a transformation, issue a warning - but
3484
            // continue the transformation.
3485
            CPLError(CE_Warning, CPLE_AppDefined,
1✔
3486
                     "Failed to create coordinate transformation between the "
3487
                     "input and target coordinate systems.");
3488
        }
3489
    }
3490
}
165✔
3491

3492
/************************************************************************/
3493
/*                          ~OGRMVTWriterLayer()                        */
3494
/************************************************************************/
3495

3496
OGRMVTWriterLayer::~OGRMVTWriterLayer()
330✔
3497
{
3498
    m_poFeatureDefn->Release();
165✔
3499
    delete m_poCT;
165✔
3500
}
330✔
3501

3502
/************************************************************************/
3503
/*                            TestCapability()                          */
3504
/************************************************************************/
3505

3506
int OGRMVTWriterLayer::TestCapability(const char *pszCap)
341✔
3507
{
3508

3509
    if (EQUAL(pszCap, OLCSequentialWrite) || EQUAL(pszCap, OLCCreateField))
341✔
3510
        return true;
96✔
3511
    return false;
245✔
3512
}
3513

3514
/************************************************************************/
3515
/*                            CreateField()                             */
3516
/************************************************************************/
3517

3518
OGRErr OGRMVTWriterLayer::CreateField(const OGRFieldDefn *poFieldDefn, int)
312✔
3519
{
3520
    m_poFeatureDefn->AddFieldDefn(poFieldDefn);
312✔
3521
    return OGRERR_NONE;
312✔
3522
}
3523

3524
/************************************************************************/
3525
/*                            ICreateFeature()                          */
3526
/************************************************************************/
3527

3528
OGRErr OGRMVTWriterLayer::ICreateFeature(OGRFeature *poFeature)
287✔
3529
{
3530
    OGRGeometry *poGeom = poFeature->GetGeometryRef();
287✔
3531
    if (poGeom == nullptr || poGeom->IsEmpty())
287✔
3532
        return OGRERR_NONE;
102✔
3533
    if (m_poCT)
185✔
3534
    {
3535
        poGeom->transform(m_poCT);
1✔
3536
    }
3537
    m_nSerial++;
185✔
3538
    return m_poDS->WriteFeature(this, poFeature, m_nSerial, poGeom);
185✔
3539
}
3540

3541
/************************************************************************/
3542
/*                          OGRMVTWriterDataset()                       */
3543
/************************************************************************/
3544

3545
OGRMVTWriterDataset::OGRMVTWriterDataset()
127✔
3546
{
3547
    // Default WebMercator tiling scheme
3548
    m_poSRS = new OGRSpatialReference();
127✔
3549
    m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
127✔
3550

3551
    InitWebMercatorTilingScheme(m_poSRS, m_dfTopX, m_dfTopY, m_dfTileDim0);
127✔
3552
}
127✔
3553

3554
/************************************************************************/
3555
/*                         ~OGRMVTWriterDataset()                       */
3556
/************************************************************************/
3557

3558
OGRMVTWriterDataset::~OGRMVTWriterDataset()
254✔
3559
{
3560
    OGRMVTWriterDataset::Close();
127✔
3561

3562
    if (m_pMyVFS)
127✔
3563
    {
3564
        sqlite3_vfs_unregister(m_pMyVFS);
127✔
3565
        CPLFree(m_pMyVFS->pAppData);
127✔
3566
        CPLFree(m_pMyVFS);
127✔
3567
    }
3568

3569
    m_poSRS->Release();
127✔
3570
}
254✔
3571

3572
/************************************************************************/
3573
/*                              Close()                                 */
3574
/************************************************************************/
3575

3576
CPLErr OGRMVTWriterDataset::Close()
244✔
3577
{
3578
    CPLErr eErr = CE_None;
244✔
3579
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
244✔
3580
    {
3581
        if (GetDescription()[0] != '\0')
127✔
3582
        {
3583
            if (!CreateOutput())
117✔
3584
                eErr = CE_Failure;
3✔
3585
        }
3586
        if (m_hInsertStmt != nullptr)
127✔
3587
        {
3588
            sqlite3_finalize(m_hInsertStmt);
124✔
3589
        }
3590
        if (m_hDB)
127✔
3591
        {
3592
            sqlite3_close(m_hDB);
124✔
3593
        }
3594
        if (m_hDBMBTILES)
127✔
3595
        {
3596
            sqlite3_close(m_hDBMBTILES);
74✔
3597
        }
3598
        if (!m_osTempDB.empty() && !m_bReuseTempFile &&
250✔
3599
            CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
123✔
3600
        {
3601
            VSIUnlink(m_osTempDB);
122✔
3602
        }
3603

3604
        if (GDALDataset::Close() != CE_None)
127✔
3605
            eErr = CE_Failure;
×
3606
    }
3607
    return eErr;
244✔
3608
}
3609

3610
/************************************************************************/
3611
/*                        ConvertToTileCoords()                     */
3612
/************************************************************************/
3613

3614
void OGRMVTWriterDataset::ConvertToTileCoords(double dfX, double dfY, int &nX,
12,121✔
3615
                                              int &nY, double dfTopX,
3616
                                              double dfTopY,
3617
                                              double dfTileDim) const
3618
{
3619
    if (dfTileDim == 0)
12,121✔
3620
    {
3621
        nX = static_cast<int>(dfX);
333✔
3622
        nY = static_cast<int>(dfY);
333✔
3623
    }
3624
    else
3625
    {
3626
        nX = static_cast<int>(
11,788✔
3627
            std::round((dfX - dfTopX) * m_nExtent / dfTileDim));
11,788✔
3628
        nY = static_cast<int>(
11,788✔
3629
            std::round((dfTopY - dfY) * m_nExtent / dfTileDim));
11,788✔
3630
    }
3631
}
12,121✔
3632

3633
/************************************************************************/
3634
/*                       GetCmdCountCombined()                          */
3635
/************************************************************************/
3636

3637
static unsigned GetCmdCountCombined(unsigned int nCmdId, unsigned int nCmdCount)
2,964✔
3638
{
3639
    return (nCmdId | (nCmdCount << 3));
2,964✔
3640
}
3641

3642
/************************************************************************/
3643
/*                          EncodeLineString()                          */
3644
/************************************************************************/
3645

3646
bool OGRMVTWriterDataset::EncodeLineString(
2,676✔
3647
    MVTTileLayerFeature *poGPBFeature, const OGRLineString *poLS,
3648
    OGRLineString *poOutLS, bool bWriteLastPoint, bool bReverseOrder,
3649
    GUInt32 nMinLineTo, double dfTopX, double dfTopY, double dfTileDim,
3650
    int &nLastX, int &nLastY) const
3651
{
3652
    const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
2,676✔
3653
    const int nLastXOri = nLastX;
2,671✔
3654
    const int nLastYOri = nLastY;
2,671✔
3655
    GUInt32 nLineToCount = 0;
2,671✔
3656
    const int nPoints = poLS->getNumPoints() - (bWriteLastPoint ? 0 : 1);
2,671✔
3657
    if (poOutLS)
2,649✔
3658
        poOutLS->setNumPoints(nPoints);
2,652✔
3659
    int nFirstX = 0;
2,718✔
3660
    int nFirstY = 0;
2,718✔
3661
    int nLastXValid = nLastX;
2,718✔
3662
    int nLastYValid = nLastY;
2,718✔
3663
    for (int i = 0; i < nPoints; i++)
13,585✔
3664
    {
3665
        int nX, nY;
3666
        int nSrcIdx = bReverseOrder ? poLS->getNumPoints() - 1 - i : i;
10,913✔
3667
        double dfX = poLS->getX(nSrcIdx);
10,913✔
3668
        double dfY = poLS->getY(nSrcIdx);
10,803✔
3669
        ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
10,691✔
3670
        int nDiffX = nX - nLastX;
10,884✔
3671
        int nDiffY = nY - nLastY;
10,884✔
3672
        if (i == 0 || nDiffX != 0 || nDiffY != 0)
10,884✔
3673
        {
3674
            if (i > 0)
3,778✔
3675
            {
3676
                nLineToCount++;
1,085✔
3677
                if (nLineToCount == 1)
1,085✔
3678
                {
3679
                    poGPBFeature->addGeometry(
301✔
3680
                        GetCmdCountCombined(knCMD_MOVETO, 1));
3681
                    const int nLastDiffX = nLastX - nLastXOri;
300✔
3682
                    const int nLastDiffY = nLastY - nLastYOri;
300✔
3683
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
300✔
3684
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
300✔
3685
                    if (poOutLS)
301✔
3686
                        poOutLS->setPoint(0, nLastX, nLastY);
301✔
3687

3688
                    // To be modified later
3689
                    poGPBFeature->addGeometry(
300✔
3690
                        GetCmdCountCombined(knCMD_LINETO, 0));
3691
                }
3692

3693
                poGPBFeature->addGeometry(EncodeSInt(nDiffX));
1,085✔
3694
                poGPBFeature->addGeometry(EncodeSInt(nDiffY));
1,086✔
3695
                if (poOutLS)
1,086✔
3696
                    poOutLS->setPoint(nLineToCount, nX, nY);
1,086✔
3697
            }
3698
            else
3699
            {
3700
                nFirstX = nX;
2,693✔
3701
                nFirstY = nY;
2,693✔
3702
            }
3703
            nLastXValid = nLastX;
3,761✔
3704
            nLastYValid = nLastY;
3,761✔
3705
            nLastX = nX;
3,761✔
3706
            nLastY = nY;
3,761✔
3707
        }
3708
    }
3709

3710
    // If last point of ring is identical to first one, discard it
3711
    if (nMinLineTo == 2 && nLineToCount > 0 && nFirstX == nLastX &&
2,672✔
3712
        nFirstY == nLastY)
106✔
3713
    {
3714
        poGPBFeature->resizeGeometryArray(poGPBFeature->getGeometryCount() - 2);
66✔
3715
        nLineToCount--;
70✔
3716
        nLastX = nLastXValid;
70✔
3717
        nLastY = nLastYValid;
70✔
3718
    }
3719

3720
    if (nLineToCount >= nMinLineTo)
2,676✔
3721
    {
3722
        if (poOutLS)
252✔
3723
            poOutLS->setNumPoints(1 + nLineToCount);
251✔
3724
        // Patch actual number of points in LINETO command
3725
        poGPBFeature->setGeometry(
253✔
3726
            nInitialSize + 3, GetCmdCountCombined(knCMD_LINETO, nLineToCount));
3727
        return true;
251✔
3728
    }
3729
    else
3730
    {
3731
        poGPBFeature->resizeGeometryArray(nInitialSize);
2,424✔
3732
        nLastX = nLastXOri;
2,399✔
3733
        nLastY = nLastYOri;
2,399✔
3734
        return false;
2,399✔
3735
    }
3736
}
3737

3738
#ifdef notdef
3739
/************************************************************************/
3740
/*                     EncodeRepairedOuterRing()                        */
3741
/************************************************************************/
3742

3743
bool OGRMVTWriterDataset::EncodeRepairedOuterRing(
3744
    MVTTileLayerFeature *poGPBFeature, OGRPolygon &oInPoly, int &nLastX,
3745
    int &nLastY) const
3746
{
3747
    std::unique_ptr<OGRGeometry> poFixedGeom(oInPoly.Buffer(0));
3748
    if (!poFixedGeom.get() || poFixedGeom->IsEmpty())
3749
    {
3750
        return false;
3751
    }
3752

3753
    OGRPolygon *poPoly = nullptr;
3754
    if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbMultiPolygon)
3755
    {
3756
        OGRMultiPolygon *poMP = poFixedGeom.get()->toMultiPolygon();
3757
        poPoly = poMP->getGeometryRef(0)->toPolygon();
3758
    }
3759
    else if (wkbFlatten(poFixedGeom->getGeometryType()) == wkbPolygon)
3760
    {
3761
        poPoly = poFixedGeom.get()->toPolygon();
3762
    }
3763
    if (!poPoly)
3764
        return false;
3765

3766
    OGRLinearRing *poRing = poPoly->getExteriorRing();
3767
    const bool bReverseOrder = !poRing->isClockwise();
3768

3769
    const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
3770
    const int nLastXOri = nLastX;
3771
    const int nLastYOri = nLastY;
3772
    GUInt32 nLineToCount = 0;
3773
    const int nPoints = poRing->getNumPoints() - 1;
3774
    auto poOutLinearRing = std::make_unique<OGRLinearRing>();
3775
    poOutLinearRing->setNumPoints(nPoints);
3776
    for (int i = 0; i < nPoints; i++)
3777
    {
3778
        int nSrcIdx = bReverseOrder ? poRing->getNumPoints() - 1 - i : i;
3779
        double dfX = poRing->getX(nSrcIdx);
3780
        double dfY = poRing->getY(nSrcIdx);
3781
        int nX = static_cast<int>(std::round(dfX));
3782
        int nY = static_cast<int>(std::round(dfY));
3783
        if (nX != dfX || nY != dfY)
3784
            continue;
3785
        int nDiffX = nX - nLastX;
3786
        int nDiffY = nY - nLastY;
3787
        if (i == 0 || nDiffX != 0 || nDiffY != 0)
3788
        {
3789
            if (i > 0)
3790
            {
3791
                nLineToCount++;
3792
                if (nLineToCount == 1)
3793
                {
3794
                    poGPBFeature->addGeometry(
3795
                        GetCmdCountCombined(knCMD_MOVETO, 1));
3796
                    const int nLastDiffX = nLastX - nLastXOri;
3797
                    const int nLastDiffY = nLastY - nLastYOri;
3798
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffX));
3799
                    poGPBFeature->addGeometry(EncodeSInt(nLastDiffY));
3800
                    poOutLinearRing->setPoint(0, nLastX, nLastY);
3801

3802
                    // To be modified later
3803
                    poGPBFeature->addGeometry(
3804
                        GetCmdCountCombined(knCMD_LINETO, 0));
3805
                }
3806

3807
                poGPBFeature->addGeometry(EncodeSInt(nDiffX));
3808
                poGPBFeature->addGeometry(EncodeSInt(nDiffY));
3809
                poOutLinearRing->setPoint(nLineToCount, nX, nY);
3810
            }
3811
            nLastX = nX;
3812
            nLastY = nY;
3813
        }
3814
    }
3815
    if (nLineToCount >= 2)
3816
    {
3817
        poOutLinearRing->setNumPoints(1 + nLineToCount);
3818
        OGRPolygon oOutPoly;
3819
        oOutPoly.addRingDirectly(poOutLinearRing.release());
3820
        int bIsValid;
3821
        {
3822
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
3823
            bIsValid = oOutPoly.IsValid();
3824
        }
3825
        if (bIsValid)
3826
        {
3827
            // Patch actual number of points in LINETO command
3828
            poGPBFeature->setGeometry(
3829
                nInitialSize + 3,
3830
                GetCmdCountCombined(knCMD_LINETO, nLineToCount));
3831
            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
3832
            return true;
3833
        }
3834
    }
3835

3836
    poGPBFeature->resizeGeometryArray(nInitialSize);
3837
    nLastX = nLastXOri;
3838
    nLastY = nLastYOri;
3839
    return false;
3840
}
3841
#endif
3842

3843
/************************************************************************/
3844
/*                          EncodePolygon()                             */
3845
/************************************************************************/
3846

3847
bool OGRMVTWriterDataset::EncodePolygon(MVTTileLayerFeature *poGPBFeature,
1,352✔
3848
                                        const OGRPolygon *poPoly,
3849
                                        OGRPolygon *poOutPoly, double dfTopX,
3850
                                        double dfTopY, double dfTileDim,
3851
                                        int &nLastX, int &nLastY,
3852
                                        double &dfArea) const
3853
{
3854
    dfArea = 0;
1,352✔
3855
    auto poOutOuterRing = std::make_unique<OGRLinearRing>();
2,710✔
3856
    for (int i = 0; i < 1 + poPoly->getNumInteriorRings(); i++)
1,520✔
3857
    {
3858
        const OGRLinearRing *poRing = (i == 0) ? poPoly->getExteriorRing()
1,311✔
3859
                                               : poPoly->getInteriorRing(i - 1);
22✔
3860
        if (poRing->getNumPoints() < 4 ||
2,696✔
3861
            poRing->getX(0) != poRing->getX(poRing->getNumPoints() - 1) ||
2,680✔
3862
            poRing->getY(0) != poRing->getY(poRing->getNumPoints() - 1))
1,362✔
3863
        {
3864
            if (i == 0)
×
3865
                return false;
1,139✔
3866
            continue;
83✔
3867
        }
3868
        const bool bWriteLastPoint = false;
1,337✔
3869
        // If dealing with input geometry in CRS units, exterior rings must
3870
        // be clockwise oriented.
3871
        // But if re-encoding a geometry already in tile coordinates
3872
        // (dfTileDim == 0), this is the reverse.
3873
        const bool bReverseOrder = dfTileDim != 0
3874
                                       ? ((i == 0 && !poRing->isClockwise()) ||
1,419✔
3875
                                          (i > 0 && poRing->isClockwise()))
24✔
3876
                                       : ((i == 0 && poRing->isClockwise()) ||
59✔
3877
                                          (i > 0 && !poRing->isClockwise()));
1✔
3878
        const GUInt32 nMinLineTo = 2;
1,364✔
3879
        std::unique_ptr<OGRLinearRing> poOutInnerRing;
×
3880
        if (i > 0)
1,364✔
3881
            poOutInnerRing = std::make_unique<OGRLinearRing>();
25✔
3882
        OGRLinearRing *poOutRing =
3883
            poOutInnerRing.get() ? poOutInnerRing.get() : poOutOuterRing.get();
1,364✔
3884

3885
        bool bSuccess = EncodeLineString(
1,357✔
3886
            poGPBFeature, poRing, poOutRing, bWriteLastPoint, bReverseOrder,
3887
            nMinLineTo, dfTopX, dfTopY, dfTileDim, nLastX, nLastY);
3888
        if (!bSuccess)
1,349✔
3889
        {
3890
            if (i == 0)
1,158✔
3891
                return false;
1,133✔
3892
            continue;
25✔
3893
        }
3894

3895
        if (poOutPoly == nullptr)
191✔
3896
        {
3897
            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
58✔
3898
            continue;
58✔
3899
        }
3900

3901
        poOutRing->closeRings();
133✔
3902

3903
        poOutPoly->addRing(poOutRing);
128✔
3904
        if (i > 0)
128✔
3905
            dfArea -= poOutRing->get_Area();
17✔
3906
        else
3907
            dfArea = poOutRing->get_Area();
111✔
3908

3909
        poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_CLOSEPATH, 1));
128✔
3910
    }
3911

3912
    return true;
219✔
3913
}
3914

3915
/************************************************************************/
3916
/*                          PreGenerateForTile()                        */
3917
/************************************************************************/
3918

3919
OGRErr OGRMVTWriterDataset::PreGenerateForTileReal(
4,011✔
3920
    int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
3921
    bool bIsMaxZoomForLayer, const OGRMVTFeatureContent *poFeatureContent,
3922
    GIntBig nSerial, const OGRGeometry *poGeom,
3923
    const OGREnvelope &sEnvelope) const
3924
{
3925
    double dfTileDim = m_dfTileDim0 / (1 << nZ);
4,011✔
3926
    double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
4,011✔
3927
    double dfTopX = m_dfTopX + nTileX * dfTileDim;
4,011✔
3928
    double dfTopY = m_dfTopY - nTileY * dfTileDim;
4,011✔
3929
    double dfBottomRightX = dfTopX + dfTileDim;
4,011✔
3930
    double dfBottomRightY = dfTopY - dfTileDim;
4,011✔
3931
    double dfIntersectTopX = dfTopX - dfBuffer;
4,011✔
3932
    double dfIntersectTopY = dfTopY + dfBuffer;
4,011✔
3933
    double dfIntersectBottomRightX = dfBottomRightX + dfBuffer;
4,011✔
3934
    double dfIntersectBottomRightY = dfBottomRightY - dfBuffer;
4,011✔
3935

3936
    const OGRGeometry *poIntersection;
3937
    std::unique_ptr<OGRGeometry> poIntersectionHolder;  // keep in that scope
3,988✔
3938
    if (sEnvelope.MinX >= dfIntersectTopX &&
4,011✔
3939
        sEnvelope.MinY >= dfIntersectBottomRightY &&
3,986✔
3940
        sEnvelope.MaxX <= dfIntersectBottomRightX &&
3,981✔
3941
        sEnvelope.MaxY <= dfIntersectTopY)
3,963✔
3942
    {
3943
        poIntersection = poGeom;
3,960✔
3944
    }
3945
    else
3946
    {
3947
        OGRLinearRing *poLR = new OGRLinearRing();
51✔
3948
        poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
49✔
3949
        poLR->addPoint(dfIntersectTopX, dfIntersectBottomRightY);
49✔
3950
        poLR->addPoint(dfIntersectBottomRightX, dfIntersectBottomRightY);
49✔
3951
        poLR->addPoint(dfIntersectBottomRightX, dfIntersectTopY);
49✔
3952
        poLR->addPoint(dfIntersectTopX, dfIntersectTopY);
49✔
3953
        OGRPolygon oPoly;
49✔
3954
        oPoly.addRingDirectly(poLR);
49✔
3955

3956
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
49✔
3957
        auto poTmp = poGeom->Intersection(&oPoly);
49✔
3958
        poIntersection = poTmp;
49✔
3959
        poIntersectionHolder.reset(poTmp);
49✔
3960
        if (poIntersection == nullptr || poIntersection->IsEmpty())
49✔
3961
        {
3962
            return OGRERR_NONE;
2✔
3963
        }
3964
    }
3965

3966
    // Create a layer with a single feature in it
3967
    std::shared_ptr<MVTTileLayer> poLayer =
3968
        std::shared_ptr<MVTTileLayer>(new MVTTileLayer());
7,911✔
3969
    std::shared_ptr<MVTTileLayerFeature> poGPBFeature =
3970
        std::shared_ptr<MVTTileLayerFeature>(new MVTTileLayerFeature());
7,886✔
3971
    poLayer->addFeature(poGPBFeature);
3,972✔
3972

3973
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
3,955✔
3974
    if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
3,958✔
3975
        poGPBFeature->setType(MVTTileLayerFeature::GeomType::POINT);
1,383✔
3976
    else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
2,575✔
3977
        poGPBFeature->setType(MVTTileLayerFeature::GeomType::LINESTRING);
1,305✔
3978
    else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
1,270✔
3979
        poGPBFeature->setType(MVTTileLayerFeature::GeomType::POLYGON);
1,270✔
3980
    else
3981
    {
3982
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
×
3983
        return OGRERR_NONE;
×
3984
    }
3985

3986
    OGRwkbGeometryType eGeomToEncodeType =
3987
        wkbFlatten(poIntersection->getGeometryType());
3,923✔
3988

3989
    // Simplify contour if requested by user
3990
    const OGRGeometry *poGeomToEncode = poIntersection;
3,907✔
3991
    std::unique_ptr<OGRGeometry> poGeomSimplified;
3,899✔
3992
    const double dfSimplification =
3,907✔
3993
        bIsMaxZoomForLayer ? m_dfSimplificationMaxZoom : m_dfSimplification;
3,907✔
3994
    if (dfSimplification > 0 &&
3,907✔
3995
        (eGeomType == wkbLineString || eGeomType == wkbMultiLineString ||
6✔
3996
         eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon))
6✔
3997
    {
3998
        const double dfTol = dfTileDim / m_nExtent;
×
3999
        poGeomSimplified = std::unique_ptr<OGRGeometry>(
×
4000
            poIntersection->SimplifyPreserveTopology(dfTol * dfSimplification));
6✔
4001
        if (poGeomSimplified.get())
6✔
4002
        {
4003
            poGeomToEncode = poGeomSimplified.get();
6✔
4004
            eGeomToEncodeType = wkbFlatten(poGeomSimplified->getGeometryType());
6✔
4005
        }
4006
    }
4007

4008
    bool bGeomOK = false;
3,953✔
4009
    double dfAreaOrLength = 0.0;
3,953✔
4010

4011
    const auto EmitValidPolygon =
4012
        [this, &bGeomOK, &dfAreaOrLength,
57✔
4013
         &poGPBFeature](const OGRGeometry *poValidGeom)
182✔
4014
    {
4015
        bGeomOK = false;
57✔
4016
        dfAreaOrLength = 0;
57✔
4017
        int nLastX = 0;
57✔
4018
        int nLastY = 0;
57✔
4019

4020
        if (wkbFlatten(poValidGeom->getGeometryType()) == wkbPolygon)
57✔
4021
        {
4022
            const OGRPolygon *poPoly = poValidGeom->toPolygon();
11✔
4023
            double dfPartArea = 0.0;
11✔
4024
            bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
11✔
4025
                                    0, nLastX, nLastY, dfPartArea);
4026
            dfAreaOrLength = dfPartArea;
11✔
4027
        }
4028
        else if (OGR_GT_IsSubClassOf(poValidGeom->getGeometryType(),
46✔
4029
                                     wkbGeometryCollection))
46✔
4030
        {
4031
            for (auto &&poSubGeom : poValidGeom->toGeometryCollection())
130✔
4032
            {
4033
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
88✔
4034
                {
4035
                    const OGRPolygon *poPoly = poSubGeom->toPolygon();
36✔
4036
                    double dfPartArea = 0.0;
36✔
4037
                    bGeomOK |=
36✔
4038
                        EncodePolygon(poGPBFeature.get(), poPoly, nullptr, 0, 0,
36✔
4039
                                      0, nLastX, nLastY, dfPartArea);
36✔
4040
                    dfAreaOrLength += dfPartArea;
36✔
4041
                }
4042
                else if (wkbFlatten(poSubGeom->getGeometryType()) ==
52✔
4043
                         wkbMultiPolygon)
4044
                {
4045
                    const OGRMultiPolygon *poMPoly =
4046
                        poSubGeom->toMultiPolygon();
5✔
4047
                    for (const auto *poPoly : poMPoly)
15✔
4048
                    {
4049
                        double dfPartArea = 0.0;
10✔
4050
                        bGeomOK |=
10✔
4051
                            EncodePolygon(poGPBFeature.get(), poPoly, nullptr,
10✔
4052
                                          0, 0, 0, nLastX, nLastY, dfPartArea);
10✔
4053
                        dfAreaOrLength += dfPartArea;
10✔
4054
                    }
4055
                }
4056
            }
4057
        }
4058
    };
57✔
4059

4060
    if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint)
3,953✔
4061
    {
4062
        if (eGeomToEncodeType == wkbPoint)
1,360✔
4063
        {
4064
            const OGRPoint *poPoint = poIntersection->toPoint();
978✔
4065
            int nX, nY;
4066
            double dfX = poPoint->getX();
979✔
4067
            double dfY = poPoint->getY();
978✔
4068
            bGeomOK = true;
980✔
4069
            ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY, dfTileDim);
980✔
4070
            poGPBFeature->addGeometry(GetCmdCountCombined(knCMD_MOVETO, 1));
978✔
4071
            poGPBFeature->addGeometry(EncodeSInt(nX));
977✔
4072
            poGPBFeature->addGeometry(EncodeSInt(nY));
976✔
4073
        }
4074
        else if (eGeomToEncodeType == wkbMultiPoint ||
382✔
4075
                 eGeomToEncodeType == wkbGeometryCollection)
4076
        {
4077
            const OGRGeometryCollection *poGC =
4078
                poIntersection->toGeometryCollection();
386✔
4079
            std::set<std::pair<int, int>> oSetUniqueCoords;
758✔
4080
            poGPBFeature->addGeometry(
383✔
4081
                GetCmdCountCombined(knCMD_MOVETO, 0));  // To be modified later
4082
            int nLastX = 0;
382✔
4083
            int nLastY = 0;
382✔
4084
            for (auto &&poSubGeom : poGC)
771✔
4085
            {
4086
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPoint)
387✔
4087
                {
4088
                    const OGRPoint *poPoint = poSubGeom->toPoint();
386✔
4089
                    int nX, nY;
4090
                    double dfX = poPoint->getX();
388✔
4091
                    double dfY = poPoint->getY();
390✔
4092
                    ConvertToTileCoords(dfX, dfY, nX, nY, dfTopX, dfTopY,
390✔
4093
                                        dfTileDim);
4094
                    if (oSetUniqueCoords.find(std::pair<int, int>(nX, nY)) ==
387✔
4095
                        oSetUniqueCoords.end())
776✔
4096
                    {
4097
                        oSetUniqueCoords.insert(std::pair<int, int>(nX, nY));
388✔
4098

4099
                        int nDiffX = nX - nLastX;
387✔
4100
                        int nDiffY = nY - nLastY;
387✔
4101
                        poGPBFeature->addGeometry(EncodeSInt(nDiffX));
387✔
4102
                        poGPBFeature->addGeometry(EncodeSInt(nDiffY));
383✔
4103
                        nLastX = nX;
389✔
4104
                        nLastY = nY;
389✔
4105
                    }
4106
                }
4107
            }
4108
            GUInt32 nPoints = static_cast<GUInt32>(oSetUniqueCoords.size());
382✔
4109
            bGeomOK = nPoints > 0;
377✔
4110
            poGPBFeature->setGeometry(
377✔
4111
                0, GetCmdCountCombined(knCMD_MOVETO, nPoints));
4112
        }
1,343✔
4113
    }
4114
    else if (eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
2,593✔
4115
    {
4116
        const bool bWriteLastPoint = true;
1,323✔
4117
        const bool bReverseOrder = false;
1,323✔
4118
        const GUInt32 nMinLineTo = 1;
1,323✔
4119

4120
        if (eGeomToEncodeType == wkbLineString)
1,323✔
4121
        {
4122
            const OGRLineString *poLS = poGeomToEncode->toLineString();
917✔
4123
            int nLastX = 0;
911✔
4124
            int nLastY = 0;
911✔
4125
            OGRLineString oOutLS;
911✔
4126
            bGeomOK =
914✔
4127
                EncodeLineString(poGPBFeature.get(), poLS, &oOutLS,
917✔
4128
                                 bWriteLastPoint, bReverseOrder, nMinLineTo,
4129
                                 dfTopX, dfTopY, dfTileDim, nLastX, nLastY);
4130
            dfAreaOrLength = oOutLS.get_Length();
914✔
4131
        }
4132
        else if (eGeomToEncodeType == wkbMultiLineString ||
406✔
4133
                 eGeomToEncodeType == wkbGeometryCollection)
4134
        {
4135
            const OGRGeometryCollection *poGC =
4136
                poGeomToEncode->toGeometryCollection();
406✔
4137
            int nLastX = 0;
386✔
4138
            int nLastY = 0;
386✔
4139
            for (auto &&poSubGeom : poGC)
778✔
4140
            {
4141
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbLineString)
393✔
4142
                {
4143
                    const OGRLineString *poLS = poSubGeom->toLineString();
392✔
4144
                    OGRLineString oOutLS;
395✔
4145
                    bool bSubGeomOK = EncodeLineString(
393✔
4146
                        poGPBFeature.get(), poLS, &oOutLS, bWriteLastPoint,
4147
                        bReverseOrder, nMinLineTo, dfTopX, dfTopY, dfTileDim,
4148
                        nLastX, nLastY);
4149
                    if (bSubGeomOK)
395✔
4150
                        dfAreaOrLength += oOutLS.get_Length();
18✔
4151
                    bGeomOK |= bSubGeomOK;
395✔
4152
                }
4153
            }
4154
        }
1,295✔
4155
    }
4156
    else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
1,270✔
4157
    {
4158
        if (eGeomToEncodeType == wkbPolygon)
1,271✔
4159
        {
4160
            const OGRPolygon *poPoly = poGeomToEncode->toPolygon();
918✔
4161
            int nLastX = 0;
919✔
4162
            int nLastY = 0;
919✔
4163
            OGRPolygon oOutPoly;
1,831✔
4164
            const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
922✔
4165
            CPL_IGNORE_RET_VAL(nInitialSize);
925✔
4166
            bGeomOK = EncodePolygon(poGPBFeature.get(), poPoly, &oOutPoly,
906✔
4167
                                    dfTopX, dfTopY, dfTileDim, nLastX, nLastY,
4168
                                    dfAreaOrLength);
4169
            int bIsValid;
4170
            {
4171
                CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
930✔
4172
                bIsValid = oOutPoly.IsValid();
901✔
4173
            }
4174
            if (!bIsValid)
912✔
4175
            {
4176
                // Build a valid geometry from the initial MVT geometry and emit
4177
                // it
4178
                std::unique_ptr<OGRGeometry> poPolyValid(oOutPoly.MakeValid());
110✔
4179
                if (poPolyValid)
55✔
4180
                {
4181
                    poGPBFeature->resizeGeometryArray(nInitialSize);
55✔
4182
                    EmitValidPolygon(poPolyValid.get());
55✔
4183
                }
4184
            }
4185
        }
4186
        else if (eGeomToEncodeType == wkbMultiPolygon ||
353✔
4187
                 eGeomToEncodeType == wkbGeometryCollection)
4188
        {
4189
            const OGRGeometryCollection *poGC =
4190
                poGeomToEncode->toGeometryCollection();
369✔
4191
            int nLastX = 0;
357✔
4192
            int nLastY = 0;
357✔
4193
            OGRMultiPolygon oOutMP;
705✔
4194
            const GUInt32 nInitialSize = poGPBFeature->getGeometryCount();
354✔
4195
            CPL_IGNORE_RET_VAL(nInitialSize);
365✔
4196
            for (auto &&poSubGeom : poGC)
705✔
4197
            {
4198
                if (wkbFlatten(poSubGeom->getGeometryType()) == wkbPolygon)
352✔
4199
                {
4200
                    const OGRPolygon *poPoly = poSubGeom->toPolygon();
361✔
4201
                    double dfPartArea = 0.0;
368✔
4202
                    auto poOutPoly = std::make_unique<OGRPolygon>();
728✔
4203
                    bGeomOK |= EncodePolygon(
360✔
4204
                        poGPBFeature.get(), poPoly, poOutPoly.get(), dfTopX,
4205
                        dfTopY, dfTileDim, nLastX, nLastY, dfPartArea);
368✔
4206
                    dfAreaOrLength += dfPartArea;
368✔
4207
                    oOutMP.addGeometryDirectly(poOutPoly.release());
368✔
4208
                }
4209
            }
4210
            int bIsValid;
4211
            {
4212
                CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
350✔
4213
                bIsValid = oOutMP.IsValid();
349✔
4214
            }
4215
            if (!bIsValid)
348✔
4216
            {
4217
                // Build a valid geometry from the initial MVT geometry and emit
4218
                // it
4219
                std::unique_ptr<OGRGeometry> poMPValid(oOutMP.MakeValid());
4✔
4220
                if (poMPValid)
2✔
4221
                {
4222
                    poGPBFeature->resizeGeometryArray(nInitialSize);
2✔
4223
                    EmitValidPolygon(poMPValid.get());
2✔
4224
                }
4225
            }
4226
        }
4227
    }
4228
    if (!bGeomOK)
3,874✔
4229
        return OGRERR_NONE;
2,381✔
4230

4231
    for (const auto &pair : poFeatureContent->oValues)
5,783✔
4232
    {
4233
        GUInt32 nKey = poLayer->addKey(pair.first);
4,280✔
4234
        GUInt32 nVal = poLayer->addValue(pair.second);
4,313✔
4235
        poGPBFeature->addTag(nKey);
4,281✔
4236
        poGPBFeature->addTag(nVal);
4,278✔
4237
    }
4238
    if (poFeatureContent->nFID >= 0)
1,501✔
4239
    {
4240
        poGPBFeature->setId(poFeatureContent->nFID);
53✔
4241
    }
4242

4243
#ifdef notdef
4244
    {
4245
        MVTTile oTile;
4246
        poLayer->setName("x");
4247
        oTile.addLayer(poLayer);
4248

4249
        CPLString oBuffer(oTile.write());
4250

4251
        VSILFILE *fp = VSIFOpenL(
4252
            CPLSPrintf("/tmp/%d-%d-%d.pbf", nZ, nTileX, nTileY), "wb");
4253
        VSIFWriteL(oBuffer.data(), 1, oBuffer.size(), fp);
4254
        VSIFCloseL(fp);
4255
    }
4256
#endif
4257

4258
    // GPB encode the layer with our single feature
4259
    CPLString oBuffer(poLayer->write());
3,019✔
4260

4261
    // Compress buffer
4262
    size_t nCompressedSize = 0;
1,499✔
4263
    void *pCompressed = CPLZLibDeflate(oBuffer.data(), oBuffer.size(), -1,
1,499✔
4264
                                       nullptr, 0, &nCompressedSize);
4265
    oBuffer.assign(static_cast<char *>(pCompressed), nCompressedSize);
1,500✔
4266
    CPLFree(pCompressed);
1,484✔
4267

4268
    std::unique_ptr<std::lock_guard<std::mutex>> poLockGuard;
1,518✔
4269
    if (m_bThreadPoolOK)
1,488✔
4270
        poLockGuard = std::make_unique<std::lock_guard<std::mutex>>(m_oDBMutex);
1,488✔
4271

4272
    m_nTempTiles++;
1,505✔
4273
    sqlite3_bind_int(m_hInsertStmt, 1, nZ);
1,505✔
4274
    sqlite3_bind_int(m_hInsertStmt, 2, nTileX);
1,518✔
4275
    sqlite3_bind_int(m_hInsertStmt, 3, nTileY);
1,518✔
4276
    sqlite3_bind_text(m_hInsertStmt, 4, osTargetName.c_str(), -1,
1,518✔
4277
                      SQLITE_STATIC);
4278
    sqlite3_bind_int64(m_hInsertStmt, 5, nSerial);
1,518✔
4279
    sqlite3_bind_blob(m_hInsertStmt, 6, oBuffer.data(),
1,518✔
4280
                      static_cast<int>(oBuffer.size()), SQLITE_STATIC);
1,518✔
4281
    sqlite3_bind_int(m_hInsertStmt, 7,
1,518✔
4282
                     static_cast<int>(poGPBFeature->getType()));
1,518✔
4283
    sqlite3_bind_double(m_hInsertStmt, 8, dfAreaOrLength);
1,518✔
4284
    int rc = sqlite3_step(m_hInsertStmt);
1,518✔
4285
    sqlite3_reset(m_hInsertStmt);
1,518✔
4286

4287
    if (!(rc == SQLITE_OK || rc == SQLITE_DONE))
1,518✔
4288
    {
4289
        return OGRERR_FAILURE;
22✔
4290
    }
4291

4292
    return OGRERR_NONE;
1,496✔
4293
}
4294

4295
/************************************************************************/
4296
/*                           MVTWriterTask()                            */
4297
/************************************************************************/
4298

4299
class MVTWriterTask
4300
{
4301
  public:
4302
    const OGRMVTWriterDataset *poDS;
4303
    int nZ;
4304
    int nTileX;
4305
    int nTileY;
4306
    CPLString osTargetName;
4307
    bool bIsMaxZoomForLayer;
4308
    std::shared_ptr<OGRMVTFeatureContent> poFeatureContent;
4309
    GIntBig nSerial;
4310
    std::shared_ptr<OGRGeometry> poGeom;
4311
    OGREnvelope sEnvelope;
4312
};
4313

4314
/************************************************************************/
4315
/*                          WriterTaskFunc()                            */
4316
/************************************************************************/
4317

4318
void OGRMVTWriterDataset::WriterTaskFunc(void *pParam)
3,996✔
4319
{
4320
    MVTWriterTask *poTask = static_cast<MVTWriterTask *>(pParam);
3,996✔
4321
    OGRErr eErr = poTask->poDS->PreGenerateForTileReal(
15,952✔
4322
        poTask->nZ, poTask->nTileX, poTask->nTileY, poTask->osTargetName,
3,997✔
4323
        poTask->bIsMaxZoomForLayer, poTask->poFeatureContent.get(),
3,997✔
4324
        poTask->nSerial, poTask->poGeom.get(), poTask->sEnvelope);
3,996✔
4325
    if (eErr != OGRERR_NONE)
3,962✔
4326
    {
4327
        std::lock_guard oLock(poTask->poDS->m_oDBMutex);
21✔
4328
        poTask->poDS->m_bWriteFeatureError = true;
21✔
4329
    }
4330
    delete poTask;
3,962✔
4331
}
4,019✔
4332

4333
/************************************************************************/
4334
/*                         PreGenerateForTile()                         */
4335
/************************************************************************/
4336

4337
OGRErr OGRMVTWriterDataset::PreGenerateForTile(
4,011✔
4338
    int nZ, int nTileX, int nTileY, const CPLString &osTargetName,
4339
    bool bIsMaxZoomForLayer,
4340
    const std::shared_ptr<OGRMVTFeatureContent> &poFeatureContent,
4341
    GIntBig nSerial, const std::shared_ptr<OGRGeometry> &poGeom,
4342
    const OGREnvelope &sEnvelope) const
4343
{
4344
    if (!m_bThreadPoolOK)
4,011✔
4345
    {
4346
        return PreGenerateForTileReal(
13✔
4347
            nZ, nTileX, nTileY, osTargetName, bIsMaxZoomForLayer,
4348
            poFeatureContent.get(), nSerial, poGeom.get(), sEnvelope);
26✔
4349
    }
4350
    else
4351
    {
4352
        MVTWriterTask *poTask = new MVTWriterTask;
3,998✔
4353
        poTask->poDS = this;
3,998✔
4354
        poTask->nZ = nZ;
3,998✔
4355
        poTask->nTileX = nTileX;
3,998✔
4356
        poTask->nTileY = nTileY;
3,998✔
4357
        poTask->osTargetName = osTargetName;
3,998✔
4358
        poTask->bIsMaxZoomForLayer = bIsMaxZoomForLayer;
3,998✔
4359
        poTask->poFeatureContent = poFeatureContent;
3,998✔
4360
        poTask->nSerial = nSerial;
3,998✔
4361
        poTask->poGeom = poGeom;
3,998✔
4362
        poTask->sEnvelope = sEnvelope;
3,998✔
4363
        m_oThreadPool.SubmitJob(OGRMVTWriterDataset::WriterTaskFunc, poTask);
3,998✔
4364
        // Do not queue more than 1000 jobs to avoid memory exhaustion
4365
        m_oThreadPool.WaitCompletion(1000);
3,998✔
4366

4367
        std::lock_guard oLock(m_oDBMutex);
3,998✔
4368
        return m_bWriteFeatureError ? OGRERR_FAILURE : OGRERR_NONE;
3,998✔
4369
    }
4370
}
4371

4372
/************************************************************************/
4373
/*                        UpdateLayerProperties()                       */
4374
/************************************************************************/
4375

4376
void OGRMVTWriterDataset::UpdateLayerProperties(
4,347✔
4377
    MVTLayerProperties *poLayerProperties, const std::string &osKey,
4378
    const MVTTileLayerValue &oValue)
4379
{
4380
    auto oFieldIter = poLayerProperties->m_oMapFieldNameToIdx.find(osKey);
4,347✔
4381
    MVTFieldProperties *poFieldProps = nullptr;
4,347✔
4382
    if (oFieldIter == poLayerProperties->m_oMapFieldNameToIdx.end())
4,347✔
4383
    {
4384
        if (poLayerProperties->m_oSetFields.size() < knMAX_COUNT_FIELDS)
180✔
4385
        {
4386
            poLayerProperties->m_oSetFields.insert(osKey);
180✔
4387
            if (poLayerProperties->m_oMapFieldNameToIdx.size() <
180✔
4388
                knMAX_REPORT_FIELDS)
4389
            {
4390
                MVTFieldProperties oFieldProps;
360✔
4391
                oFieldProps.m_osName = osKey;
180✔
4392
                if (oValue.isNumeric())
180✔
4393
                {
4394
                    oFieldProps.m_dfMinVal = oValue.getNumericValue();
73✔
4395
                    oFieldProps.m_dfMaxVal = oValue.getNumericValue();
73✔
4396
                    oFieldProps.m_bAllInt = true;  // overridden just below
73✔
4397
                }
4398
                oFieldProps.m_eType =
180✔
4399
                    oValue.isNumeric()  ? MVTTileLayerValue::ValueType::DOUBLE
287✔
4400
                    : oValue.isString() ? MVTTileLayerValue::ValueType::STRING
107✔
4401
                                        : MVTTileLayerValue::ValueType::BOOL;
4402

4403
                poLayerProperties->m_oMapFieldNameToIdx[osKey] =
180✔
4404
                    poLayerProperties->m_aoFields.size();
180✔
4405
                poLayerProperties->m_aoFields.push_back(oFieldProps);
180✔
4406
                poFieldProps = &(
4407
                    poLayerProperties
4408
                        ->m_aoFields[poLayerProperties->m_aoFields.size() - 1]);
180✔
4409
            }
4410
        }
4411
    }
4412
    else
4413
    {
4414
        poFieldProps = &(poLayerProperties->m_aoFields[oFieldIter->second]);
4,167✔
4415
    }
4416

4417
    if (poFieldProps)
4,347✔
4418
    {
4419
        if (oValue.getType() == MVTTileLayerValue::ValueType::BOOL)
4,347✔
4420
        {
4421
            MVTTileLayerValue oUniqVal;
24✔
4422
            oUniqVal.setBoolValue(oValue.getBoolValue());
12✔
4423
            poFieldProps->m_oSetAllValues.insert(oUniqVal);
12✔
4424
            poFieldProps->m_oSetValues.insert(oUniqVal);
12✔
4425
        }
4426
        else if (oValue.isNumeric())
4,335✔
4427
        {
4428
            if (poFieldProps->m_bAllInt)
1,780✔
4429
            {
4430
                poFieldProps->m_bAllInt =
935✔
4431
                    oValue.getType() == MVTTileLayerValue::ValueType::INT ||
1,870✔
4432
                    oValue.getType() == MVTTileLayerValue::ValueType::SINT ||
2,751✔
4433
                    (oValue.getType() == MVTTileLayerValue::ValueType::UINT &&
1,798✔
4434
                     oValue.getUIntValue() < GINT64_MAX);
881✔
4435
            }
4436
            double dfVal = oValue.getNumericValue();
1,780✔
4437
            poFieldProps->m_dfMinVal =
1,780✔
4438
                std::min(poFieldProps->m_dfMinVal, dfVal);
1,780✔
4439
            poFieldProps->m_dfMaxVal =
1,780✔
4440
                std::max(poFieldProps->m_dfMaxVal, dfVal);
1,780✔
4441
            if (poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
1,780✔
4442
            {
4443
                MVTTileLayerValue oUniqVal;
3,560✔
4444
                oUniqVal.setDoubleValue(dfVal);
1,780✔
4445
                poFieldProps->m_oSetAllValues.insert(oUniqVal);
1,780✔
4446
                if (poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
1,780✔
4447
                {
4448
                    poFieldProps->m_oSetValues.insert(oUniqVal);
1,780✔
4449
                }
4450
            }
4451
        }
4452
        else if (oValue.isString() &&
5,110✔
4453
                 poFieldProps->m_oSetAllValues.size() < knMAX_COUNT_VALUES)
2,555✔
4454
        {
4455
            auto osVal = oValue.getStringValue();
5,110✔
4456
            MVTTileLayerValue oUniqVal;
5,110✔
4457
            oUniqVal.setStringValue(osVal);
2,555✔
4458
            poFieldProps->m_oSetAllValues.insert(oUniqVal);
2,555✔
4459
            if (osVal.size() <= knMAX_STRING_VALUE_LENGTH &&
5,110✔
4460
                poFieldProps->m_oSetValues.size() < knMAX_REPORT_VALUES)
2,555✔
4461
            {
4462
                poFieldProps->m_oSetValues.insert(oUniqVal);
2,555✔
4463
            }
4464
        }
4465
    }
4466
}
4,347✔
4467

4468
/************************************************************************/
4469
/*                           GZIPCompress()                             */
4470
/************************************************************************/
4471

4472
static void GZIPCompress(std::string &oTileBuffer)
906✔
4473
{
4474
    if (!oTileBuffer.empty())
906✔
4475
    {
4476
        CPLString osTmpFilename(CPLSPrintf("/vsimem/%p.gz", &oTileBuffer));
1,812✔
4477
        CPLString osTmpGZipFilename("/vsigzip/" + osTmpFilename);
1,812✔
4478
        VSILFILE *fpGZip = VSIFOpenL(osTmpGZipFilename, "wb");
906✔
4479
        if (fpGZip)
906✔
4480
        {
4481
            VSIFWriteL(oTileBuffer.data(), 1, oTileBuffer.size(), fpGZip);
906✔
4482
            VSIFCloseL(fpGZip);
906✔
4483

4484
            vsi_l_offset nCompressedSize = 0;
906✔
4485
            GByte *pabyCompressed =
4486
                VSIGetMemFileBuffer(osTmpFilename, &nCompressedSize, false);
906✔
4487
            oTileBuffer.assign(reinterpret_cast<char *>(pabyCompressed),
4488
                               static_cast<size_t>(nCompressedSize));
906✔
4489
        }
4490
        VSIUnlink(osTmpFilename);
906✔
4491
    }
4492
}
906✔
4493

4494
/************************************************************************/
4495
/*                     GetReducedPrecisionGeometry()                    */
4496
/************************************************************************/
4497

4498
static std::vector<GUInt32>
4499
GetReducedPrecisionGeometry(MVTTileLayerFeature::GeomType eGeomType,
95✔
4500
                            const std::vector<GUInt32> &anSrcGeometry,
4501
                            GUInt32 nSrcExtent, GUInt32 nDstExtent)
4502
{
4503
    std::vector<GUInt32> anDstGeometry;
95✔
4504
    size_t nLastMoveToIdx = 0;
95✔
4505
    int nX = 0;
95✔
4506
    int nY = 0;
95✔
4507
    int nFirstReducedX = 0;
95✔
4508
    int nFirstReducedY = 0;
95✔
4509
    int nLastReducedX = 0;
95✔
4510
    int nLastReducedY = 0;
95✔
4511
    int nLastReducedXValid = 0;
95✔
4512
    int nLastReducedYValid = 0;
95✔
4513
    std::unique_ptr<OGRLinearRing> poInRing;
95✔
4514
    std::unique_ptr<OGRLinearRing> poOutRing;
95✔
4515
    std::unique_ptr<OGRLinearRing> poOutOuterRing;
95✔
4516
    bool bDiscardInnerRings = false;
95✔
4517
    const bool bIsPoly = eGeomType == MVTTileLayerFeature::GeomType::POLYGON;
95✔
4518
    for (size_t iSrc = 0; iSrc < anSrcGeometry.size();)
326✔
4519
    {
4520
        const unsigned nCount = GetCmdCount(anSrcGeometry[iSrc]);
231✔
4521
        switch (GetCmdId(anSrcGeometry[iSrc]))
231✔
4522
        {
4523
            case knCMD_MOVETO:
113✔
4524
            {
4525
                nLastMoveToIdx = anDstGeometry.size();
113✔
4526

4527
                anDstGeometry.push_back(anSrcGeometry[iSrc]);
113✔
4528
                iSrc++;
113✔
4529

4530
                unsigned nDstPoints = 0;
113✔
4531
                for (unsigned j = 0;
113✔
4532
                     iSrc + 1 < anSrcGeometry.size() && j < nCount;
226✔
4533
                     j++, iSrc += 2)
113✔
4534
                {
4535
                    nX += DecodeSInt(anSrcGeometry[iSrc]);
113✔
4536
                    nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
113✔
4537

4538
                    int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
113✔
4539
                                                     nDstExtent / nSrcExtent);
113✔
4540
                    int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
113✔
4541
                                                     nDstExtent / nSrcExtent);
113✔
4542
                    int nDiffX = nReducedX - nLastReducedX;
113✔
4543
                    int nDiffY = nReducedY - nLastReducedY;
113✔
4544
                    if (j == 0)
113✔
4545
                    {
4546
                        if (bIsPoly)
113✔
4547
                        {
4548
                            poInRing = std::unique_ptr<OGRLinearRing>(
82✔
4549
                                new OGRLinearRing());
82✔
4550
                            poOutRing = std::unique_ptr<OGRLinearRing>(
82✔
4551
                                new OGRLinearRing());
82✔
4552
                        }
4553
                        nFirstReducedX = nReducedX;
113✔
4554
                        nFirstReducedY = nReducedY;
113✔
4555
                    }
4556
                    if (j == 0 || nDiffX != 0 || nDiffY != 0)
113✔
4557
                    {
4558
                        if (bIsPoly)
113✔
4559
                        {
4560
                            poInRing->addPoint(nX, nY);
41✔
4561
                            poOutRing->addPoint(nReducedX, nReducedY);
41✔
4562
                        }
4563
                        nDstPoints++;
113✔
4564
                        anDstGeometry.push_back(EncodeSInt(nDiffX));
113✔
4565
                        anDstGeometry.push_back(EncodeSInt(nDiffY));
113✔
4566
                        nLastReducedX = nReducedX;
113✔
4567
                        nLastReducedY = nReducedY;
113✔
4568
                    }
4569
                }
4570
                // Patch count of MOVETO
4571
                anDstGeometry[nLastMoveToIdx] = GetCmdCountCombined(
113✔
4572
                    GetCmdId(anDstGeometry[nLastMoveToIdx]), nDstPoints);
113✔
4573
                break;
113✔
4574
            }
4575
            case knCMD_LINETO:
77✔
4576
            {
4577
                size_t nIdxToPatch = anDstGeometry.size();
77✔
4578
                anDstGeometry.push_back(anSrcGeometry[iSrc]);
77✔
4579
                iSrc++;
77✔
4580
                unsigned nDstPoints = 0;
77✔
4581
                int nLastReducedXBefore = nLastReducedX;
77✔
4582
                int nLastReducedYBefore = nLastReducedY;
77✔
4583
                for (unsigned j = 0;
77✔
4584
                     iSrc + 1 < anSrcGeometry.size() && j < nCount;
195✔
4585
                     j++, iSrc += 2)
118✔
4586
                {
4587
                    nX += DecodeSInt(anSrcGeometry[iSrc]);
118✔
4588
                    nY += DecodeSInt(anSrcGeometry[iSrc + 1]);
118✔
4589

4590
                    int nReducedX = static_cast<int>(static_cast<GIntBig>(nX) *
118✔
4591
                                                     nDstExtent / nSrcExtent);
118✔
4592
                    int nReducedY = static_cast<int>(static_cast<GIntBig>(nY) *
118✔
4593
                                                     nDstExtent / nSrcExtent);
118✔
4594
                    int nDiffX = nReducedX - nLastReducedX;
118✔
4595
                    int nDiffY = nReducedY - nLastReducedY;
118✔
4596
                    if (nDiffX != 0 || nDiffY != 0)
118✔
4597
                    {
4598
                        if (bIsPoly)
87✔
4599
                        {
4600
                            CPLAssert(poInRing);
60✔
4601
                            CPLAssert(poOutRing);
60✔
4602
                            poInRing->addPoint(nX, nY);
60✔
4603
                            poOutRing->addPoint(nReducedX, nReducedY);
60✔
4604
                        }
4605
                        nDstPoints++;
87✔
4606
                        anDstGeometry.push_back(EncodeSInt(nDiffX));
87✔
4607
                        anDstGeometry.push_back(EncodeSInt(nDiffY));
87✔
4608
                        nLastReducedXBefore = nLastReducedX;
87✔
4609
                        nLastReducedYBefore = nLastReducedY;
87✔
4610
                        nLastReducedX = nReducedX;
87✔
4611
                        nLastReducedY = nReducedY;
87✔
4612
                    }
4613
                }
4614

4615
                // If last point of ring is identical to first one, discard it
4616
                if (nDstPoints > 0 && bIsPoly &&
77✔
4617
                    nLastReducedX == nFirstReducedX &&
1✔
4618
                    nLastReducedY == nFirstReducedY)
4619
                {
4620
                    nLastReducedX = nLastReducedXBefore;
×
4621
                    nLastReducedY = nLastReducedYBefore;
×
4622
                    nDstPoints -= 1;
×
4623
                    anDstGeometry.resize(anDstGeometry.size() - 2);
×
4624
                    poOutRing->setNumPoints(poOutRing->getNumPoints() - 1);
×
4625
                }
4626

4627
                // Patch count of LINETO
4628
                anDstGeometry[nIdxToPatch] = GetCmdCountCombined(
77✔
4629
                    GetCmdId(anDstGeometry[nIdxToPatch]), nDstPoints);
77✔
4630

4631
                // A valid linestring should have at least one MOVETO +
4632
                // one coord pair + one LINETO + one coord pair
4633
                if (eGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
77✔
4634
                {
4635
                    if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2)
36✔
4636
                    {
4637
                        // Remove last linestring
4638
                        nLastReducedX = nLastReducedXValid;
9✔
4639
                        nLastReducedY = nLastReducedYValid;
9✔
4640
                        anDstGeometry.resize(nLastMoveToIdx);
9✔
4641
                    }
4642
                    else
4643
                    {
4644
                        nLastReducedXValid = nLastReducedX;
27✔
4645
                        nLastReducedYValid = nLastReducedY;
27✔
4646
                    }
4647
                }
4648

4649
                break;
77✔
4650
            }
4651
            case knCMD_CLOSEPATH:
41✔
4652
            {
4653
                CPLAssert(bIsPoly);
41✔
4654
                CPLAssert(poInRing);
41✔
4655
                CPLAssert(poOutRing);
41✔
4656
                int bIsValid = true;
41✔
4657

4658
                // A valid ring should have at least one MOVETO + one
4659
                // coord pair + one LINETO + two coord pairs
4660
                if (anDstGeometry.size() < nLastMoveToIdx + 1 + 2 + 1 + 2 * 2)
41✔
4661
                {
4662
                    // Remove ring. Normally if we remove an outer ring,
4663
                    // its inner rings should also be removed, given they are
4664
                    // smaller than the outer ring.
4665
                    bIsValid = false;
14✔
4666
                }
4667
                else
4668
                {
4669
                    poInRing->closeRings();
27✔
4670
                    poOutRing->closeRings();
27✔
4671
                    bool bIsOuterRing = !poInRing->isClockwise();
27✔
4672
                    // Normally the first ring of a polygon geometry should
4673
                    // be a outer ring, except when it is degenerate enough
4674
                    // in which case poOutOuterRing might be null.
4675
                    if (bIsOuterRing)
27✔
4676
                    {
4677
                        // if the outer ring turned out to be a inner ring
4678
                        // once reduced
4679
                        if (poOutRing->isClockwise())
18✔
4680
                        {
4681
                            bIsValid = false;
×
4682
                            bDiscardInnerRings = true;
×
4683
                        }
4684
                        else
4685
                        {
4686
                            OGRPolygon oPoly;
18✔
4687
                            oPoly.addRing(poOutRing.get());
18✔
4688
                            poOutOuterRing = std::unique_ptr<OGRLinearRing>(
36✔
4689
                                poOutRing.release());
18✔
4690
                            {
4691
                                CPLErrorStateBackuper oErrorStateBackuper(
4692
                                    CPLQuietErrorHandler);
18✔
4693
                                bIsValid = oPoly.IsValid();
18✔
4694
                            }
4695
                            bDiscardInnerRings = !bIsValid;
18✔
4696
                        }
4697
                    }
4698
                    else if (bDiscardInnerRings ||
9✔
4699
                             poOutOuterRing.get() == nullptr ||
18✔
4700
                             // if the inner ring turned out to be a outer ring
4701
                             // once reduced
4702
                             !poOutRing->isClockwise())
9✔
4703
                    {
4704
                        bIsValid = false;
×
4705
                    }
4706
                    else
4707
                    {
4708
                        OGRPolygon oPoly;
18✔
4709
                        oPoly.addRing(poOutOuterRing.get());
9✔
4710
                        oPoly.addRingDirectly(poOutRing.release());
9✔
4711
                        {
4712
                            CPLErrorStateBackuper oErrorStateBackuper(
4713
                                CPLQuietErrorHandler);
9✔
4714
                            bIsValid = oPoly.IsValid();
9✔
4715
                        }
4716
                    }
4717
                }
4718

4719
                if (bIsValid)
41✔
4720
                {
4721
                    nLastReducedXValid = nLastReducedX;
24✔
4722
                    nLastReducedYValid = nLastReducedY;
24✔
4723
                    anDstGeometry.push_back(anSrcGeometry[iSrc]);
24✔
4724
                }
4725
                else
4726
                {
4727
                    // Remove this ring
4728
                    nLastReducedX = nLastReducedXValid;
17✔
4729
                    nLastReducedY = nLastReducedYValid;
17✔
4730
                    anDstGeometry.resize(nLastMoveToIdx);
17✔
4731
                }
4732

4733
                iSrc++;
41✔
4734
                break;
41✔
4735
            }
4736
            default:
×
4737
            {
4738
                CPLAssert(false);
×
4739
                break;
4740
            }
4741
        }
4742
    }
4743

4744
    return anDstGeometry;
190✔
4745
}
4746

4747
/************************************************************************/
4748
/*                          EncodeFeature()                             */
4749
/************************************************************************/
4750

4751
void OGRMVTWriterDataset::EncodeFeature(
1,592✔
4752
    const void *pabyBlob, int nBlobSize,
4753
    std::shared_ptr<MVTTileLayer> &poTargetLayer,
4754
    std::map<CPLString, GUInt32> &oMapKeyToIdx,
4755
    std::map<MVTTileLayerValue, GUInt32> &oMapValueToIdx,
4756
    MVTLayerProperties *poLayerProperties, GUInt32 nExtent,
4757
    unsigned &nFeaturesInTile)
4758
{
4759
    size_t nUncompressedSize = 0;
1,592✔
4760
    void *pCompressed =
4761
        CPLZLibInflate(pabyBlob, nBlobSize, nullptr, 0, &nUncompressedSize);
1,592✔
4762
    GByte *pabyUncompressed = static_cast<GByte *>(pCompressed);
1,592✔
4763

4764
    MVTTileLayer oSrcTileLayer;
3,184✔
4765
    if (nUncompressedSize &&
1,592✔
4766
        oSrcTileLayer.read(pabyUncompressed,
1,592✔
4767
                           pabyUncompressed + nUncompressedSize))
1,592✔
4768
    {
4769
        const auto &srcFeatures = oSrcTileLayer.getFeatures();
1,592✔
4770
        if (srcFeatures.size() == 1)  // should always be true !
1,592✔
4771
        {
4772
            const auto &poSrcFeature = srcFeatures[0];
1,592✔
4773
            std::shared_ptr<MVTTileLayerFeature> poFeature(
4774
                new MVTTileLayerFeature());
3,184✔
4775

4776
            if (poSrcFeature->hasId())
1,592✔
4777
                poFeature->setId(poSrcFeature->getId());
53✔
4778
            poFeature->setType(poSrcFeature->getType());
1,592✔
4779
            if (poLayerProperties)
1,592✔
4780
            {
4781
                poLayerProperties->m_oCountGeomType[poSrcFeature->getType()]++;
1,491✔
4782
            }
4783
            bool bOK = true;
1,592✔
4784
            if (nExtent < m_nExtent)
1,592✔
4785
            {
4786
#ifdef for_debugging
4787
                const auto &srcKeys = oSrcTileLayer.getKeys();
4788
                const auto &srcValues = oSrcTileLayer.getValues();
4789
                const auto &anSrcTags = poSrcFeature->getTags();
4790
                for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
4791
                {
4792
                    GUInt32 nSrcIdxKey = anSrcTags[i];
4793
                    GUInt32 nSrcIdxValue = anSrcTags[i + 1];
4794
                    if (nSrcIdxKey < srcKeys.size() &&
4795
                        nSrcIdxValue < srcValues.size())
4796
                    {
4797
                        auto &osKey = srcKeys[nSrcIdxKey];
4798
                        auto &oValue = srcValues[nSrcIdxValue];
4799
                        if (osKey == "tunnus" &&
4800
                            oValue.getUIntValue() == 28799760)
4801
                        {
4802
                            printf("foo\n"); /* ok */
4803
                            break;
4804
                        }
4805
                    }
4806
                }
4807
#endif
4808

4809
                poFeature->setGeometry(GetReducedPrecisionGeometry(
95✔
4810
                    poSrcFeature->getType(), poSrcFeature->getGeometry(),
4811
                    m_nExtent, nExtent));
4812
                if (poFeature->getGeometry().empty())
95✔
4813
                {
4814
                    bOK = false;
14✔
4815
                }
4816
            }
4817
            else
4818
            {
4819
                poFeature->setGeometry(poSrcFeature->getGeometry());
1,497✔
4820
            }
4821
            if (bOK)
1,592✔
4822
            {
4823
                const auto &srcKeys = oSrcTileLayer.getKeys();
1,578✔
4824
                for (const auto &osKey : srcKeys)
5,979✔
4825
                {
4826
                    auto oIter = oMapKeyToIdx.find(osKey);
4,401✔
4827
                    if (oIter == oMapKeyToIdx.end())
4,401✔
4828
                    {
4829
                        oMapKeyToIdx[osKey] = poTargetLayer->addKey(osKey);
3,603✔
4830
                    }
4831
                }
4832

4833
                const auto &srcValues = oSrcTileLayer.getValues();
1,578✔
4834
                for (const auto &oValue : srcValues)
5,979✔
4835
                {
4836
                    auto oIter = oMapValueToIdx.find(oValue);
4,401✔
4837
                    if (oIter == oMapValueToIdx.end())
4,401✔
4838
                    {
4839
                        oMapValueToIdx[oValue] =
3,729✔
4840
                            poTargetLayer->addValue(oValue);
3,729✔
4841
                    }
4842
                }
4843

4844
                const auto &anSrcTags = poSrcFeature->getTags();
1,578✔
4845
                for (size_t i = 0; i + 1 < anSrcTags.size(); i += 2)
5,979✔
4846
                {
4847
                    GUInt32 nSrcIdxKey = anSrcTags[i];
4,401✔
4848
                    GUInt32 nSrcIdxValue = anSrcTags[i + 1];
4,401✔
4849
                    if (nSrcIdxKey < srcKeys.size() &&
8,802✔
4850
                        nSrcIdxValue < srcValues.size())
4,401✔
4851
                    {
4852
                        const auto &osKey = srcKeys[nSrcIdxKey];
4,401✔
4853
                        const auto &oValue = srcValues[nSrcIdxValue];
4,401✔
4854

4855
                        if (poLayerProperties)
4,401✔
4856
                        {
4857
                            UpdateLayerProperties(poLayerProperties, osKey,
4,347✔
4858
                                                  oValue);
4859
                        }
4860

4861
                        poFeature->addTag(oMapKeyToIdx[osKey]);
4,401✔
4862
                        poFeature->addTag(oMapValueToIdx[oValue]);
4,401✔
4863
                    }
4864
                }
4865

4866
                nFeaturesInTile++;
1,578✔
4867
                poTargetLayer->addFeature(std::move(poFeature));
1,578✔
4868
            }
4869
        }
4870
    }
4871
    else
4872
    {
4873
        // Shouldn't fail
4874
        CPLError(CE_Failure, CPLE_AppDefined, "Deserialization failure");
×
4875
    }
4876

4877
    CPLFree(pabyUncompressed);
1,592✔
4878
}
1,592✔
4879

4880
/************************************************************************/
4881
/*                            EncodeTile()                              */
4882
/************************************************************************/
4883

4884
std::string OGRMVTWriterDataset::EncodeTile(
841✔
4885
    int nZ, int nX, int nY, sqlite3_stmt *hStmtLayer, sqlite3_stmt *hStmtRows,
4886
    std::map<CPLString, MVTLayerProperties> &oMapLayerProps,
4887
    std::set<CPLString> &oSetLayers, GIntBig &nTempTilesRead)
4888
{
4889
    MVTTile oTargetTile;
1,682✔
4890

4891
    sqlite3_bind_int(hStmtLayer, 1, nZ);
841✔
4892
    sqlite3_bind_int(hStmtLayer, 2, nX);
841✔
4893
    sqlite3_bind_int(hStmtLayer, 3, nY);
841✔
4894

4895
    unsigned nFeaturesInTile = 0;
841✔
4896
    const GIntBig nProgressStep =
4897
        std::max(static_cast<GIntBig>(1), m_nTempTiles / 10);
841✔
4898

4899
    while (nFeaturesInTile < m_nMaxFeatures &&
4,240✔
4900
           sqlite3_step(hStmtLayer) == SQLITE_ROW)
2,117✔
4901
    {
4902
        const char *pszLayerName =
4903
            reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
1,282✔
4904
        sqlite3_bind_int(hStmtRows, 1, nZ);
1,282✔
4905
        sqlite3_bind_int(hStmtRows, 2, nX);
1,282✔
4906
        sqlite3_bind_int(hStmtRows, 3, nY);
1,282✔
4907
        sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
1,282✔
4908

4909
        auto oIterMapLayerProps = oMapLayerProps.find(pszLayerName);
1,282✔
4910
        MVTLayerProperties *poLayerProperties = nullptr;
1,282✔
4911
        if (oIterMapLayerProps == oMapLayerProps.end())
1,282✔
4912
        {
4913
            if (oSetLayers.size() < knMAX_COUNT_LAYERS)
71✔
4914
            {
4915
                oSetLayers.insert(pszLayerName);
71✔
4916
                if (oMapLayerProps.size() < knMAX_REPORT_LAYERS)
71✔
4917
                {
4918
                    MVTLayerProperties props;
71✔
4919
                    props.m_nMinZoom = nZ;
71✔
4920
                    props.m_nMaxZoom = nZ;
71✔
4921
                    oMapLayerProps[pszLayerName] = std::move(props);
71✔
4922
                    poLayerProperties = &(oMapLayerProps[pszLayerName]);
71✔
4923
                }
4924
            }
4925
        }
4926
        else
4927
        {
4928
            poLayerProperties = &(oIterMapLayerProps->second);
1,211✔
4929
        }
4930
        if (poLayerProperties)
1,282✔
4931
        {
4932
            poLayerProperties->m_nMinZoom =
1,282✔
4933
                std::min(nZ, poLayerProperties->m_nMinZoom);
1,282✔
4934
            poLayerProperties->m_nMaxZoom =
1,282✔
4935
                std::max(nZ, poLayerProperties->m_nMaxZoom);
1,282✔
4936
        }
4937

4938
        std::shared_ptr<MVTTileLayer> poTargetLayer(new MVTTileLayer());
2,564✔
4939
        oTargetTile.addLayer(poTargetLayer);
1,282✔
4940
        poTargetLayer->setName(pszLayerName);
1,282✔
4941
        poTargetLayer->setVersion(m_nMVTVersion);
1,282✔
4942
        poTargetLayer->setExtent(m_nExtent);
1,282✔
4943

4944
        std::map<CPLString, GUInt32> oMapKeyToIdx;
2,564✔
4945
        std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
2,564✔
4946

4947
        while (nFeaturesInTile < m_nMaxFeatures &&
5,540✔
4948
               sqlite3_step(hStmtRows) == SQLITE_ROW)
2,767✔
4949
        {
4950
            int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
1,491✔
4951
            const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
1,491✔
4952

4953
            EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
1,491✔
4954
                          oMapValueToIdx, poLayerProperties, m_nExtent,
4955
                          nFeaturesInTile);
4956

4957
            nTempTilesRead++;
1,491✔
4958
            if (nTempTilesRead == m_nTempTiles ||
1,491✔
4959
                (nTempTilesRead % nProgressStep) == 0)
1,443✔
4960
            {
4961
                const int nPct =
499✔
4962
                    static_cast<int>((100 * nTempTilesRead) / m_nTempTiles);
499✔
4963
                CPLDebug("MVT", "%d%%...", nPct);
499✔
4964
            }
4965
        }
4966
        sqlite3_reset(hStmtRows);
1,282✔
4967
    }
4968

4969
    sqlite3_reset(hStmtLayer);
841✔
4970

4971
    std::string oTileBuffer(oTargetTile.write());
1,682✔
4972
    size_t nSizeBefore = oTileBuffer.size();
841✔
4973
    if (m_bGZip)
841✔
4974
        GZIPCompress(oTileBuffer);
841✔
4975
    const size_t nSizeAfter = oTileBuffer.size();
841✔
4976
    const double dfCompressionRatio =
841✔
4977
        static_cast<double>(nSizeAfter) / nSizeBefore;
841✔
4978

4979
    // If the tile size is above the allowed values or there are too many
4980
    // features, then sort by descending area / length until we get to the
4981
    // limit.
4982
    bool bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
841✔
4983
    const bool bTooManyFeatures = nFeaturesInTile >= m_nMaxFeatures;
841✔
4984

4985
    GUInt32 nExtent = m_nExtent;
841✔
4986
    while (bTooBigTile && !bTooManyFeatures && nExtent >= 256)
893✔
4987
    {
4988
        nExtent /= 2;
52✔
4989
        nSizeBefore = oTileBuffer.size();
52✔
4990
        oTileBuffer = RecodeTileLowerResolution(nZ, nX, nY, nExtent, hStmtLayer,
104✔
4991
                                                hStmtRows);
52✔
4992
        bTooBigTile = oTileBuffer.size() > m_nMaxTileSize;
52✔
4993
        CPLDebug("MVT",
52✔
4994
                 "Recoding tile %d/%d/%d with extent = %u. "
4995
                 "From %u to %u bytes",
4996
                 nZ, nX, nY, nExtent, static_cast<unsigned>(nSizeBefore),
4997
                 static_cast<unsigned>(oTileBuffer.size()));
52✔
4998
    }
4999

5000
    if (bTooBigTile || bTooManyFeatures)
841✔
5001
    {
5002
        if (bTooBigTile)
13✔
5003
        {
5004
            CPLDebug("MVT", "For tile %d/%d/%d, tile size is %u > %u", nZ, nX,
7✔
5005
                     nY, static_cast<unsigned>(oTileBuffer.size()),
7✔
5006
                     m_nMaxTileSize);
5007
        }
5008
        if (bTooManyFeatures)
13✔
5009
        {
5010
            CPLDebug("MVT",
6✔
5011
                     "For tile %d/%d/%d, feature count limit of %u is reached",
5012
                     nZ, nX, nY, m_nMaxFeatures);
5013
        }
5014

5015
        oTargetTile.clear();
13✔
5016

5017
        const unsigned nTotalFeaturesInTile =
5018
            std::min(m_nMaxFeatures, nFeaturesInTile);
13✔
5019
        char *pszSQL =
5020
            sqlite3_mprintf("SELECT layer, feature FROM temp "
13✔
5021
                            "WHERE z = %d AND x = %d AND y = %d ORDER BY "
5022
                            "area_or_length DESC LIMIT %d",
5023
                            nZ, nX, nY, nTotalFeaturesInTile);
5024
        sqlite3_stmt *hTmpStmt = nullptr;
13✔
5025
        CPL_IGNORE_RET_VAL(
13✔
5026
            sqlite3_prepare_v2(m_hDB, pszSQL, -1, &hTmpStmt, nullptr));
13✔
5027
        sqlite3_free(pszSQL);
13✔
5028
        if (!hTmpStmt)
13✔
5029
            return std::string();
×
5030

5031
        class TargetTileLayerProps
5032
        {
5033
          public:
5034
            std::shared_ptr<MVTTileLayer> m_poLayer;
5035
            std::map<CPLString, GUInt32> m_oMapKeyToIdx;
5036
            std::map<MVTTileLayerValue, GUInt32> m_oMapValueToIdx;
5037
        };
5038

5039
        std::map<std::string, TargetTileLayerProps> oMapLayerNameToTargetLayer;
26✔
5040

5041
        nFeaturesInTile = 0;
13✔
5042
        const unsigned nCheckStep = std::max(1U, nTotalFeaturesInTile / 100);
13✔
5043
        while (sqlite3_step(hTmpStmt) == SQLITE_ROW)
25✔
5044
        {
5045
            const char *pszLayerName = reinterpret_cast<const char *>(
5046
                sqlite3_column_text(hTmpStmt, 0));
19✔
5047
            int nBlobSize = sqlite3_column_bytes(hTmpStmt, 1);
19✔
5048
            const void *pabyBlob = sqlite3_column_blob(hTmpStmt, 1);
19✔
5049

5050
            std::shared_ptr<MVTTileLayer> poTargetLayer;
×
5051
            std::map<CPLString, GUInt32> *poMapKeyToIdx;
5052
            std::map<MVTTileLayerValue, GUInt32> *poMapValueToIdx;
5053
            auto oIter = oMapLayerNameToTargetLayer.find(pszLayerName);
19✔
5054
            if (oIter == oMapLayerNameToTargetLayer.end())
19✔
5055
            {
5056
                poTargetLayer =
5057
                    std::shared_ptr<MVTTileLayer>(new MVTTileLayer());
13✔
5058
                TargetTileLayerProps props;
13✔
5059
                props.m_poLayer = poTargetLayer;
13✔
5060
                oTargetTile.addLayer(poTargetLayer);
13✔
5061
                poTargetLayer->setName(pszLayerName);
13✔
5062
                poTargetLayer->setVersion(m_nMVTVersion);
13✔
5063
                poTargetLayer->setExtent(nExtent);
13✔
5064
                oMapLayerNameToTargetLayer[pszLayerName] = std::move(props);
13✔
5065
                poMapKeyToIdx =
13✔
5066
                    &oMapLayerNameToTargetLayer[pszLayerName].m_oMapKeyToIdx;
13✔
5067
                poMapValueToIdx =
13✔
5068
                    &oMapLayerNameToTargetLayer[pszLayerName].m_oMapValueToIdx;
13✔
5069
            }
5070
            else
5071
            {
5072
                poTargetLayer = oIter->second.m_poLayer;
6✔
5073
                poMapKeyToIdx = &oIter->second.m_oMapKeyToIdx;
6✔
5074
                poMapValueToIdx = &oIter->second.m_oMapValueToIdx;
6✔
5075
            }
5076

5077
            EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, *poMapKeyToIdx,
19✔
5078
                          *poMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
5079

5080
            if (nFeaturesInTile == nTotalFeaturesInTile ||
19✔
5081
                (bTooBigTile && (nFeaturesInTile % nCheckStep == 0)))
9✔
5082
            {
5083
                if (oTargetTile.getSize() * dfCompressionRatio > m_nMaxTileSize)
19✔
5084
                {
5085
                    break;
7✔
5086
                }
5087
            }
5088
        }
5089

5090
        oTileBuffer = oTargetTile.write();
13✔
5091
        if (m_bGZip)
13✔
5092
            GZIPCompress(oTileBuffer);
13✔
5093

5094
        if (bTooBigTile)
13✔
5095
        {
5096
            CPLDebug("MVT", "For tile %d/%d/%d, final tile size is %u", nZ, nX,
7✔
5097
                     nY, static_cast<unsigned>(oTileBuffer.size()));
7✔
5098
        }
5099

5100
        sqlite3_finalize(hTmpStmt);
13✔
5101
    }
5102

5103
    return oTileBuffer;
841✔
5104
}
5105

5106
/************************************************************************/
5107
/*                    RecodeTileLowerResolution()                       */
5108
/************************************************************************/
5109

5110
std::string OGRMVTWriterDataset::RecodeTileLowerResolution(
52✔
5111
    int nZ, int nX, int nY, int nExtent, sqlite3_stmt *hStmtLayer,
5112
    sqlite3_stmt *hStmtRows)
5113
{
5114
    MVTTile oTargetTile;
104✔
5115

5116
    sqlite3_bind_int(hStmtLayer, 1, nZ);
52✔
5117
    sqlite3_bind_int(hStmtLayer, 2, nX);
52✔
5118
    sqlite3_bind_int(hStmtLayer, 3, nY);
52✔
5119

5120
    unsigned nFeaturesInTile = 0;
52✔
5121
    while (nFeaturesInTile < m_nMaxFeatures &&
208✔
5122
           sqlite3_step(hStmtLayer) == SQLITE_ROW)
104✔
5123
    {
5124
        const char *pszLayerName =
5125
            reinterpret_cast<const char *>(sqlite3_column_text(hStmtLayer, 0));
52✔
5126
        sqlite3_bind_int(hStmtRows, 1, nZ);
52✔
5127
        sqlite3_bind_int(hStmtRows, 2, nX);
52✔
5128
        sqlite3_bind_int(hStmtRows, 3, nY);
52✔
5129
        sqlite3_bind_text(hStmtRows, 4, pszLayerName, -1, SQLITE_STATIC);
52✔
5130

5131
        std::shared_ptr<MVTTileLayer> poTargetLayer(new MVTTileLayer());
104✔
5132
        oTargetTile.addLayer(poTargetLayer);
52✔
5133
        poTargetLayer->setName(pszLayerName);
52✔
5134
        poTargetLayer->setVersion(m_nMVTVersion);
52✔
5135
        poTargetLayer->setExtent(nExtent);
52✔
5136

5137
        std::map<CPLString, GUInt32> oMapKeyToIdx;
104✔
5138
        std::map<MVTTileLayerValue, GUInt32> oMapValueToIdx;
104✔
5139

5140
        while (nFeaturesInTile < m_nMaxFeatures &&
268✔
5141
               sqlite3_step(hStmtRows) == SQLITE_ROW)
134✔
5142
        {
5143
            int nBlobSize = sqlite3_column_bytes(hStmtRows, 0);
82✔
5144
            const void *pabyBlob = sqlite3_column_blob(hStmtRows, 0);
82✔
5145

5146
            EncodeFeature(pabyBlob, nBlobSize, poTargetLayer, oMapKeyToIdx,
82✔
5147
                          oMapValueToIdx, nullptr, nExtent, nFeaturesInTile);
5148
        }
5149
        sqlite3_reset(hStmtRows);
52✔
5150
    }
5151

5152
    sqlite3_reset(hStmtLayer);
52✔
5153

5154
    std::string oTileBuffer(oTargetTile.write());
52✔
5155
    if (m_bGZip)
52✔
5156
        GZIPCompress(oTileBuffer);
52✔
5157

5158
    return oTileBuffer;
104✔
5159
}
5160

5161
/************************************************************************/
5162
/*                            CreateOutput()                            */
5163
/************************************************************************/
5164

5165
bool OGRMVTWriterDataset::CreateOutput()
117✔
5166
{
5167
    if (m_bThreadPoolOK)
117✔
5168
        m_oThreadPool.WaitCompletion();
115✔
5169

5170
    std::map<CPLString, MVTLayerProperties> oMapLayerProps;
234✔
5171
    std::set<CPLString> oSetLayers;
234✔
5172

5173
    if (!m_oEnvelope.IsInit())
117✔
5174
    {
5175
        return GenerateMetadata(0, oMapLayerProps);
50✔
5176
    }
5177

5178
    CPLDebug("MVT", "Building output file from temporary database...");
67✔
5179

5180
    sqlite3_stmt *hStmtZXY = nullptr;
67✔
5181
    CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
67✔
5182
        m_hDB, "SELECT DISTINCT z, x, y FROM temp ORDER BY z, x, y", -1,
5183
        &hStmtZXY, nullptr));
5184
    if (hStmtZXY == nullptr)
67✔
5185
    {
5186
        CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
2✔
5187
        return false;
2✔
5188
    }
5189

5190
    sqlite3_stmt *hStmtLayer = nullptr;
65✔
5191
    CPL_IGNORE_RET_VAL(
65✔
5192
        sqlite3_prepare_v2(m_hDB,
65✔
5193
                           "SELECT DISTINCT layer FROM temp "
5194
                           "WHERE z = ? AND x = ? AND y = ? ORDER BY layer",
5195
                           -1, &hStmtLayer, nullptr));
5196
    if (hStmtLayer == nullptr)
65✔
5197
    {
5198
        CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
×
5199
        sqlite3_finalize(hStmtZXY);
×
5200
        return false;
×
5201
    }
5202
    sqlite3_stmt *hStmtRows = nullptr;
65✔
5203
    CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
65✔
5204
        m_hDB,
5205
        "SELECT feature FROM temp "
5206
        "WHERE z = ? AND x = ? AND y = ? AND layer = ? ORDER BY idx",
5207
        -1, &hStmtRows, nullptr));
5208
    if (hStmtRows == nullptr)
65✔
5209
    {
5210
        CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
×
5211
        sqlite3_finalize(hStmtZXY);
×
5212
        sqlite3_finalize(hStmtLayer);
×
5213
        return false;
×
5214
    }
5215

5216
    sqlite3_stmt *hInsertStmt = nullptr;
65✔
5217
    if (m_hDBMBTILES)
65✔
5218
    {
5219
        CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
41✔
5220
            m_hDBMBTILES,
5221
            "INSERT INTO tiles(zoom_level, tile_column, tile_row, "
5222
            "tile_data) VALUES (?,?,?,?)",
5223
            -1, &hInsertStmt, nullptr));
5224
        if (hInsertStmt == nullptr)
41✔
5225
        {
5226
            CPLError(CE_Failure, CPLE_AppDefined, "Prepared statement failed");
×
5227
            sqlite3_finalize(hStmtZXY);
×
5228
            sqlite3_finalize(hStmtLayer);
×
5229
            sqlite3_finalize(hStmtRows);
×
5230
            return false;
×
5231
        }
5232
    }
5233

5234
    int nLastZ = -1;
65✔
5235
    int nLastX = -1;
65✔
5236
    bool bRet = true;
65✔
5237
    GIntBig nTempTilesRead = 0;
65✔
5238

5239
    while (sqlite3_step(hStmtZXY) == SQLITE_ROW)
905✔
5240
    {
5241
        int nZ = sqlite3_column_int(hStmtZXY, 0);
841✔
5242
        int nX = sqlite3_column_int(hStmtZXY, 1);
841✔
5243
        int nY = sqlite3_column_int(hStmtZXY, 2);
841✔
5244

5245
        std::string oTileBuffer(EncodeTile(nZ, nX, nY, hStmtLayer, hStmtRows,
5246
                                           oMapLayerProps, oSetLayers,
5247
                                           nTempTilesRead));
841✔
5248

5249
        if (oTileBuffer.empty())
841✔
5250
        {
5251
            bRet = false;
×
5252
        }
5253
        else if (hInsertStmt)
841✔
5254
        {
5255
            sqlite3_bind_int(hInsertStmt, 1, nZ);
519✔
5256
            sqlite3_bind_int(hInsertStmt, 2, nX);
519✔
5257
            sqlite3_bind_int(hInsertStmt, 3, (1 << nZ) - 1 - nY);
519✔
5258
            sqlite3_bind_blob(hInsertStmt, 4, oTileBuffer.data(),
519✔
5259
                              static_cast<int>(oTileBuffer.size()),
519✔
5260
                              SQLITE_STATIC);
5261
            const int rc = sqlite3_step(hInsertStmt);
519✔
5262
            bRet = (rc == SQLITE_OK || rc == SQLITE_DONE);
519✔
5263
            sqlite3_reset(hInsertStmt);
519✔
5264
        }
5265
        else
5266
        {
5267
            CPLString osZDirname(CPLFormFilename(
5268
                GetDescription(), CPLSPrintf("%d", nZ), nullptr));
644✔
5269
            CPLString osXDirname(
5270
                CPLFormFilename(osZDirname, CPLSPrintf("%d", nX), nullptr));
644✔
5271
            if (nZ != nLastZ)
322✔
5272
            {
5273
                VSIMkdir(osZDirname, 0755);
101✔
5274
                nLastZ = nZ;
101✔
5275
                nLastX = -1;
101✔
5276
            }
5277
            if (nX != nLastX)
322✔
5278
            {
5279
                VSIMkdir(osXDirname, 0755);
180✔
5280
                nLastX = nX;
180✔
5281
            }
5282
            CPLString osTileFilename(CPLFormFilename(
5283
                osXDirname, CPLSPrintf("%d", nY), m_osExtension.c_str()));
644✔
5284
            VSILFILE *fpOut = VSIFOpenL(osTileFilename, "wb");
322✔
5285
            if (fpOut)
322✔
5286
            {
5287
                const size_t nRet = VSIFWriteL(oTileBuffer.data(), 1,
321✔
5288
                                               oTileBuffer.size(), fpOut);
5289
                bRet = (nRet == oTileBuffer.size());
321✔
5290
                VSIFCloseL(fpOut);
321✔
5291
            }
5292
            else
5293
            {
5294
                bRet = false;
1✔
5295
            }
5296
        }
5297

5298
        if (!bRet)
841✔
5299
        {
5300
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
5301
                     "Error while writing tile %d/%d/%d", nZ, nX, nY);
5302
            break;
1✔
5303
        }
5304
    }
5305
    sqlite3_finalize(hStmtZXY);
65✔
5306
    sqlite3_finalize(hStmtLayer);
65✔
5307
    sqlite3_finalize(hStmtRows);
65✔
5308
    if (hInsertStmt)
65✔
5309
        sqlite3_finalize(hInsertStmt);
41✔
5310

5311
    bRet &= GenerateMetadata(oSetLayers.size(), oMapLayerProps);
65✔
5312

5313
    return bRet;
65✔
5314
}
5315

5316
/************************************************************************/
5317
/*                     SphericalMercatorToLongLat()                     */
5318
/************************************************************************/
5319

5320
static void SphericalMercatorToLongLat(double *x, double *y)
228✔
5321
{
5322
    double lng = *x / kmSPHERICAL_RADIUS / M_PI * 180;
228✔
5323
    double lat =
5324
        2 * (atan(exp(*y / kmSPHERICAL_RADIUS)) - M_PI / 4) / M_PI * 180;
228✔
5325
    *x = lng;
228✔
5326
    *y = lat;
228✔
5327
}
228✔
5328

5329
/************************************************************************/
5330
/*                          WriteMetadataItem()                         */
5331
/************************************************************************/
5332

5333
template <class T>
5334
static bool WriteMetadataItemT(const char *pszKey, T value,
1,227✔
5335
                               const char *pszValueFormat, sqlite3 *hDBMBTILES,
5336
                               CPLJSONObject &oRoot)
5337
{
5338
    if (hDBMBTILES)
1,227✔
5339
    {
5340
        char *pszSQL;
5341

5342
        pszSQL = sqlite3_mprintf(
803✔
5343
            CPLSPrintf("INSERT INTO metadata(name, value) VALUES('%%q', '%s')",
5344
                       pszValueFormat),
5345
            pszKey, value);
5346
        OGRErr eErr = SQLCommand(hDBMBTILES, pszSQL);
803✔
5347
        sqlite3_free(pszSQL);
803✔
5348
        return eErr == OGRERR_NONE;
803✔
5349
    }
5350
    else
5351
    {
5352
        oRoot.Add(pszKey, value);
424✔
5353
        return true;
424✔
5354
    }
5355
}
5356

5357
/************************************************************************/
5358
/*                          WriteMetadataItem()                         */
5359
/************************************************************************/
5360

5361
static bool WriteMetadataItem(const char *pszKey, const char *pszValue,
879✔
5362
                              sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
5363
{
5364
    return WriteMetadataItemT(pszKey, pszValue, "%q", hDBMBTILES, oRoot);
879✔
5365
}
5366

5367
/************************************************************************/
5368
/*                          WriteMetadataItem()                         */
5369
/************************************************************************/
5370

5371
static bool WriteMetadataItem(const char *pszKey, int nValue,
345✔
5372
                              sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
5373
{
5374
    return WriteMetadataItemT(pszKey, nValue, "%d", hDBMBTILES, oRoot);
345✔
5375
}
5376

5377
/************************************************************************/
5378
/*                          WriteMetadataItem()                         */
5379
/************************************************************************/
5380

5381
static bool WriteMetadataItem(const char *pszKey, double dfValue,
3✔
5382
                              sqlite3 *hDBMBTILES, CPLJSONObject &oRoot)
5383
{
5384
    return WriteMetadataItemT(pszKey, dfValue, "%.18g", hDBMBTILES, oRoot);
3✔
5385
}
5386

5387
/************************************************************************/
5388
/*                          GenerateMetadata()                          */
5389
/************************************************************************/
5390

5391
bool OGRMVTWriterDataset::GenerateMetadata(
115✔
5392
    size_t nLayers, const std::map<CPLString, MVTLayerProperties> &oMap)
5393
{
5394
    CPLJSONDocument oDoc;
230✔
5395
    CPLJSONObject oRoot = oDoc.GetRoot();
230✔
5396

5397
    OGRSpatialReference oSRS_EPSG3857;
230✔
5398
    double dfTopXWebMercator;
5399
    double dfTopYWebMercator;
5400
    double dfTileDim0WebMercator;
5401
    InitWebMercatorTilingScheme(&oSRS_EPSG3857, dfTopXWebMercator,
115✔
5402
                                dfTopYWebMercator, dfTileDim0WebMercator);
5403
    const bool bIsStandardTilingScheme =
5404
        m_poSRS->IsSame(&oSRS_EPSG3857) && m_dfTopX == dfTopXWebMercator &&
229✔
5405
        m_dfTopY == dfTopYWebMercator && m_dfTileDim0 == dfTileDim0WebMercator;
229✔
5406
    if (bIsStandardTilingScheme)
115✔
5407
    {
5408
        SphericalMercatorToLongLat(&(m_oEnvelope.MinX), &(m_oEnvelope.MinY));
114✔
5409
        SphericalMercatorToLongLat(&(m_oEnvelope.MaxX), &(m_oEnvelope.MaxY));
114✔
5410
        m_oEnvelope.MinY = std::max(-85.0, m_oEnvelope.MinY);
114✔
5411
        m_oEnvelope.MaxY = std::min(85.0, m_oEnvelope.MaxY);
114✔
5412
    }
5413
    else
5414
    {
5415
        OGRSpatialReference oSRS_EPSG4326;
2✔
5416
        oSRS_EPSG4326.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
1✔
5417
        oSRS_EPSG4326.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1✔
5418
        OGRCoordinateTransformation *poCT =
5419
            OGRCreateCoordinateTransformation(m_poSRS, &oSRS_EPSG4326);
1✔
5420
        if (poCT)
1✔
5421
        {
5422
            OGRPoint oPoint1(m_oEnvelope.MinX, m_oEnvelope.MinY);
2✔
5423
            oPoint1.transform(poCT);
1✔
5424
            OGRPoint oPoint2(m_oEnvelope.MinX, m_oEnvelope.MaxY);
2✔
5425
            oPoint2.transform(poCT);
1✔
5426
            OGRPoint oPoint3(m_oEnvelope.MaxX, m_oEnvelope.MaxY);
2✔
5427
            oPoint3.transform(poCT);
1✔
5428
            OGRPoint oPoint4(m_oEnvelope.MaxX, m_oEnvelope.MinY);
2✔
5429
            oPoint4.transform(poCT);
1✔
5430
            m_oEnvelope.MinX =
1✔
5431
                std::min(std::min(oPoint1.getX(), oPoint2.getX()),
1✔
5432
                         std::min(oPoint3.getX(), oPoint4.getX()));
2✔
5433
            m_oEnvelope.MinY =
1✔
5434
                std::min(std::min(oPoint1.getY(), oPoint2.getY()),
1✔
5435
                         std::min(oPoint3.getY(), oPoint4.getY()));
2✔
5436
            m_oEnvelope.MaxX =
1✔
5437
                std::max(std::max(oPoint1.getX(), oPoint2.getX()),
1✔
5438
                         std::max(oPoint3.getX(), oPoint4.getX()));
2✔
5439
            m_oEnvelope.MaxY =
1✔
5440
                std::max(std::max(oPoint1.getY(), oPoint2.getY()),
1✔
5441
                         std::max(oPoint3.getY(), oPoint4.getY()));
2✔
5442
            delete poCT;
1✔
5443
        }
5444
    }
5445
    const double dfCenterX = (m_oEnvelope.MinX + m_oEnvelope.MaxX) / 2;
115✔
5446
    const double dfCenterY = (m_oEnvelope.MinY + m_oEnvelope.MaxY) / 2;
115✔
5447
    CPLString osCenter(
5448
        CPLSPrintf("%.7f,%.7f,%d", dfCenterX, dfCenterY, m_nMinZoom));
230✔
5449
    CPLString osBounds(CPLSPrintf("%.7f,%.7f,%.7f,%.7f", m_oEnvelope.MinX,
5450
                                  m_oEnvelope.MinY, m_oEnvelope.MaxX,
5451
                                  m_oEnvelope.MaxY));
230✔
5452

5453
    WriteMetadataItem("name", m_osName, m_hDBMBTILES, oRoot);
115✔
5454
    WriteMetadataItem("description", m_osDescription, m_hDBMBTILES, oRoot);
115✔
5455
    WriteMetadataItem("version", m_nMetadataVersion, m_hDBMBTILES, oRoot);
115✔
5456
    WriteMetadataItem("minzoom", m_nMinZoom, m_hDBMBTILES, oRoot);
115✔
5457
    WriteMetadataItem("maxzoom", m_nMaxZoom, m_hDBMBTILES, oRoot);
115✔
5458
    WriteMetadataItem("center", !m_osCenter.empty() ? m_osCenter : osCenter,
115✔
5459
                      m_hDBMBTILES, oRoot);
5460
    WriteMetadataItem("bounds", !m_osBounds.empty() ? m_osBounds : osBounds,
115✔
5461
                      m_hDBMBTILES, oRoot);
5462
    WriteMetadataItem("type", m_osType, m_hDBMBTILES, oRoot);
115✔
5463
    WriteMetadataItem("format", "pbf", m_hDBMBTILES, oRoot);
115✔
5464
    if (m_hDBMBTILES)
115✔
5465
    {
5466
        WriteMetadataItem("scheme", "tms", m_hDBMBTILES, oRoot);
73✔
5467
    }
5468

5469
    // GDAL extension for custom tiling schemes
5470
    if (!bIsStandardTilingScheme)
115✔
5471
    {
5472
        const char *pszAuthName = m_poSRS->GetAuthorityName(nullptr);
1✔
5473
        const char *pszAuthCode = m_poSRS->GetAuthorityCode(nullptr);
1✔
5474
        if (pszAuthName && pszAuthCode)
1✔
5475
        {
5476
            WriteMetadataItem("crs",
1✔
5477
                              CPLSPrintf("%s:%s", pszAuthName, pszAuthCode),
5478
                              m_hDBMBTILES, oRoot);
5479
        }
5480
        else
5481
        {
5482
            char *pszWKT = nullptr;
×
5483
            m_poSRS->exportToWkt(&pszWKT);
×
5484
            WriteMetadataItem("crs", pszWKT, m_hDBMBTILES, oRoot);
×
5485
            CPLFree(pszWKT);
×
5486
        }
5487
        WriteMetadataItem("tile_origin_upper_left_x", m_dfTopX, m_hDBMBTILES,
1✔
5488
                          oRoot);
5489
        WriteMetadataItem("tile_origin_upper_left_y", m_dfTopY, m_hDBMBTILES,
1✔
5490
                          oRoot);
5491
        WriteMetadataItem("tile_dimension_zoom_0", m_dfTileDim0, m_hDBMBTILES,
1✔
5492
                          oRoot);
5493
    }
5494

5495
    CPLJSONDocument oJsonDoc;
230✔
5496
    CPLJSONObject oJsonRoot = oJsonDoc.GetRoot();
230✔
5497

5498
    CPLJSONArray oVectorLayers;
230✔
5499
    oJsonRoot.Add("vector_layers", oVectorLayers);
115✔
5500
    std::set<std::string> oAlreadyVisited;
230✔
5501
    for (const auto &poLayer : m_apoLayers)
277✔
5502
    {
5503
        auto oIter = oMap.find(poLayer->m_osTargetName);
162✔
5504
        if (oIter != oMap.end() &&
233✔
5505
            oAlreadyVisited.find(poLayer->m_osTargetName) ==
71✔
5506
                oAlreadyVisited.end())
233✔
5507
        {
5508
            oAlreadyVisited.insert(poLayer->m_osTargetName);
71✔
5509

5510
            CPLJSONObject oLayerObj;
142✔
5511
            oLayerObj.Add("id", poLayer->m_osTargetName);
71✔
5512
            oLayerObj.Add("description",
71✔
5513
                          m_oMapLayerNameToDesc[poLayer->m_osTargetName]);
71✔
5514
            oLayerObj.Add("minzoom", oIter->second.m_nMinZoom);
71✔
5515
            oLayerObj.Add("maxzoom", oIter->second.m_nMaxZoom);
71✔
5516

5517
            CPLJSONObject oFields;
142✔
5518
            oLayerObj.Add("fields", oFields);
71✔
5519
            auto poFDefn = poLayer->GetLayerDefn();
71✔
5520
            for (int i = 0; i < poFDefn->GetFieldCount(); i++)
274✔
5521
            {
5522
                auto poFieldDefn = poFDefn->GetFieldDefn(i);
203✔
5523
                auto eType = poFieldDefn->GetType();
203✔
5524
                if (eType == OFTInteger &&
238✔
5525
                    poFieldDefn->GetSubType() == OFSTBoolean)
35✔
5526
                {
5527
                    oFields.Add(poFieldDefn->GetNameRef(), "Boolean");
1✔
5528
                }
5529
                else if (eType == OFTInteger || eType == OFTInteger64 ||
202✔
5530
                         eType == OFTReal)
5531
                {
5532
                    oFields.Add(poFieldDefn->GetNameRef(), "Number");
73✔
5533
                }
5534
                else
5535
                {
5536
                    oFields.Add(poFieldDefn->GetNameRef(), "String");
129✔
5537
                }
5538
            }
5539

5540
            oVectorLayers.Add(oLayerObj);
71✔
5541
        }
5542
    }
5543

5544
    CPLJSONObject oTileStats;
230✔
5545
    oJsonRoot.Add("tilestats", oTileStats);
115✔
5546
    oTileStats.Add("layerCount", static_cast<int>(nLayers));
115✔
5547
    CPLJSONArray oTileStatsLayers;
230✔
5548
    oTileStats.Add("layers", oTileStatsLayers);
115✔
5549
    oAlreadyVisited.clear();
115✔
5550
    for (const auto &poLayer : m_apoLayers)
277✔
5551
    {
5552
        auto oIter = oMap.find(poLayer->m_osTargetName);
162✔
5553
        if (oIter != oMap.end() &&
233✔
5554
            oAlreadyVisited.find(poLayer->m_osTargetName) ==
71✔
5555
                oAlreadyVisited.end())
233✔
5556
        {
5557
            oAlreadyVisited.insert(poLayer->m_osTargetName);
71✔
5558
            auto &oLayerProps = oIter->second;
71✔
5559
            CPLJSONObject oLayerObj;
142✔
5560

5561
            std::string osName(poLayer->m_osTargetName);
142✔
5562
            osName.resize(std::min(knMAX_LAYER_NAME_LENGTH, osName.size()));
71✔
5563
            oLayerObj.Add("layer", osName);
71✔
5564
            oLayerObj.Add(
71✔
5565
                "count",
5566
                m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]);
71✔
5567

5568
            // Find majority geometry type
5569
            MVTTileLayerFeature::GeomType eMaxGeomType =
71✔
5570
                MVTTileLayerFeature::GeomType::UNKNOWN;
5571
            GIntBig nMaxCountGeom = 0;
71✔
5572
            for (int i = static_cast<int>(MVTTileLayerFeature::GeomType::POINT);
284✔
5573
                 i <= static_cast<int>(MVTTileLayerFeature::GeomType::POLYGON);
284✔
5574
                 i++)
5575
            {
5576
                MVTTileLayerFeature::GeomType eGeomType =
213✔
5577
                    static_cast<MVTTileLayerFeature::GeomType>(i);
213✔
5578
                auto oIterCountGeom =
5579
                    oLayerProps.m_oCountGeomType.find(eGeomType);
213✔
5580
                if (oIterCountGeom != oLayerProps.m_oCountGeomType.end())
213✔
5581
                {
5582
                    if (oIterCountGeom->second >= nMaxCountGeom)
74✔
5583
                    {
5584
                        eMaxGeomType = eGeomType;
73✔
5585
                        nMaxCountGeom = oIterCountGeom->second;
73✔
5586
                    }
5587
                }
5588
            }
5589
            if (eMaxGeomType == MVTTileLayerFeature::GeomType::POINT)
71✔
5590
                oLayerObj.Add("geometry", "Point");
60✔
5591
            else if (eMaxGeomType == MVTTileLayerFeature::GeomType::LINESTRING)
11✔
5592
                oLayerObj.Add("geometry", "LineString");
5✔
5593
            else if (eMaxGeomType == MVTTileLayerFeature::GeomType::POLYGON)
6✔
5594
                oLayerObj.Add("geometry", "Polygon");
6✔
5595

5596
            oLayerObj.Add("attributeCount",
71✔
5597
                          static_cast<int>(oLayerProps.m_oSetFields.size()));
71✔
5598
            CPLJSONArray oAttributes;
142✔
5599
            oLayerObj.Add("attributes", oAttributes);
71✔
5600
            for (const auto &oFieldProps : oLayerProps.m_aoFields)
251✔
5601
            {
5602
                CPLJSONObject oFieldObj;
360✔
5603
                oAttributes.Add(oFieldObj);
180✔
5604
                std::string osFieldNameTruncated(oFieldProps.m_osName);
360✔
5605
                osFieldNameTruncated.resize(std::min(
180✔
5606
                    knMAX_FIELD_NAME_LENGTH, osFieldNameTruncated.size()));
180✔
5607
                oFieldObj.Add("attribute", osFieldNameTruncated);
180✔
5608
                oFieldObj.Add("count", static_cast<int>(
180✔
5609
                                           oFieldProps.m_oSetAllValues.size()));
180✔
5610
                oFieldObj.Add("type",
180✔
5611
                              oFieldProps.m_eType ==
180✔
5612
                                      MVTTileLayerValue::ValueType::DOUBLE
5613
                                  ? "number"
5614
                              : oFieldProps.m_eType ==
107✔
5615
                                      MVTTileLayerValue::ValueType::STRING
5616
                                  ? "string"
107✔
5617
                                  : "boolean");
5618

5619
                CPLJSONArray oValues;
360✔
5620
                oFieldObj.Add("values", oValues);
180✔
5621
                for (const auto &oIterValue : oFieldProps.m_oSetValues)
406✔
5622
                {
5623
                    if (oIterValue.getType() ==
226✔
5624
                        MVTTileLayerValue::ValueType::BOOL)
5625
                    {
5626
                        oValues.Add(oIterValue.getBoolValue());
1✔
5627
                    }
5628
                    else if (oIterValue.isNumeric())
225✔
5629
                    {
5630
                        if (oFieldProps.m_bAllInt)
104✔
5631
                        {
5632
                            oValues.Add(static_cast<GInt64>(
53✔
5633
                                oIterValue.getNumericValue()));
53✔
5634
                        }
5635
                        else
5636
                        {
5637
                            oValues.Add(oIterValue.getNumericValue());
51✔
5638
                        }
5639
                    }
5640
                    else if (oIterValue.isString())
121✔
5641
                    {
5642
                        oValues.Add(oIterValue.getStringValue());
121✔
5643
                    }
5644
                }
5645

5646
                if (oFieldProps.m_eType == MVTTileLayerValue::ValueType::DOUBLE)
180✔
5647
                {
5648
                    if (oFieldProps.m_bAllInt)
73✔
5649
                    {
5650
                        oFieldObj.Add(
37✔
5651
                            "min", static_cast<GInt64>(oFieldProps.m_dfMinVal));
37✔
5652
                        oFieldObj.Add(
37✔
5653
                            "max", static_cast<GInt64>(oFieldProps.m_dfMaxVal));
37✔
5654
                    }
5655
                    else
5656
                    {
5657
                        oFieldObj.Add("min", oFieldProps.m_dfMinVal);
36✔
5658
                        oFieldObj.Add("max", oFieldProps.m_dfMaxVal);
36✔
5659
                    }
5660
                }
5661
            }
5662

5663
            oTileStatsLayers.Add(oLayerObj);
71✔
5664
        }
5665
    }
5666

5667
    WriteMetadataItem("json", oJsonDoc.SaveAsString().c_str(), m_hDBMBTILES,
115✔
5668
                      oRoot);
5669

5670
    if (m_hDBMBTILES)
115✔
5671
    {
5672
        return true;
73✔
5673
    }
5674

5675
    return oDoc.Save(
126✔
5676
        CPLFormFilename(GetDescription(), "metadata.json", nullptr));
84✔
5677
}
5678

5679
/************************************************************************/
5680
/*                            WriteFeature()                            */
5681
/************************************************************************/
5682

5683
OGRErr OGRMVTWriterDataset::WriteFeature(OGRMVTWriterLayer *poLayer,
243✔
5684
                                         OGRFeature *poFeature, GIntBig nSerial,
5685
                                         OGRGeometry *poGeom)
5686
{
5687
    if (poFeature->GetGeometryRef() == poGeom)
243✔
5688
    {
5689
        m_oMapLayerNameToFeatureCount[poLayer->m_osTargetName]++;
185✔
5690
    }
5691

5692
    OGRwkbGeometryType eGeomType = wkbFlatten(poGeom->getGeometryType());
243✔
5693
    if (eGeomType == wkbGeometryCollection)
243✔
5694
    {
5695
        OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
21✔
5696
        for (int i = 0; i < poGC->getNumGeometries(); i++)
78✔
5697
        {
5698
            if (WriteFeature(poLayer, poFeature, nSerial,
58✔
5699
                             poGC->getGeometryRef(i)) != OGRERR_NONE)
58✔
5700
            {
5701
                return OGRERR_FAILURE;
1✔
5702
            }
5703
        }
5704
        return OGRERR_NONE;
20✔
5705
    }
5706

5707
    OGREnvelope sExtent;
222✔
5708
    poGeom->getEnvelope(&sExtent);
222✔
5709

5710
    if (!m_oEnvelope.IsInit())
222✔
5711
    {
5712
        CPLDebug("MVT", "Creating temporary database...");
67✔
5713
    }
5714

5715
    m_oEnvelope.Merge(sExtent);
222✔
5716

5717
    if (!m_bReuseTempFile)
222✔
5718
    {
5719
        auto poFeatureContent =
5720
            std::shared_ptr<OGRMVTFeatureContent>(new OGRMVTFeatureContent());
221✔
5721
        auto poSharedGeom = std::shared_ptr<OGRGeometry>(poGeom->clone());
221✔
5722

5723
        poFeatureContent->nFID = poFeature->GetFID();
221✔
5724

5725
        const OGRFeatureDefn *poFDefn = poFeature->GetDefnRef();
221✔
5726
        for (int i = 0; i < poFeature->GetFieldCount(); i++)
986✔
5727
        {
5728
            if (poFeature->IsFieldSetAndNotNull(i))
765✔
5729
            {
5730
                MVTTileLayerValue oValue;
665✔
5731
                const OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(i);
665✔
5732
                OGRFieldType eFieldType = poFieldDefn->GetType();
665✔
5733
                if (eFieldType == OFTInteger || eFieldType == OFTInteger64)
665✔
5734
                {
5735
                    if (poFieldDefn->GetSubType() == OFSTBoolean)
145✔
5736
                    {
5737
                        oValue.setBoolValue(poFeature->GetFieldAsInteger(i) !=
2✔
5738
                                            0);
5739
                    }
5740
                    else
5741
                    {
5742
                        oValue.setValue(poFeature->GetFieldAsInteger64(i));
143✔
5743
                    }
5744
                }
5745
                else if (eFieldType == OFTReal)
520✔
5746
                {
5747
                    oValue.setValue(poFeature->GetFieldAsDouble(i));
140✔
5748
                }
5749
                else if (eFieldType == OFTDate || eFieldType == OFTDateTime)
380✔
5750
                {
5751
                    int nYear, nMonth, nDay, nHour, nMin, nTZ;
5752
                    float fSec;
5753
                    poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay,
238✔
5754
                                                  &nHour, &nMin, &fSec, &nTZ);
5755
                    CPLString osFormatted;
476✔
5756
                    if (eFieldType == OFTDate)
238✔
5757
                    {
5758
                        osFormatted.Printf("%04d-%02d-%02d", nYear, nMonth,
119✔
5759
                                           nDay);
119✔
5760
                    }
5761
                    else
5762
                    {
5763
                        char *pszFormatted =
5764
                            OGRGetXMLDateTime(poFeature->GetRawFieldRef(i));
119✔
5765
                        osFormatted = pszFormatted;
119✔
5766
                        CPLFree(pszFormatted);
119✔
5767
                    }
5768
                    oValue.setStringValue(osFormatted);
476✔
5769
                }
5770
                else
5771
                {
5772
                    oValue.setStringValue(
142✔
5773
                        std::string(poFeature->GetFieldAsString(i)));
284✔
5774
                }
5775

5776
                poFeatureContent->oValues.emplace_back(
665✔
5777
                    std::pair<std::string, MVTTileLayerValue>(
665✔
5778
                        poFieldDefn->GetNameRef(), oValue));
1,330✔
5779
            }
5780
        }
5781

5782
        for (int nZ = poLayer->m_nMinZoom; nZ <= poLayer->m_nMaxZoom; nZ++)
1,513✔
5783
        {
5784
            double dfTileDim = m_dfTileDim0 / (1 << nZ);
1,293✔
5785
            double dfBuffer = dfTileDim * m_nBuffer / m_nExtent;
1,293✔
5786
            const int nTileMinX = std::max(
5787
                0, static_cast<int>((sExtent.MinX - m_dfTopX - dfBuffer) /
2,586✔
5788
                                    dfTileDim));
1,293✔
5789
            const int nTileMinY = std::max(
5790
                0, static_cast<int>((m_dfTopY - sExtent.MaxY - dfBuffer) /
2,586✔
5791
                                    dfTileDim));
1,293✔
5792
            const int nTileMaxX =
5793
                std::min(static_cast<int>((sExtent.MaxX - m_dfTopX + dfBuffer) /
2,586✔
5794
                                          dfTileDim),
5795
                         (1 << nZ) - 1);
1,293✔
5796
            const int nTileMaxY =
5797
                std::min(static_cast<int>((m_dfTopY - sExtent.MinY + dfBuffer) /
2,586✔
5798
                                          dfTileDim),
5799
                         (1 << nZ) - 1);
1,293✔
5800
            for (int iX = nTileMinX; iX <= nTileMaxX; iX++)
3,507✔
5801
            {
5802
                for (int iY = nTileMinY; iY <= nTileMaxY; iY++)
6,225✔
5803
                {
5804
                    if (PreGenerateForTile(
8,022✔
5805
                            nZ, iX, iY, poLayer->m_osTargetName,
4,011✔
5806
                            (nZ == poLayer->m_nMaxZoom), poFeatureContent,
4,011✔
5807
                            nSerial, poSharedGeom, sExtent) != OGRERR_NONE)
4,011✔
5808
                    {
5809
                        return OGRERR_FAILURE;
1✔
5810
                    }
5811
                }
5812
            }
5813
        }
5814
    }
5815

5816
    return OGRERR_NONE;
221✔
5817
}
5818

5819
/************************************************************************/
5820
/*                            TestCapability()                          */
5821
/************************************************************************/
5822

5823
int OGRMVTWriterDataset::TestCapability(const char *pszCap)
189✔
5824
{
5825
    if (EQUAL(pszCap, ODsCCreateLayer))
189✔
5826
        return true;
111✔
5827
    return false;
78✔
5828
}
5829

5830
/************************************************************************/
5831
/*                         ValidateMinMaxZoom()                         */
5832
/************************************************************************/
5833

5834
static bool ValidateMinMaxZoom(int nMinZoom, int nMaxZoom)
289✔
5835
{
5836
    if (nMinZoom < 0 || nMinZoom > 22)
289✔
5837
    {
5838
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid MINZOOM");
2✔
5839
        return false;
2✔
5840
    }
5841
    if (nMaxZoom < 0 || nMaxZoom > 22)
287✔
5842
    {
5843
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM");
1✔
5844
        return false;
1✔
5845
    }
5846
    if (nMaxZoom < nMinZoom)
286✔
5847
    {
5848
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid MAXZOOM < MINZOOM");
1✔
5849
        return false;
1✔
5850
    }
5851
    return true;
285✔
5852
}
5853

5854
/************************************************************************/
5855
/*                           ICreateLayer()                             */
5856
/************************************************************************/
5857

5858
OGRLayer *
5859
OGRMVTWriterDataset::ICreateLayer(const char *pszLayerName,
165✔
5860
                                  const OGRGeomFieldDefn *poGeomFieldDefn,
5861
                                  CSLConstList papszOptions)
5862
{
5863
    OGRSpatialReference *poSRSClone = nullptr;
165✔
5864
    const auto poSRS =
5865
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
165✔
5866
    if (poSRS)
165✔
5867
    {
5868
        poSRSClone = poSRS->Clone();
4✔
5869
        poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4✔
5870
    }
5871
    OGRMVTWriterLayer *poLayer =
5872
        new OGRMVTWriterLayer(this, pszLayerName, poSRSClone);
165✔
5873
    if (poSRSClone)
165✔
5874
        poSRSClone->Release();
4✔
5875
    poLayer->m_nMinZoom = m_nMinZoom;
165✔
5876
    poLayer->m_nMaxZoom = m_nMaxZoom;
165✔
5877
    poLayer->m_osTargetName = pszLayerName;
165✔
5878

5879
    /*
5880

5881
            {
5882
                "src_layer":
5883
                    { "target_name": "",
5884
                      "description": "",
5885
                      "minzoom": 0,
5886
                      "maxzoom": 0
5887
                    }
5888
            }
5889
    */
5890

5891
    CPLJSONObject oObj = m_oConf.GetRoot().GetObj(pszLayerName);
495✔
5892
    CPLString osDescription;
330✔
5893
    if (oObj.IsValid())
165✔
5894
    {
5895
        std::string osTargetName = oObj.GetString("target_name");
4✔
5896
        if (!osTargetName.empty())
2✔
5897
            poLayer->m_osTargetName = std::move(osTargetName);
2✔
5898
        int nMinZoom = oObj.GetInteger("minzoom", -1);
2✔
5899
        if (nMinZoom >= 0)
2✔
5900
            poLayer->m_nMinZoom = nMinZoom;
2✔
5901
        int nMaxZoom = oObj.GetInteger("maxzoom", -1);
2✔
5902
        if (nMaxZoom >= 0)
2✔
5903
            poLayer->m_nMaxZoom = nMaxZoom;
2✔
5904
        osDescription = oObj.GetString("description");
2✔
5905
    }
5906

5907
    poLayer->m_nMinZoom = atoi(CSLFetchNameValueDef(
165✔
5908
        papszOptions, "MINZOOM", CPLSPrintf("%d", poLayer->m_nMinZoom)));
5909
    poLayer->m_nMaxZoom = atoi(CSLFetchNameValueDef(
165✔
5910
        papszOptions, "MAXZOOM", CPLSPrintf("%d", poLayer->m_nMaxZoom)));
5911
    if (!ValidateMinMaxZoom(poLayer->m_nMinZoom, poLayer->m_nMaxZoom))
165✔
5912
    {
5913
        delete poLayer;
1✔
5914
        return nullptr;
1✔
5915
    }
5916
    poLayer->m_osTargetName = CSLFetchNameValueDef(
5917
        papszOptions, "NAME", poLayer->m_osTargetName.c_str());
164✔
5918
    osDescription =
5919
        CSLFetchNameValueDef(papszOptions, "DESCRIPTION", osDescription);
164✔
5920
    if (!osDescription.empty())
164✔
5921
        m_oMapLayerNameToDesc[poLayer->m_osTargetName] =
4✔
5922
            std::move(osDescription);
2✔
5923

5924
    m_apoLayers.push_back(std::unique_ptr<OGRMVTWriterLayer>(poLayer));
164✔
5925
    return m_apoLayers.back().get();
164✔
5926
}
5927

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

5932
GDALDataset *OGRMVTWriterDataset::Create(const char *pszFilename, int nXSize,
134✔
5933
                                         int nYSize, int nBandsIn,
5934
                                         GDALDataType eDT, char **papszOptions)
5935
{
5936
    if (nXSize != 0 || nYSize != 0 || nBandsIn != 0 || eDT != GDT_Unknown)
134✔
5937
    {
5938
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
5939
                 "Only vector creation supported");
5940
        return nullptr;
1✔
5941
    }
5942

5943
    const char *pszFormat = CSLFetchNameValue(papszOptions, "FORMAT");
133✔
5944
    const bool bMBTILESExt = EQUAL(CPLGetExtension(pszFilename), "mbtiles");
133✔
5945
    if (pszFormat == nullptr && bMBTILESExt)
133✔
5946
    {
5947
        pszFormat = "MBTILES";
3✔
5948
    }
5949
    const bool bMBTILES = pszFormat != nullptr && EQUAL(pszFormat, "MBTILES");
133✔
5950

5951
    // For debug only
5952
    bool bReuseTempFile =
5953
        CPLTestBool(CPLGetConfigOption("OGR_MVT_REUSE_TEMP_FILE", "NO"));
133✔
5954

5955
    if (bMBTILES)
133✔
5956
    {
5957
        if (!bMBTILESExt)
78✔
5958
        {
5959
            CPLError(CE_Failure, CPLE_FileIO,
1✔
5960
                     "%s should have mbtiles extension", pszFilename);
5961
            return nullptr;
1✔
5962
        }
5963

5964
        VSIUnlink(pszFilename);
77✔
5965
    }
5966
    else
5967
    {
5968
        VSIStatBufL sStat;
5969
        if (VSIStatL(pszFilename, &sStat) == 0)
55✔
5970
        {
5971
            CPLError(CE_Failure, CPLE_FileIO, "%s already exists", pszFilename);
3✔
5972
            return nullptr;
5✔
5973
        }
5974

5975
        if (VSIMkdir(pszFilename, 0755) != 0)
52✔
5976
        {
5977
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create directory %s",
2✔
5978
                     pszFilename);
5979
            return nullptr;
2✔
5980
        }
5981
    }
5982

5983
    OGRMVTWriterDataset *poDS = new OGRMVTWriterDataset();
127✔
5984
    poDS->m_pMyVFS = OGRSQLiteCreateVFS(nullptr, poDS);
127✔
5985
    sqlite3_vfs_register(poDS->m_pMyVFS, 0);
127✔
5986

5987
    CPLString osTempDBDefault = CPLString(pszFilename) + ".temp.db";
381✔
5988
    if (STARTS_WITH(osTempDBDefault, "/vsizip/"))
127✔
5989
    {
5990
        osTempDBDefault =
5991
            CPLString(pszFilename + strlen("/vsizip/")) + ".temp.db";
×
5992
    }
5993
    CPLString osTempDB = CSLFetchNameValueDef(papszOptions, "TEMPORARY_DB",
5994
                                              osTempDBDefault.c_str());
254✔
5995
    if (!bReuseTempFile)
127✔
5996
        VSIUnlink(osTempDB);
126✔
5997

5998
    sqlite3 *hDB = nullptr;
127✔
5999
    if (sqlite3_open_v2(osTempDB, &hDB,
127✔
6000
                        SQLITE_OPEN_READWRITE |
6001
                            (bReuseTempFile ? 0 : SQLITE_OPEN_CREATE) |
6002
                            SQLITE_OPEN_NOMUTEX,
6003
                        poDS->m_pMyVFS->zName) != SQLITE_OK ||
378✔
6004
        hDB == nullptr)
124✔
6005
    {
6006
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", osTempDB.c_str());
3✔
6007
        delete poDS;
3✔
6008
        sqlite3_close(hDB);
3✔
6009
        return nullptr;
3✔
6010
    }
6011
    poDS->m_osTempDB = osTempDB;
124✔
6012
    poDS->m_hDB = hDB;
124✔
6013
    poDS->m_bReuseTempFile = bReuseTempFile;
124✔
6014

6015
    // For Unix
6016
    if (!poDS->m_bReuseTempFile &&
247✔
6017
        CPLTestBool(CPLGetConfigOption("OGR_MVT_REMOVE_TEMP_FILE", "YES")))
123✔
6018
    {
6019
        VSIUnlink(osTempDB);
120✔
6020
    }
6021

6022
    if (poDS->m_bReuseTempFile)
124✔
6023
    {
6024
        poDS->m_nTempTiles =
1✔
6025
            SQLGetInteger64(hDB, "SELECT COUNT(*) FROM temp", nullptr);
1✔
6026
    }
6027
    else
6028
    {
6029
        CPL_IGNORE_RET_VAL(SQLCommand(
123✔
6030
            hDB,
6031
            "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
6032
            "PRAGMA synchronous = OFF;"
6033
            "PRAGMA journal_mode = OFF;"
6034
            "PRAGMA temp_store = MEMORY;"
6035
            "CREATE TABLE temp(z INTEGER, x INTEGER, y INTEGER, layer TEXT, "
6036
            "idx INTEGER, feature BLOB, geomtype INTEGER, area_or_length "
6037
            "DOUBLE);"
6038
            "CREATE INDEX temp_index ON temp (z, x, y, layer, idx);"));
6039
    }
6040

6041
    sqlite3_stmt *hInsertStmt = nullptr;
124✔
6042
    CPL_IGNORE_RET_VAL(sqlite3_prepare_v2(
124✔
6043
        hDB,
6044
        "INSERT INTO temp (z,x,y,layer,idx,feature,geomtype,area_or_length) "
6045
        "VALUES (?,?,?,?,?,?,?,?)",
6046
        -1, &hInsertStmt, nullptr));
6047
    if (hInsertStmt == nullptr)
124✔
6048
    {
6049
        delete poDS;
×
6050
        return nullptr;
×
6051
    }
6052
    poDS->m_hInsertStmt = hInsertStmt;
124✔
6053

6054
    poDS->m_nMinZoom = atoi(CSLFetchNameValueDef(
124✔
6055
        papszOptions, "MINZOOM", CPLSPrintf("%d", poDS->m_nMinZoom)));
6056
    poDS->m_nMaxZoom = atoi(CSLFetchNameValueDef(
124✔
6057
        papszOptions, "MAXZOOM", CPLSPrintf("%d", poDS->m_nMaxZoom)));
6058
    if (!ValidateMinMaxZoom(poDS->m_nMinZoom, poDS->m_nMaxZoom))
124✔
6059
    {
6060
        delete poDS;
3✔
6061
        return nullptr;
3✔
6062
    }
6063

6064
    const char *pszConf = CSLFetchNameValue(papszOptions, "CONF");
121✔
6065
    if (pszConf)
121✔
6066
    {
6067
        VSIStatBufL sStat;
6068
        bool bSuccess;
6069
        if (VSIStatL(pszConf, &sStat) == 0)
3✔
6070
        {
6071
            bSuccess = poDS->m_oConf.Load(pszConf);
2✔
6072
        }
6073
        else
6074
        {
6075
            bSuccess = poDS->m_oConf.LoadMemory(pszConf);
1✔
6076
        }
6077
        if (!bSuccess)
3✔
6078
        {
6079
            delete poDS;
1✔
6080
            return nullptr;
1✔
6081
        }
6082
    }
6083

6084
    poDS->m_dfSimplification =
120✔
6085
        CPLAtof(CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION", "0"));
120✔
6086
    poDS->m_dfSimplificationMaxZoom = CPLAtof(
120✔
6087
        CSLFetchNameValueDef(papszOptions, "SIMPLIFICATION_MAX_ZOOM",
6088
                             CPLSPrintf("%g", poDS->m_dfSimplification)));
6089
    poDS->m_nExtent = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
120✔
6090
        papszOptions, "EXTENT", CPLSPrintf("%u", poDS->m_nExtent))));
6091
    poDS->m_nBuffer = static_cast<unsigned>(atoi(CSLFetchNameValueDef(
120✔
6092
        papszOptions, "BUFFER", CPLSPrintf("%u", 5 * poDS->m_nExtent / 256))));
120✔
6093

6094
    poDS->m_nMaxTileSize =
120✔
6095
        std::max(100U, static_cast<unsigned>(atoi(CSLFetchNameValueDef(
120✔
6096
                           papszOptions, "MAX_SIZE",
6097
                           CPLSPrintf("%u", poDS->m_nMaxTileSize)))));
120✔
6098
    poDS->m_nMaxFeatures =
120✔
6099
        std::max(1U, static_cast<unsigned>(atoi(CSLFetchNameValueDef(
120✔
6100
                         papszOptions, "MAX_FEATURES",
6101
                         CPLSPrintf("%u", poDS->m_nMaxFeatures)))));
120✔
6102

6103
    poDS->m_osName =
6104
        CSLFetchNameValueDef(papszOptions, "NAME", CPLGetBasename(pszFilename));
120✔
6105
    poDS->m_osDescription = CSLFetchNameValueDef(papszOptions, "DESCRIPTION",
6106
                                                 poDS->m_osDescription.c_str());
120✔
6107
    poDS->m_osType =
6108
        CSLFetchNameValueDef(papszOptions, "TYPE", poDS->m_osType.c_str());
120✔
6109
    poDS->m_bGZip = CPLFetchBool(papszOptions, "COMPRESS", poDS->m_bGZip);
120✔
6110
    poDS->m_osBounds = CSLFetchNameValueDef(papszOptions, "BOUNDS", "");
120✔
6111
    poDS->m_osCenter = CSLFetchNameValueDef(papszOptions, "CENTER", "");
120✔
6112
    poDS->m_osExtension = CSLFetchNameValueDef(papszOptions, "TILE_EXTENSION",
6113
                                               poDS->m_osExtension);
120✔
6114

6115
    const char *pszTilingScheme =
6116
        CSLFetchNameValue(papszOptions, "TILING_SCHEME");
120✔
6117
    if (pszTilingScheme)
120✔
6118
    {
6119
        if (bMBTILES)
3✔
6120
        {
6121
            CPLError(CE_Failure, CPLE_NotSupported,
1✔
6122
                     "Custom TILING_SCHEME not supported with MBTILES output");
6123
            delete poDS;
1✔
6124
            return nullptr;
2✔
6125
        }
6126

6127
        CPLStringList aoList(CSLTokenizeString2(pszTilingScheme, ",", 0));
2✔
6128
        if (aoList.Count() == 4)
2✔
6129
        {
6130
            poDS->m_poSRS->SetFromUserInput(aoList[0]);
1✔
6131
            poDS->m_dfTopX = CPLAtof(aoList[1]);
1✔
6132
            poDS->m_dfTopY = CPLAtof(aoList[2]);
1✔
6133
            poDS->m_dfTileDim0 = CPLAtof(aoList[3]);
1✔
6134
        }
6135
        else
6136
        {
6137
            CPLError(CE_Failure, CPLE_AppDefined,
1✔
6138
                     "Wrong format for TILING_SCHEME. "
6139
                     "Expecting EPSG:XXXX,tile_origin_upper_left_x,"
6140
                     "tile_origin_upper_left_y,tile_dimension_zoom_0");
6141
            delete poDS;
1✔
6142
            return nullptr;
1✔
6143
        }
6144
    }
6145

6146
    if (bMBTILES)
118✔
6147
    {
6148
        if (sqlite3_open_v2(pszFilename, &poDS->m_hDBMBTILES,
222✔
6149
                            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
6150
                                SQLITE_OPEN_NOMUTEX,
6151
                            poDS->m_pMyVFS->zName) != SQLITE_OK ||
147✔
6152
            poDS->m_hDBMBTILES == nullptr)
73✔
6153
        {
6154
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", pszFilename);
1✔
6155
            delete poDS;
1✔
6156
            return nullptr;
1✔
6157
        }
6158

6159
        if (SQLCommand(
73✔
6160
                poDS->m_hDBMBTILES,
6161
                "PRAGMA page_size = 4096;"  // 4096: default since sqlite 3.12
6162
                "PRAGMA synchronous = OFF;"
6163
                "PRAGMA journal_mode = OFF;"
6164
                "PRAGMA temp_store = MEMORY;"
6165
                "CREATE TABLE metadata (name text, value text);"
6166
                "CREATE TABLE tiles (zoom_level integer, tile_column integer, "
6167
                "tile_row integer, tile_data blob, "
6168
                "UNIQUE (zoom_level, tile_column, tile_row))") != OGRERR_NONE)
73✔
6169
        {
6170
            delete poDS;
×
6171
            return nullptr;
×
6172
        }
6173
    }
6174

6175
    int nThreads = CPLGetNumCPUs();
117✔
6176
    const char *pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
117✔
6177
    if (pszNumThreads && CPLGetValueType(pszNumThreads) == CPL_VALUE_INTEGER)
117✔
6178
    {
6179
        nThreads = atoi(pszNumThreads);
2✔
6180
    }
6181
    if (nThreads > 1)
117✔
6182
    {
6183
        poDS->m_bThreadPoolOK =
115✔
6184
            poDS->m_oThreadPool.Setup(nThreads, nullptr, nullptr);
115✔
6185
    }
6186

6187
    poDS->SetDescription(pszFilename);
117✔
6188
    return poDS;
117✔
6189
}
6190

6191
GDALDataset *OGRMVTWriterDatasetCreate(const char *pszFilename, int nXSize,
74✔
6192
                                       int nYSize, int nBandsIn,
6193
                                       GDALDataType eDT, char **papszOptions)
6194
{
6195
    return OGRMVTWriterDataset::Create(pszFilename, nXSize, nYSize, nBandsIn,
74✔
6196
                                       eDT, papszOptions);
74✔
6197
}
6198

6199
#endif  // HAVE_MVT_WRITE_SUPPORT
6200

6201
/************************************************************************/
6202
/*                           RegisterOGRMVT()                           */
6203
/************************************************************************/
6204

6205
void RegisterOGRMVT()
1,512✔
6206

6207
{
6208
    if (GDALGetDriverByName("MVT") != nullptr)
1,512✔
6209
        return;
295✔
6210

6211
    GDALDriver *poDriver = new GDALDriver();
1,217✔
6212

6213
    poDriver->SetDescription("MVT");
1,217✔
6214
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1,217✔
6215
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Mapbox Vector Tiles");
1,217✔
6216
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/mvt.html");
1,217✔
6217
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,217✔
6218
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "mvt mvt.gz pbf");
1,217✔
6219
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
1,217✔
6220

6221
    poDriver->SetMetadataItem(
1,217✔
6222
        GDAL_DMD_OPENOPTIONLIST,
6223
        "<OpenOptionList>"
6224
        "  <Option name='X' type='int' description='X coordinate of tile'/>"
6225
        "  <Option name='Y' type='int' description='Y coordinate of tile'/>"
6226
        "  <Option name='Z' type='int' description='Z coordinate of tile'/>"
6227
        //"  <Option name='@GEOREF_TOPX' type='float' description='X coordinate
6228
        // of top-left corner of tile'/>" "  <Option name='@GEOREF_TOPY'
6229
        // type='float' description='Y coordinate of top-left corner of tile'/>"
6230
        //"  <Option name='@GEOREF_TILEDIMX' type='float' description='Tile
6231
        // width in georeferenced units'/>" "  <Option name='@GEOREF_TILEDIMY'
6232
        // type='float' description='Tile height in georeferenced units'/>"
6233
        "  <Option name='METADATA_FILE' type='string' "
6234
        "description='Path to metadata.json'/>"
6235
        "  <Option name='CLIP' type='boolean' "
6236
        "description='Whether to clip geometries to tile extent' "
6237
        "default='YES'/>"
6238
        "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
6239
        "description="
6240
        "'For tilesets, extension of tiles'/>"
6241
        "  <Option name='TILE_COUNT_TO_ESTABLISH_FEATURE_DEFN' type='int' "
6242
        "description="
6243
        "'For tilesets without metadata file, maximum number of tiles to use "
6244
        "to "
6245
        "establish the layer schemas' default='1000'/>"
6246
        "  <Option name='JSON_FIELD' type='boolean' description='For tilesets, "
6247
        "whether to put all attributes as a serialized JSon dictionary'/>"
6248
        "</OpenOptionList>");
1,217✔
6249

6250
    poDriver->pfnIdentify = OGRMVTDriverIdentify;
1,217✔
6251
    poDriver->pfnOpen = OGRMVTDataset::Open;
1,217✔
6252
#ifdef HAVE_MVT_WRITE_SUPPORT
6253
    poDriver->pfnCreate = OGRMVTWriterDataset::Create;
1,217✔
6254
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
1,217✔
6255
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
1,217✔
6256
    poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
1,217✔
6257
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES,
1,217✔
6258
                              "Integer Integer64 Real String");
1,217✔
6259
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
1,217✔
6260
                              "Boolean Float32");
1,217✔
6261
    poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "SQLITE OGRSQL");
1,217✔
6262

6263
    poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, MVT_LCO);
1,217✔
6264

6265
    poDriver->SetMetadataItem(
1,217✔
6266
        GDAL_DMD_CREATIONOPTIONLIST,
6267
        "<CreationOptionList>"
6268
        "  <Option name='NAME' type='string' description='Tileset name'/>"
6269
        "  <Option name='DESCRIPTION' type='string' "
6270
        "description='A description of the tileset'/>"
6271
        "  <Option name='TYPE' type='string-select' description='Layer type' "
6272
        "default='overlay'>"
6273
        "    <Value>overlay</Value>"
6274
        "    <Value>baselayer</Value>"
6275
        "  </Option>"
6276
        "  <Option name='FORMAT' type='string-select' description='Format'>"
6277
        "    <Value>DIRECTORY</Value>"
6278
        "    <Value>MBTILES</Value>"
6279
        "  </Option>"
6280
        "  <Option name='TILE_EXTENSION' type='string' default='pbf' "
6281
        "description="
6282
        "'For tilesets as directories of files, extension of "
6283
        "tiles'/>" MVT_MBTILES_COMMON_DSCO
6284
        "  <Option name='BOUNDS' type='string' "
6285
        "description='Override default value for bounds metadata item'/>"
6286
        "  <Option name='CENTER' type='string' "
6287
        "description='Override default value for center metadata item'/>"
6288
        "  <Option name='TILING_SCHEME' type='string' "
6289
        "description='Custom tiling scheme with following format "
6290
        "\"EPSG:XXXX,tile_origin_upper_left_x,tile_origin_upper_left_y,"
6291
        "tile_dimension_zoom_0\"'/>"
6292
        "</CreationOptionList>");
1,217✔
6293
#endif  // HAVE_MVT_WRITE_SUPPORT
6294

6295
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1,217✔
6296

6297
    GetGDALDriverManager()->RegisterDriver(poDriver);
1,217✔
6298
}
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