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